How It Works
One snippet on your page collects browser, device, and network signals. ShieldLabs scores them server-side and returns a persistent identity (VisitorID and DeviceID) plus an explainable Risk Score (0 to 100) and the signals behind it. The key idea to hold onto: identification and scoring happen on the server, not in the browser. The snippet only gathers raw signals and posts them. It does not compute a VisitorID, a DeviceID, or a Risk Score. And ShieldLabs only scores. Your own code reads the result and decides allow, challenge, review, or block.ShieldLabs surfaces signals. Your application owns the decision. There is no in-product rules engine and nothing is ever blocked or challenged on our side. You act on the Risk Score and Details in your own backend.
The flow at a glance
The snippet collects signals in the browser
The ES module snippet loaded from
cdn.shieldlabs.ai gathers 100+ browser, device, and network signals (canvas, WebGL, audio, fonts, screen, navigator, timezone, and more) plus a local anonymity probe. Nothing is scored here.It POSTs the signals to rest.shieldlabs.ai automatically
When you call
checkAnonymous() or checkAuthenticatedUser(), the snippet POSTs the collected payload to rest.shieldlabs.ai on its own. You never call this host directly. The POST is fire-and-forget: it returns quickly and does not carry the score.The server scores asynchronously (about 1 second)
A background pipeline enriches and scores the request: IP reputation, the TCP and TLS network fingerprint, a real-IP (STUN) check, and server-side device derivation. It writes a snapshot, then computes the Risk Score with an explainable
Details array.You receive the initial webhook
ShieldLabs POSTs the initial webhook to your configured callback URL, with
Phase: "initial", about a second after ingest. This carries the identifiers, score, and details. See Webhooks.A second update webhook may follow after the WebRTC real-IP check
Once the WebRTC real-IP (STUN) discovery completes, the server may recompute the score and POST an update webhook (
Phase: "update") carrying only the delta. The update is skipped if more than 10 seconds have passed.You can also read results from the History API
Webhooks are at-most-once with no retries. For guaranteed reads, poll the History API by
request_id, device_id, visitor_id, user_hid, or ip. Results also appear in the dashboard.The diagram
Solid arrows are the always-on path. Dotted arrows are the WebRTC second phase, which may or may not fire depending on the network and timing.Step by step
1. The snippet collects signals (in the browser, no scoring)
The snippet is an ES module you load fromcdn.shieldlabs.ai with your domain’s public key. When you call one of its functions, it collects:
- Browser and device signals via the fingerprint library: navigator, screen, WebGL, canvas, audio, fonts, timezone, touch support, and related attributes. The server later derives a stable DeviceID from these.
- Browser and OS classification: a friendly browser name and OS, plus JavaScript-feature flags (WebRTC, WebGL, audio availability, automation flags).
- A local anonymity probe for anti-detect browsers, automation tooling, and remote-access tooling, run via local network signals.
- Private-browsing (incognito) detection.
- Traffic-attribution inputs: the entry URL and referrer, used server-side for Traffic Sources.
requestID, a 10-minute sessionID, and a long-lived cookieID. The DeviceID and VisitorID are derived on the server, not in the browser.
2. The snippet POSTs to rest.shieldlabs.ai automatically
You do not POST anything yourself. After collecting signals, the snippet sends them torest.shieldlabs.ai for you. The payload may be plain JSON or obfuscated in transit on top of TLS. Each call carries a client-generated requestID (a UUID) that becomes the join key across the snapshot, the webhook, and the History API.
The POST is fire-and-forget: it returns a quick acknowledgment, not the score. The snippet’s optional callback fires with (serverResponse, requestID) so you can capture the requestID and correlate later. The authoritative result (VisitorID, DeviceID, Risk Score) arrives server-side.
3. The server scores asynchronously (about 1 second)
The request enters a background pipeline. In roughly a second it:- Fetches IP reputation (VPN, proxy, datacenter, abuser, privacy relay, timezone).
- Pulls the TCP and TLS network fingerprint to corroborate the connection type.
- Waits briefly for real-IP (STUN) evidence from the WebRTC path.
- Derives the DeviceID server-side from the stable fingerprint components, then the VisitorID from
DeviceID + CookieID. - Computes the Risk Score by summing the signal weights, capping at 100, and recording each contributing signal as a
Detailsentry. - Stores a snapshot and pushes the initial webhook.
4. The initial webhook is delivered
About a second after ingest, ShieldLabs POSTs the initial webhook to your callback URL. The envelope wraps the body plus an HMAC signature:Assing (it is the HMAC-SHA256 signature). Verify every webhook before trusting it:
Assing is computed over the marshaled Data object only, not the full envelope. Recompute the HMAC over the Data you received and compare in constant time. See Webhooks for the full field reference and signature serialization notes.
5. The update webhook (second phase, delta only)
ShieldLabs uses a two-phase webhook:| Phase | When | What Details carries |
|---|---|---|
"initial" | About 1 second after ingest, before WebRTC completes | The full list of signals that fired |
"update" | After the WebRTC real-IP (STUN) check resolves | Only the delta: the signals added or removed by the real-IP result |
update webhook whose Details lists only what changed since the initial score.
The
update webhook is suppressed if more than 10 seconds have elapsed since the snapshot was created. Treat the initial score as the value you can always rely on, and the update as a same-request refinement when the network allows it. Apply the delta on top of the initial Details keyed by the shared RequestID.6. Read results from the History API or the dashboard
Webhook delivery is at-most-once with no retries and a roughly 1-second timeout. Do not assume at-least-once delivery. For anything that must not be missed, read from the History API:request_id, device_id, visitor_id, user_hid, or ip, newest first. History rows are a superset of the webhook body. The same snapshots also power the dashboard: Visitors, Traffic Sources, Patterns, and the raw Data view.
Idempotency on RequestID
Because the same identify call can reach you up to twice (aninitial webhook and possibly an update webhook), and because you may also re-read the same row from the History API, your handler must be idempotent on RequestID.
A safe pattern:
- Use
RequestIDas the unique key for the identification event in your store. - On the initial webhook, write or upsert the full record under that
RequestID. - On the update webhook, find the same
RequestIDand apply theDetailsdelta to the existing record, then recompute your decision. - If a webhook is missing, fall back to a History API read by
request_id. Re-processing the sameRequestIDmust not double-count or double-act.
What runs where
| Stage | Where it runs | What it produces |
|---|---|---|
| Signal collection | The visitor’s browser (the snippet) | A raw signal payload plus client identifiers |
| Transport | rest.shieldlabs.ai | An acknowledged POST with the requestID |
| Identity derivation | ShieldLabs server | DeviceID and VisitorID |
| Real-IP discovery | webrtc.shieldlabs.ai and STUN | A real-IP signal that may trigger the update |
| Scoring | ShieldLabs server | The Risk Score and Details |
| Delivery | Initial and update webhooks plus History API | The result your code reads |
| Decision | Your application | allow / challenge / review / block |
Next steps
Install the snippet
Load the ES module from the CDN and run your first check.
Configure webhooks
Set your callback URL and verify the
Assing signature.Understand the Risk Score
The 0 to 100 score, the Clean / Low / Medium / High bands, and the Details array.
Act on the score
Turn Score and Details into allow, challenge, review, or block in your code.