Telemetry: counts, never content

CKey SDKs can send anonymous, aggregate scan counts to openckey.org, so the project can see whether scanning works in the field. It never includes what was scanned, and turning it off takes one line.

The three modes

ModeCounts on the deviceSends to openckey.org
0 offNo, and existing counters are purgedNo
1 on, the defaultYesAggregate batches, at most one an hour
2 local onlyYesNever; the app can read the counts itself

Any value other than 0, 1 or 2 is an error, and the mode is never silently reset to 1. Only the scanner records anything. Encoding, rendering and one-off image decodes never do. The renderer and scanner on this site load the SDK with telemetry off and no way to send, so they record and send nothing.

What is counted

The unit is a scan session: it starts when the camera view opens, succeeds at the first valid decode, and fails when scanning stops, or 30 seconds pass, without one. A batch holds only:

  • scan session counts: started, succeeded and failed;
  • the number of camera frames processed;
  • the number of decodes for each profile;
  • two coarse histograms: time to first decode, and time per frame;
  • the SDK name and version, a coarse platform name such as ios or web, and the UTC day.

What is never collected

  • payloads, URLs or anything that was decoded;
  • user, device, installation or advertising identifiers, or fingerprints;
  • location;
  • per-scan event histories;
  • IP addresses, beyond what the network connection itself exposes.

Telemetry never runs on the decode path. Recording is a few counter updates, and sending is asynchronous and batched, so it never delays a scan. An offline device simply keeps counting until it can send.

Turn it off in one line

SDKLine
SwiftCKey.telemetry = .off
Webckey.telemetry = 0, or loadCKey({ telemetry: 0 }) before anything is counted
AndroidCKey.telemetry = 0
.NETCKey.Telemetry = 0;
C and C++ckey_telemetry_set_mode(0);, or build with -DCKEY_BUILD_TELEMETRY=OFF

If you leave telemetry on, your app’s own privacy disclosures may need to mention it. On Apple platforms, for example, it is “Product Interaction: Analytics, not linked to the user, not used for tracking”.

What a batch looks like

One request carries one batch, sent to POST /api/telemetry/v1/batch as JSON of at most 4,096 bytes. Anything that doesn’t match schema 1 exactly is refused. The API reference lists every rule.

{
  "schema": 1,
  "sdk": "swift",
  "sdk_version": "0.1.0-build.61",
  "platform": "ios",
  "day": "2026-11-05",
  "sessions": { "started": 12, "succeeded": 10, "failed": 2 },
  "frames": 3120,
  "decoded": { "p0": 7, "p0_1": 1, "p1": 1, "p2": 1, "p3": 0 },
  "time_to_decode_ms": { "count": 10, "sum": 4210, "buckets": [0, 1, 3, 4, 1, 1, 0, 0, 0, 0] },
  "frame_ms": { "count": 3120, "sum": 40110, "buckets": [10, 400, 2000, 600, 100, 10, 0, 0, 0, 0] }
}

How openckey.org handles it

  • Aggregates only. Each accepted batch is added to daily totals per day, SDK, SDK version and platform. Request bodies are not stored.
  • No IP addresses stored. They are not written to logs or the database. Rate limiting uses a key derived from the address with a random salt held in memory and replaced every day, and nothing derived from it is kept.
  • 400-day retention. Daily totals older than 400 days are deleted.
  • Strict input. Batches must match the schema exactly. Requests are size-limited, rate-limited per sender, and capped per day.

The counts are unauthenticated. They can be spoofed, replayed or inflated, and without identifiers they cannot be deduplicated. Treat them as a coarse signal, never as installs, users or total scans.

Live summary

The same figures anyone can fetch from GET /api/telemetry/v1/summary.

Reported scans

Loading the reported counts for the last 30 days…

Reported by SDKs with telemetry on; not total usage, unique users, or worldwide scans.

The normative rules are in adoption and telemetry in the CKey repository.