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 underdata.
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.
X-Shield-Signature request header:
Scored data
Fields insidedata 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.string (UUID)
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:
Send test event
Send test event delivers a fullidentification.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: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.
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.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 therisk_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.