Fingerprint Profiles & Canaries

Snapshot versioned fingerprint profiles and alert on rejection-class drift.

When the target app ships an update, its client identity changes — a new User-Agent, a bumped SDK version header, a recompiled TLS ClientHello. A collector built against the old shape keeps sending the old identity and starts tripping the anti-fraud gate. Fingerprint profiles and canary checks turn that silent breakage into a loud alert: profiles snapshot the identity a capture exhibits (with versioning), and canaries periodically replay a pinned endpoint and flag any move away from the baseline rejection class.

Both live in the API lab → Collector tab, under the Profiles view. Everything is stored in the workspace SQLite database (the same file as captures — .traffic-jam/traffic-jam-v2.sqlite3 by default, overridable with TRAFFIC_JAM_DB_PATH), so profiles and canaries survive API restarts.

[!NOTE] Canaries replay real requests against a live endpoint. Only pin endpoints on systems you are authorized to test, and keep the 10-minute cadence in mind when the target is rate-limit-sensitive.

Snapshot a fingerprint profile

A profile is a versioned snapshot of one captured request’s client identity. The backend extracts these markers from the source request:

FieldSource
headersAll request headers except HTTP/2 pseudo-headers (:method, :path, …), in captured order
userAgentThe User-Agent header
appVersionThe plaid-version header, if present
eventUrlParamsThe as and p query parameters, if present
ja3, ja4, alpn, tlsVersionThe request’s TLS ClientHello fingerprint fields
capturedAtSnapshot timestamp (UTC, RFC 3339)

To create one:

  1. Open API lab → Collector → Profiles.
  2. Under Profiles, pick the defining request in the Profile source picker (session, then request).
  3. Enter a Profile name — leave it blank to default to the request’s host (authority).
  4. Click Snapshot profile version.

Versioning is by name: snapshotting again under an existing name stores version = max(existing) + 1 rather than overwriting, so the profile list keeps a history of how the identity evolved. Each row shows the name, a vN badge, the captured app version (if any), and the snapshot date; the trash icon deletes a version.

Pin a canary

A canary pins one endpoint — a captured session/request pair — plus the replay options that reproduce the baseline, and records what “normal” looks like as a status code and a rejection class.

  1. In the same Profiles view, under Canaries, pick the endpoint in the Canary endpoint picker.
  2. Enter a Canary name (e.g. product card).
  3. Optionally attach a Profile from the dropdown (name vN) to tie the canary to a specific identity version.
  4. Click Pin canary.

The baseline is taken from the captured response: its status code, with the baseline rejection class set to accepted. The confirmation toast suggests running the canary once immediately to verify the fingerprint still passes. The stored replay options mirror the interactive replay transport knobs:

{
  "transport": "utls",
  "fingerprintProfile": "<profile id>",
  "forceHttp1": false,
  "clientCertId": "<client cert id>",
  "headerOverrides": { "X-Example": "value" },
  "body": "optional replacement body"
}

A canary run replays with captured sensitive values included and mutation/destination confirmations pre-accepted, so it reproduces the request exactly as baselined — no interactive prompts.

Run a canary and read the verdict

Each canary row shows its baseline (baseline 200 accepted) and the last run as a badge: last run 200 · stable, last run 451 · drift, or never run. Click Run now to execute one check on demand.

A run replays the pinned request, classifies the response into a rejection class, and compares it with the baseline. Drift is any change in rejection class — in either direction:

  • acceptedantifraud-reject: the fingerprint went stale; the gate now rejects what it used to accept. This is the alert case.
  • antifraud-rejectaccepted: a previously rejected request now passes — the gate changed; re-baseline.
  • No change: verdict stable.

A drifted run returns a verdict like drift: accepted → anti-fraud rejection (451) and the UI raises a warning toast naming the canary. Every run is recorded (timestamp, status, rejection class, drifted flag, transport error if any); the runs list keeps the most recent first, capped at 50 per fetch (the storage layer accepts a limit up to 200). Deleting a canary cascades to its run history.

The rejection-class vocabulary is the same one replay, mutation probes, and the conformance diff use:

ClassMeaning
(empty)accepted2xx, not a rejection
transport-errorConnection/TLS failure before a response
client-certificate-required400 whose body names the missing mTLS client certificate
unauthorized / forbidden / not-found401 / 403 / 404
rate-limited429
antifraud-reject451 — the anti-fraud gate
redirect3xx (replay never follows redirects)
client-error / server-errorother 4xx / 5xx

Scheduled drift alerts

While the app is open, the Profiles view re-runs every pinned canary on a fixed 10-minute interval and refreshes the run history. A drift detected on a scheduled run surfaces the same warning toast as a manual run, so a collector that breaks between pipeline runs is caught here — at the fingerprint level — instead of later, as unexplained data-pipeline failures. The timer runs only while the workspace is open; there is no background daemon.

[!TIP] App-version drift is the usual collector killer. When a canary drifts, snapshot a fresh profile version from a new capture of the updated app, diff the two profile versions’ headers and JA3/JA4, and port the delta into your collector. The /docs/en/collector/conformance/ diff can then confirm your updated parser matches the new reference capture.

Where this fits

Profiles feed the replay transport — a canary’s fingerprintProfile option selects which snapshotted identity a replay presents (see /docs/en/replay/fingerprints/ for TLS fingerprint control). When a canary alerts, the loop is: re-capture the updated app (/docs/en/capture/importing/), snapshot a new profile version, re-pin or re-baseline the canary, and verify the exported collector still conforms (/docs/en/collector/conformance/).

Traffic Jam documentation. Built with Hugo.