Telemetry and licence refresh
Annotide makes two regular outbound calls to R Squared Data Solutions’
licence server, plus a one-off trial request when an administrator asks for
a trial. None of them affects whether your installation works. This page
lists exactly what each one sends, field by field, because “we only send
anonymous data” is a claim worth being able to check rather than take on
faith — and because docs/LICENSING.md in the source repository is the
specification this page summarizes.
The rule that matters more than either call
Section titled “The rule that matters more than either call”No licence check in Annotide depends on reaching a server we operate. If the licence server is unreachable, down, or you block it at your firewall, your installation keeps working on the licence state (key, seat count, grace period) it already has. The heartbeat and the licence refresh are the only regular calls; both are on by default and each can be switched off on its own. The trial request is sent only when an administrator clicks Start trial.
The heartbeat (telemetry)
Section titled “The heartbeat (telemetry)”| Who sends it | Every edition, including Community |
| Default | On |
| Turn off | APP_TELEMETRY_ENABLED=false |
| Interval | Weekly |
| Purpose | Lets us notice when several separate Community installations belong to one organisation that might be better served by one Team or Business installation, without identifying any person; and, counted in aggregate only, how many installations run which version and edition |
Fields sent:
| Field | Value | Personal data? |
|---|---|---|
| Install id | A random identifier generated when the installation is set up (APP_INSTALL_ID) |
No — not tied to a person |
| Version | The installed Annotide version | No |
| Licence type | community, or the tier of the licence in force, even if it has expired |
No |
| Active user count | A number | No |
| Organisation fingerprint | One salted HMAC-SHA256 hash per signal found (see below) | Organisation-level, not about people — see “What the hashing does” |
Your admin also sees what was left out of the fingerprint and why (for
example email domain: gmail.com is a public provider). That list stays in
your installation and is never sent.
The organisation fingerprint is built from whichever of these signals are present, each individually salted and hashed before it ever leaves your installation:
| Signal | What’s hashed | What’s never sent |
|---|---|---|
| Email domain | The domain only, e.g. acme.com from anna@acme.com — the local part (anna) is dropped before hashing |
The email address itself |
| SSO/IdP tenant | Your OIDC tenant identifier | Any user’s identity within it |
| Cloud account | Your Azure subscription ID, AWS account ID or GCP project ID | Resource names or contents |
| Storage account | The connector’s target storage account/bucket identifier | Anything stored in it |
| Public hostname | The hostname your installation is reachable on, e.g. annotate.acme.com |
— |
| Egress network | Derived server-side from the connection, not sent as a field — see below | — |
Public email providers (Gmail, Outlook.com, etc.) and consumer cloud accounts are excluded entirely — they’re dropped before hashing, so a hobbyist’s Gmail address never contributes to the fingerprint.
What the hashing does: each signal is hashed with HMAC-SHA256 and a
salt (APP_LICENSE_FINGERPRINT_SALT) before it’s sent. The salt is the same
for every installation — it has to be, or two installations of the same
organisation wouldn’t produce the same hash — so it is not a secret from
us, and whoever holds it can test a guess (“is this the hash of
acme.com?”). We compare hashes with hashes to see that two installations
share a domain or tenant; we don’t try to turn hashes back into names. The
signals describe organisations, not people. The salt is built into the
software; turning the heartbeat off is how you send no fingerprint.
What we store: before a heartbeat is saved, the licence server hashes each of those hashes again with a secret key that only the server holds. Two installations of the same organisation still match, but the stored values can’t be checked against guesses with the public salt alone, so a copy of our database doesn’t reveal which domains or tenants are in it.
What happens on arrival: the licence server takes your network’s
prefix only — the IPv4 /24 or IPv6 /48 — and stores it as a hash
made with the same secret key; neither the full source address nor the
prefix itself is ever stored. Heartbeat records are deleted after 12 months. This is a weak,
corroborating signal on its own (shared corporate NAT or cloud egress
means unrelated installations can share a prefix) and by itself can never
trigger anything (docs/LICENSING.md, Clustering).
What we do with a match: at most, a human at R Squared Data Solutions reviews a cluster of installations that share several of these signals and decides whether to reach out with an offer. No automated message, invoice or restriction is ever triggered directly by a heartbeat.
The licence refresh (not telemetry)
Section titled “The licence refresh (not telemetry)”| Who sends it | Installations with a licence key |
| Default | On |
| Turn off | APP_LICENSE_REFRESH_ENABLED=false, or paste keys by hand instead |
| Interval | Daily, with retries |
| Purpose | Renews your key automatically so a paying customer never has to manually update it |
Fields sent: licence id, install id, host, active user count, and the installed version. Fields received back: the renewed key (if one exists) and the current revocation list.
This call is licence accounting, not telemetry: it carries no organisation fingerprint and no user identifiers, and it’s on by default for the same reason a browser checks for software updates by default — so that a customer who has already paid doesn’t have to do anything for their key to keep working.
The trial request (only when you ask)
Section titled “The trial request (only when you ask)”| Who sends it | An installation whose administrator clicks Start trial |
| Default | Never sent unless clicked |
| Interval | Once |
| Purpose | Issues a 30-day Business trial key, once per installation |
Fields sent: install id and host. Fields received back: the trial key. We keep the install id, the host and the dates, so the same installation or host can’t start a second trial. No name, e-mail or user data is asked for or stored.
Viewing the exact payload
Section titled “Viewing the exact payload”Before a call is sent, you can see precisely what it contains:
an admin can open Settings → Licence (/settings/license) in your own
installation to see the licence status, the “Licence server” section
showing the last refresh, and the exact heartbeat payload — hashes and
all — the same body the API also serves at
GET /api/v1/licensing/telemetry/preview.
Turning both off
Section titled “Turning both off”Set in your environment (.env or your deployment’s secret store) and
restart:
APP_TELEMETRY_ENABLED=falseAPP_LICENSE_REFRESH_ENABLED=falseSetting APP_LICENSE_SERVER_URL to an empty value disables both calls, and
the trial request, at once, since there’s nowhere to send them. An installation with telemetry off is not
treated as suspicious — it simply provides no signal, and your purchased
licence continues to work exactly as it would otherwise, right up to
expires_at plus its grace period. Enterprise customers who prefer never
to reach our servers at all can use fully offline keys and export a seat
report for manual true-up instead — see docs/LICENSING.md and
docs/OPERATIONS.md in the source repository.