Usage Reporting
What an online KubeMQ server sends to the licensing service every 15 minutes — each field and why, what is never sent, retention, and how to verify it.
An online server (one licensed with a key) sends a small usage report every 15 minutes with ±10% jitter, plus one at boot and one when it drains. The report is what usage-based plans are billed on, and what lets KubeMQ tell an active installation from a dead one. An offline server (licensed with a file) sends nothing — ever.
There is no switch to turn reporting off on an online license. If your network cannot allow it, the answer is a pre-activated file.
The report
POST https://license.kubemq.io/v1/usage/report, authenticated with the current
lease as a bearer token. The body:
{
"license_id": "6f1c2a3e-9b4d-4c5e-8a7f-1d2e3f4a5b6c",
"fingerprint": "3f2a9c1e-7b6d-4e5f-9a8b-0c1d2e3f4a5b",
"boot_id": "0b3d5f7a-9c1e-4a2b-8d6f-1e3c5a7b9d0f",
"source": {
"type": "kubernetes",
"host_id": "node-a",
"cluster_id": "3f2a9c1e-7b6d-4e5f-9a8b-0c1d2e3f4a5b",
"namespace": "kubemq",
"pod": "kubemq-next-0",
"version": "next-1.0.0",
"image_digest": "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
},
"cursor": { "from": "2026-09-19T12:00:00Z", "to": "2026-09-19T12:15:00Z" },
"totals": { "messages": 123456789, "volume_bytes": 987654321 },
"activity": null
}The host_id and image_digest values above are illustrative. On an operator-managed
Kubernetes cluster host_id is the node name (operator 1.1.1 and later inject NODE_NAME
on every pod) and image_digest is filled only when the cluster's image reference is
pinned by digest (…@sha256:…); with a tag-only reference it stays empty.
Field by field:
| Field | What it is | Why it is sent |
|---|---|---|
license_id | Your license's public id (a uuid) | Attributes the report to a license. It must match the lease used as bearer |
fingerprint | This installation's identity — the kube-system namespace UID on Kubernetes, a persisted random id elsewhere | Counts installations against your cap, and must match the lease |
boot_id | A random uuid generated when the process started; constant until it exits | Lets the service tell a restart from a continuing run, so counters can be cumulative (below) |
source.type | standalone, docker or kubernetes | Support context; how the server detected its environment |
source.host_id | OS or container hostname; on Kubernetes the node name from NODE_NAME, which operator 1.1.1 and later inject on every pod | Support context — which machine |
source.cluster_id | The kube-system UID (Kubernetes only; empty if unreadable) | Groups pods of one cluster |
source.namespace, source.pod | Pod namespace and name (Kubernetes only): POD_NAMESPACE (else the service-account namespace file, else default) and POD_NAME (else the hostname) | Support context — which pod |
source.version | The server version | Which release is running |
source.image_digest | The digest of the running image — sent only when the deployment injects it as KUBEMQ_IMAGE_DIGEST (a well-formed sha256: digest). The server cannot read its own digest; without the variable the field is empty (omitted on a standalone binary). On Kubernetes, operator 1.1.1 and later inject it when the cluster's image reference is pinned by digest (…@sha256:…); a tag-only reference leaves it empty | Confirms the server is the signed official image, once injected |
cursor.from, cursor.to | The window this report covers (UTC) | Ordering and de-duplication; windows never overlap and always advance |
totals.messages | Messages handled since this boot_id started | The billable count |
totals.volume_bytes | Bytes handled since this boot_id started | The billable volume |
activity | "start" on the boot report, "stop" on the drain report, null otherwise | Marks the edges of a run |
Cumulative counters
totals are cumulative since boot, never deltas. The service subtracts the previous
accepted report for the same (license, fingerprint, boot_id) to get the 15-minute
increment; a new boot_id starts from zero. This is why nothing is lost if a report is
missed — the next one carries the full count — and why the server cannot shrink a bill
by choosing windows. A counter that goes backwards without a new boot_id is clamped
to zero and flagged, never silently accepted.
Delivery
- At-least-once. The server keeps a cursor in
metrics.dbunder the store path and advances it only after the service acknowledges (200). A report sent twice — for example after a crash between the acknowledgement and the cursor write — is answeredduplicateand not counted again. A401(the lease the report carried is no longer accepted) keeps the row for the next attempt with the next lease; a413or422is terminal — the row is dropped and the cursor advances, so one bad report cannot block the ones after it. - Rate limited. The service allows a burst of 5 and one report per minute per
license; the server's 15-minute cadence sits far inside that. A
Retry-Afteron a429(or any other unexpected answer) puts the reporter on hold: ticks that fall inside the hold are skipped. - Never blocking. Reporting runs in the background and never affects message traffic. There is no retry backoff: a failed report is simply attempted again at the next 15-minute tick, with the full cumulative counters.
What is never sent
The report contains no payload data and no identifying traffic data:
- No message content, headers, metadata or tags.
- No channel names, queue names, client ids or subscription details.
- No client IP addresses, and no server IP addresses — the service sees only the connection it receives.
- No configuration values, credentials, or the license key.
- No per-channel or per-client breakdown — only two totals.
Everything it does contain is listed in the table above. The whole body is limited to
16 KiB and source to 2 KiB.
What if reports cannot get through?
Nothing stops. An online license whose refreshes succeed but whose usage reports do not arrive for 72 hours is marked silent: the server logs a warning every minute (#silent-usage), leases are shortened to 24 hours, and KubeMQ sends you an email. After 30 days of continuous silence the service stops issuing new leases, and the server runs out its lease and grace. That is the only automatic consequence, and it is suspended fleet-wide whenever the silence looks like an outage on KubeMQ's side rather than yours.
Retention
| Data | Kept for |
|---|---|
| Raw 15-minute reports | 90 days, then pruned |
| Daily totals per license and installation | Indefinitely — this is the billing record |
Raw rows are pruned only after every day they cover has been rolled up into the daily totals.
Verify what your server is sending
GET /api/v1/license reports the state of the reporter:
curl -s http://localhost:8080/api/v1/license | jq .usage{
"enabled": true,
"last_report_at": "2026-09-19T12:10:00Z",
"last_outcome": "accepted",
"last_status": 200,
"cursor_to": "2026-09-19T12:10:00Z",
"boot_id": "0b3d5f7a-9c1e-4a2b-8d6f-1e3c5a7b9d0f"
}| Field | Healthy value |
|---|---|
enabled | true for an online license; false for a file |
last_report_at | Within the last 15–17 minutes |
last_outcome | accepted (or duplicate right after a restart). rejected means the service acknowledged but did not count the row; error means it never got through; never means no report has been sent since boot |
last_status | 200 |
cursor_to | Equal to the end of the last accepted window |
The silent flag at the top level of the same response tells you whether the service
agrees that it is receiving your reports. To see the traffic itself, watch outbound
HTTPS to license.kubemq.io from the container; the server sends one request per
report and nothing else on that connection.
Was this page helpful?