Skip to main content
When the server finishes scoring an identification, it pushes the result to each configured endpoint as a POST. This is the canonical, low-latency way to receive the Risk Score and the signals behind it. This page is the reference: the envelope schema, delivery timing, and how to verify the signature. The webhook setup guide gives a step-by-step walkthrough of configuring and testing an endpoint.
You do not poll for webhooks. The server sends them to the endpoints you configure per domain (up to 10) in the dashboard. Delivery is at-most-once with no retries, so pair it with a History API read when you cannot afford to miss a result.

Envelope

Each POST body is a JSON envelope in snake_case. Event metadata lives at the top level; the scored visit lives under data.
string
Discriminator for the delivery. Real scored visits always use identification.scored. Service deliveries use webhook.ping (endpoint Verify). Ignore unknown types until you add support.
string
Contract version, for example 2026-06-01. Check this before parsing data so you can branch when ShieldLabs ships a new schema.
string (ISO 8601)
When this webhook event was created and signed, in RFC 3339 form.
object
Present on identification.scored events. Absent on webhook.ping. See Scored data below.

Scored delivery (identification.scored)

Each scored identification produces one webhook per enabled endpoint.
The signature is not in the body. It travels in the X-Shield-Signature request header:

Scored data

Fields inside data on identification.scored events.
string (UUID)
The client-generated UUID for this identify call. Join key across the snapshot, the webhook, and the History API. Make your handler idempotent on this value.
string (UUID)
Per-visit identifier from the browser’s sessionStorage (short-lived). Resets each browser session or tab.
First-party cookie / localStorage identifier minted client-side. Lost when the user clears cookies or storage.
string (UUID)
Server-derived identity, computed from stable Device Intelligence. Durable: survives cleared cookies, incognito, and IP rotation, because it is derived from the browser environment rather than stored, as the Identifiers reference explains.
string (UUID)
Server-derived from the device_id and the cookie_id. Changes when the cookie is cleared. Multiple visitor_ids can map to one device_id. The durability claim belongs to device_id, not visitor_id.
string | null
The customer’s own account id, passed in through the snippet via checkAuthenticatedUser. This must be a hashed or pseudonymous value, never a raw email or user id. On an anonymous call (checkAnonymous), this carries the literal placeholder value anonymous. null when not provided.
string
The site domain this identification belongs to (your registered domain key).
object
The public client IP resolved for this HTTP request.
object
The real network IP observed by an optional follow-up network check (when available). May differ from public_ip when the visitor uses a VPN, proxy, or split tunnel. In the example above, public_ip is a US proxy exit (203.0.113.42, US) while local_ip resolves to the visitor’s real network in Germany (198.51.100.23, DE); because the two disagree, detection_flags.ip_mismatch is true. ShieldLabs surfaces both IPs so your code can compare them; the difference is informational and does not add to the Risk Score.
string
The operating system derived for the visitor (for example Windows, Mac OS X, or IOS (iPhone)). May be empty when it cannot be determined.
string
The browser family detected for this visit, for example Chrome or Firefox.
string
Device class: desktop, mobile, or tablet.
string
The detected network connection type, one of direct, mobile, vpn, proxy, tor, privacy_relay, browser_vpn_proxy, or unknown (when the type could not be resolved).
integer
The Risk Score, an integer from 0 to 100 (hard-capped at 100). Higher means more anonymous or more likely masked, spoofed, or abusive. ShieldLabs scores; your application decides allow, challenge, review, or block. Map bands in your own code: Clean 0–9, Low 10–29, Medium 30–59, High 60–100. A rate-limit ban can surface the marker value 999 here, so guard risk_score > 100 before reading the band.
array of objects
The full explainable breakdown: every scored signal that contributed to risk_score, as { "name": "<slug>", "weight": <int> }.
object
Resolved traffic attribution for the visit.
object
Boolean flags for each detection dimension. Use these for quick branching; use signals for the explainable score breakdown. Not every flag contributes to the Risk Score — some are informational (for example ip_mismatch and incognito). The scored subset and their weights are in the Risk Scoring table.
string (ISO 8601)
When the visit was scored and this payload was finalized, in RFC 3339 / ISO 8601 form.
Branch on risk_score, stable signal name slugs, and detection_flags — not on free-form labels from the History API Description field.

Delivery timing

The server waits up to 60 seconds after the check started for optional follow-up network checks. If a follow-up never arrives, the webhook is still sent with the best score available at the deadline. Full background is in the Identification Flow.

Service events

Verify ping (webhook.ping)

Endpoint Verify in the dashboard sends a minimal envelope — no data:
Use it only to confirm URL reachability and signature verification. Do not treat it as a scored visit.

Send test event

Send test event delivers a full identification.scored envelope with sample data (fixed UUIDs). Parse it like production traffic; deduplicate on data.request_id if you replay tests.

Verification

Verify the signature on every webhook before acting on it. The recipe:
The HMAC is computed over the raw request body bytes exactly as received (capture them before any re-encoding), keyed with that endpoint’s signing secret (whsec_…). Hex-encode it, prefix with sha256=, and constant-time compare against the X-Shield-Signature header. Re-serializing the parsed JSON changes the bytes and the signature will not match.
The signing secret is backend-only. Never put it in the browser, in client-side code, or in the snippet. If a request to your endpoint has a missing or mismatched X-Shield-Signature, reject it. Each endpoint has its own secret — verify with the secret that belongs to the endpoint that received the call.
Copy-paste verification handlers for Node, Go, and Python live in the webhook setup guide.

Delivery guarantees

Webhook delivery is intentionally lightweight. Design your handler around these properties.

At-most-once

Each check produces one send attempt per endpoint. There is no retry, no backoff, and no dead-letter queue. A dropped network connection means that webhook is gone.

~1s timeout

The sender waits about one second for your endpoint, then moves on. Acknowledge with a fast 2xx and do heavy work asynchronously, off the request path.

Idempotent on request_id

Key your writes on data.request_id so a redelivery is a no-op.

Read fallback

For anything you cannot afford to miss, read the result from the History API by request_id. That is the guaranteed, pull-based path.
A reliable pattern:
1

Persist the request_id early

Capture requestID from the snippet callback and store it with the user action you are protecting.
2

Apply the webhook

On delivery, verify the signature, check event_type === "identification.scored", then record data.risk_score and data.signals against data.request_id. Treat the write as idempotent.
3

Fall back to a read

If no webhook arrives within your expected window (up to ~60s), call the History API by request_id to pull the stored snapshot.

Acting on the payload

The webhook gives you the risk_score and the signals behind it; your application owns the decision. The per-band playbook covers how to turn a payload into an allow, challenge, review, or block.

Next steps

Set up a webhook

The tutorial: configure your endpoints, test them, and go live.

Data Models

The full envelope and Snapshot schemas in one place.

Server API

History search by request_id, the snapshot superset, and profile.

Risk Score

How the 0-100 explainable score and the Clean / Low / Medium / High bands work.