Skip to main content
A webhook pushes the Risk Score and the signals behind it to your backend the moment ShieldLabs finishes scoring a visit. Register one or more endpoints per domain, verify the signature on the X-Shield-Signature header, and act on the score in your own code. This page is the how-to. The Webhooks API reference has the exact field-by-field payload schema.
Webhooks are optional. If you do not register an endpoint, checks still run and still bill, but results are only available in the dashboard or through the History API. An endpoint is what makes scores real-time.

Register an endpoint

You can configure up to 10 endpoints per domain. Each endpoint has its own name, HTTPS URL, and a unique signing secret (whsec_…). Manage them from the dashboard.
1

Open the domain

Go to app.shieldlabs.ai, open your domain, and switch to the Webhooks tab.
2

Add an endpoint

Click Add endpoint, give it a name and a public HTTPS URL on a route your backend controls, and save. ShieldLabs generates a dedicated signing secret (whsec_…) for that endpoint.
3

Verify it

Use Verify to send a signed ping. When your endpoint answers with a 2xx, its status flips to Active. You can also use Send test event to deliver a sample risk event at any time.
Each endpoint signs with its own whsec_… secret. Copy the secret from the endpoint row and store it as a backend-only environment variable — never in the browser or in a URL. Rotating a secret invalidates the old one immediately.
Endpoints can be individually enabled or disabled. A disabled endpoint stops receiving events without losing its configuration. The Requests counter on each row reflects live delivery volume.

What a delivery looks like

ShieldLabs sends a POST with Content-Type: application/json to each enabled endpoint. The body is a signed envelope (snake_case), and the signature travels in the X-Shield-Signature header.
Correlate the delivery to the original identify call by data.request_id. The full field list is in the Webhooks API reference. This delivery is a masked session: data.public_ip is a US proxy exit (203.0.113.42) while data.local_ip is the visitor’s real network in Germany (198.51.100.23). The two countries disagree, so data.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 score, which here comes from the proxy, datacenter, and abuser signals.
Branch on data.risk_score, signal name slugs, and detection_flags.

One webhook per check

Each identification produces exactly one scored webhook per enabled endpoint, joined by request_id. ShieldLabs waits for follow-up network checks when they apply to the visit. The webhook is sent as soon as those checks finish, or no later than 60 seconds after the check started — whichever comes first. If a follow-up never arrives, you still get one webhook with the best score available at that point. Typical timing:
  • Simple visits (no follow-up network check): about 1 second after ingest.
  • Chrome with a follow-up network check: usually within a few seconds, up to 60 seconds in slow cases.
Do not block your UX waiting for the webhook. Use the snippet callback for immediate UI; treat the webhook as the authoritative scored result for server-side decisions.

Verify the signature

Every delivery is signed. The X-Shield-Signature header is the hex-encoded HMAC-SHA256 of the raw request body, keyed with that endpoint’s signing secret, prefixed with sha256=:
To verify: read the raw request body bytes exactly as received, compute HMAC-SHA256 over those bytes with the endpoint secret, hex-encode the result, prefix it with sha256=, and compare it to the X-Shield-Signature header using a constant-time comparison. Reject the request (respond 401) on a mismatch, and do not process the body.
HMAC the raw request body bytes as received. Re-serializing the parsed JSON can reorder keys or change spacing, which changes the bytes you hash, so the signature will not match.

Delivery guarantees

Webhook delivery is at-most-once.
There are no retries and the send has a ~1 second timeout. If your endpoint is slow, down, or returns a non-2xx response, that delivery is dropped and not resent. Do not rely on webhooks for guaranteed delivery: for guaranteed reads, poll the History API, which returns stored snapshots by request_id (and by IP, VisitorID, DeviceID, UserHID, SessionID, or CookieID).
A practical handler pattern:
1

Verify

Constant-time compare the X-Shield-Signature header. Reject with 401 on a mismatch.
2

Acknowledge

Return 200 immediately once the signature is valid and the body is parsed.
3

Deduplicate

Upsert on request_id. A redelivery for the same id should be a no-op.
4

Decide

Map data.risk_score plus data.signals to allow, challenge, review, or block in your application logic.

Next steps

Webhooks API reference

Field-by-field payload schema, timing, and signature details.

Acting on the Risk Score

Map scores and signals to allow, challenge, review, or block.

History API

Poll stored snapshots for guaranteed reads.

API keys

Where your public and secret keys come from.