Skip to main content
The ShieldLabs snippet identifies your signed-in users and anonymous visitors in the browser. It is a single ES module you load by dynamic import() from cdn.shieldlabs.ai, on any site or web app that runs in a browser. The module collects 300+ device and network signals and posts them to ShieldLabs automatically. ShieldLabs derives the Visitor ID, Device ID and Risk Score on the server and delivers them by webhook and the Server API. Call checkAuthenticatedUser with a hashed User HID for signed-in users and checkAnonymous for everyone else. You correlate the browser call with the server result by the requestID from the optional onInitialized handler.
The snippet stores a long-lived first-party id under the key cookieID in both localStorage and a first-party cookie. That value is sent to ShieldLabs as cookieID and stored server-side as the Cookie ID. The Visitor ID (one device plus one cookie) is computed on the server from the Device ID and the Cookie ID and reaches your backend with each result.
No built-in consent gate. Calling checkAnonymous(), checkAuthenticatedUser(), or either forceCheck* starts identification immediately. The module does not read your cookie banner or CMP. You control when the call runs. For SDK processing you initiate on your site, you generally act as the controller and ShieldLabs as your processor, as set out in your agreement with ShieldLabs. Confirm what applicable law requires with your own counsel. See Cookie & Tracking Policy §2. This is not legal advice.
If applicable law or your policy requires prior consent, call the snippet only after consent is granted (for example from your CMP accept callback). In your own privacy and cookie notices, include the first-party cookieID, the device and network signals the SDK collects, your anti-fraud purpose, and, where applicable, the legal basis you rely on. Never pass directly identifying information (such as names, email addresses, or phone numbers) as the User HID.

Install (HTML)

Add this near the top of <body>. Use type="module" (the snippet relies on import.meta.url and top-level dynamic import, so it cannot run as a classic script).
publicKey is your site’s Public Key, one per registered domain. It is safe to expose in page source. In the analytics dashboard, open Integration > Install and select the domain: its snippets already carry the Public Key (see API keys and the Integration screen). The <noscript><img> beacon is a crawlable GET /noscript so visits still appear when JavaScript does not run (search crawlers, JS-off browsers). Allow https://rest.shieldlabs.ai in img-src if you set a CSP.
Load the snippet directly from https://cdn.shieldlabs.ai. Do not self-host, mirror, bundle, or pin copies of snippet.js or its runtime imports: the snippet and its modules receive compatibility and security updates together. The official CDN keeps these unversioned runtime files on a short revalidation policy.
The call is fully async and non-blocking. Several checks run in parallel while the page renders. Each export returns nothing (void) and never throws. Pass { onInitialized } if you need the requestID as soon as the check starts; omit it to fire-and-forget.

Identify signed-in users

On every page a signed-in user loads, call checkAuthenticatedUser with a hashed User HID instead of checkAnonymous. Users, account-level risk and all four High-Risk Events are built on it. Keep checkAnonymous for visitors who are not signed in.
Always pass a hashed or pseudonymous id to checkAuthenticatedUser and forceCheckAuthenticatedUser. Never pass a raw email or a real account id. ShieldLabs stores this value as the User HID: the key that ties identifications to the account and on which High-Risk Events are detected. Keep it irreversible to a real identity.
High-Risk Events (Multi-accounting, Account sharing, Impossible travel and Account takeover) are detected on users and are available in the analytics dashboard, the API and webhooks. Integration > Install in the analytics dashboard shows both snippets for the domain you select, one for anonymous visitors and one for authenticated users, and whether the domain is reporting.
Integration > Install for example.com in the analytics dashboard: the stack picker with JavaScript selected, the Anonymous visitors and Authenticated users snippet cards (each expands to its snippet), the Snippet methods table with checkAnonymous, checkAuthenticatedUser, forceCheckAnonymous and forceCheckAuthenticatedUser, and the line Identifications are arriving from example.com. Last identification 2 minutes ago.Integration > Install for example.com in the analytics dashboard in the dark theme: the stack picker with JavaScript selected, the Anonymous visitors and Authenticated users snippet cards (each expands to its snippet), the Snippet methods table with checkAnonymous, checkAuthenticatedUser, forceCheckAnonymous and forceCheckAuthenticatedUser, and the line Identifications are arriving from example.com. Last identification 2 minutes ago.

Integration > Install in the analytics dashboard: pick your stack, open the snippet for anonymous visitors or authenticated users, and check that identifications are arriving.

The four exports

The module exports four functions. Each call that runs an identification counts as one identification against your plan. They differ in two ways: whether they tie the identification to your user (User HID), and whether they wait for the five-minute window. Within one visit (while a page of your site stays open in the browser, across route changes in a single-page app and across open tabs), checkAnonymous and checkAuthenticatedUser run at most one identification every five minutes for the same user. A call inside that window posts nothing, counts nothing, and its onInitialized handler receives { status: "not_initialized" }. On a multi-page site, a full page load in the only open tab can start a new visit with its own identification. The forceCheck* exports run an identification every time.

forceCheck*: run now

checkAnonymous and checkAuthenticatedUser share one session per user in the browser, across the open tabs of your site. Calls in the same session share one Session ID. A new session, with a new Session ID, starts on the next page load after the last open page of your site is closed or navigated away from; in a single tab on a multi-page site, that can be every full page load. forceCheckAnonymous and forceCheckAuthenticatedUser run an identification every time, even inside the five-minute window. They keep the current Session ID, issue a new requestID and restart the five-minute window. Reach for them when the moment matters:
  • Right after login. Run the check now that you know who the user is, so ShieldLabs ties this identification to the account.
  • Before a sensitive action (checkout, withdrawal, password change, a new device approval). Get a fresh Risk Score keyed to a requestID you can act on.

The optional onInitialized handler

Each export accepts an optional options object. onInitialized is called once, asynchronously, when the check starts (or when no check runs). The methods return void and never throw:
  • { status: "initialized", requestID }: the join key to the webhook and the History API
  • { status: "not_initialized" }: no identification ran and nothing is counted. The five-minute window for this user is still open, another tab is already running the check, the User HID or Public Key is invalid, or the check could not start.
Anonymous calls take options as the only argument. Do not pass undefined first.
The server computes the Visitor ID, Device ID and Risk Score. Send the requestID to your backend, then read the result from the webhook payload or the History API (query by request_id).
The flow:
  1. onInitialized receives { status: "initialized", requestID } as soon as the check starts, or { status: "not_initialized" } when no check runs.
  2. The snippet collects signals and posts them to rest.shieldlabs.ai.
  3. ShieldLabs scores the identification in about 300 ms and delivers one webhook with the final Risk Score. When follow-up network checks run, it waits for them, at most about 10 seconds.
  4. Your backend matches the webhook (or History API row) to the browser call by requestID, and to the account by user_hid.

Framework integrations

The HTML method above works anywhere. In a framework, put the same dynamic import() inside a lifecycle hook so it runs once on mount. Pass your Public Key in from config or props, and the hashed User HID once the user is signed in.
Memoize the import (the Angular example caches modulePromise) so the module loads once even if you mount the wrapper in several places. The framework wrappers are thin: they call the same CDN module as the HTML method, just from your app code instead of an inline script.

Capturing the requestID with onInitialized

Pass { onInitialized } in any framework to grab the requestID and hand it to your backend:
Inside the five-minute window this call runs no identification, so onInitialized receives { status: "not_initialized" } and there is no requestID to forward. Where a server-side decision needs a fresh result (login, checkout, a password change), call forceCheckAuthenticatedUser or forceCheckAnonymous at that moment instead. ShieldLabs returns a Risk Score and every named risk signal on each identification, and detects High-Risk Events on your users. You choose the action for each case (allow, step up, review or block) and act on the result in your backend; acting on results walks each path.

Next steps

Content Security Policy

Allowlist the ShieldLabs snippet hosts if your site sends a strict CSP header.

API keys

Find your domain’s Public Key for the snippet, and the Private API Key and Secret Key for the server.

Webhooks

Receive the scored result keyed by requestID, one webhook per identification.

How ShieldLabs works

Users, devices, visitors and IPs, and the identification behind each Risk Score.