signals array next to the number names every risk signal that produced it. Higher means riskier: more likely masked, spoofed, automated or abusive. Every user, device, visitor and IP address then carries the worst band of its identifications, so one Dangerous signup marks the account. You act on the result in your backend and choose the action for each case: allow, challenge, review or block.
What the Risk Score is
The Risk Score is one value per identification, with the evidence attached.- Range 0 to 100. The only value above 100 is
999, the rate-limit marker described under Caveats. - Explainable. Every Risk Score ships with a
signalsarray, so you never act on a black box. - Delivered, not computed in the browser. It arrives by webhook and is readable from the History API.
How the Risk Score is built
Each risk signal that fires adds a fixed weight, and the weights are summed under the combination rules below, then capped at 100. The webhooksignals array lists every risk signal that changed the Risk Score as { "name": "<slug>", "weight": <int> }: name is a stable slug (proxy, antidetect_browser) and weight is what it added to the Risk Score. Two details matter when you parse it: a slug can repeat with a partial weight when an earlier result for the same device and IP address is carried forward, and stun_late_correction carries a negative weight when a late network check cancels stun_not_checked. Branch on slugs and detection_flags, and read risk_score for the total.
The data object of a webhook for an identification from an anti-detect browser behind a proxy, shortened:
70: Anti-detect Browser adds 60 and Proxy adds 10, which places the identification in the Dangerous band. Because both reasons are named, you can see that the anti-detect browser drove this result and choose a different action than you would for a proxy alone.
The identification card in the analytics dashboard shows the same Risk Score and weights:


The Risk Score of one identification and each risk signal with its weight, in the analytics dashboard.
Webhook
signals[].name is a stable slug (proxy, antidetect_browser). Branch on name or detection_flags. Display labels can change in the analytics dashboard, and score_details on the History API holds internal descriptions rather than slugs. A numeric weight alone does not tell you which risk signal fired: 30 from os_not_detected is not the same case as 30 from browser_vpn_proxy. Read the band, the flags, the user’s history and the sensitivity of the action together. See Acting on results.The weights
Every risk signal that adds to the Risk Score carries a fixed weight, published below. This is the scoring contract: each risk signal adds the weight in this table, and the combination rules decide which weights are added. Only two entries differ: a slug carried forward from an earlier result, which can repeat with a partial weight (see above), and therate_limited entry that comes with the 999 marker (see Caveats). The heavier the weight, the stronger and rarer the tell: Tor or a headless client is a near-certain tell, while a lone VPN or proxy is common and often legitimate, so it carries far less.
The first column is the display label; the second is the slug you receive in
signals[].name. Branch on the slug or on detection_flags, and treat score_details on the History API as an internal log: it holds internal descriptions and debug entries, so store it for review but never branch on it or show it to your users. Flag keys match the slugs except for Anti-detect Browser, whose flag is detection_flags.anti_detect_browser; the proxy-routed anti-detect and late network check rows have no flag. The same weight from two different risk signals is not interchangeable. IP Mismatch, Incognito, Search bot, Suspicious Paid Click and Check Incomplete are informational flags with a weight of 0.How signals combine
The Risk Score is additive, with these rules applied before the sum:- Tor and Privacy Relay are exclusive. When either fires, no other network, operating system or anti-detect weight is added next to it. JavaScript Disabled and Browser Automation still add.
- VPN and Browser VPN/Proxy stand in for the proxy stack. Each replaces the Proxy, Datacenter IP, Abuser Flag, OS Mismatch, OS not Detected, STUN not Checked and Timezone Mismatch weights with its own. Anti-detect Browser still adds on top of a VPN.
- Anti-detect indicators do not stack. One Anti-detect Browser weight is added, however many indicators fire.
- Proxy, Datacenter IP and Abuser Flag stack with each other when neither VPN nor Browser VPN/Proxy fires.
- Known search-engine crawlers score 0. The identification is set to 0 and carries the
search_botflag. - JavaScript Disabled (90) on its own places an identification in the Dangerous band.
How to use the Risk Score
The Risk Score maps to three bands. The payload carries only the number (risk_score on the webhook, score in History), with no band field, so map the number to a band in your backend. The recommended action is a guide: pick the action per band that fits your risk tolerance.
ShieldLabs returns the 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. ShieldLabs stops fraud and abuse and helps block fraudulent and abusive traffic.
Risk of a user, device, visitor or IP
Only an identification has a number. A user, device, visitor or IP address carries a band: the worst band among its identifications in the period you look at. A user with twenty Trusted identifications and one Dangerous identification is Dangerous, so one masked or automated signup is enough to mark the account. Users, devices, visitors and IPs explains how identifications link to each identity. High-Risk Events are a separate axis. ShieldLabs detects multi-accounting, account sharing, impossible travel and account takeover on your users, each at Medium or High confidence, and a user can be Trusted on every identification and still be detected for multi-accounting. High-Risk Events are available in the analytics dashboard, the API and webhooks. See High-Risk Events. To read an account’s band in your backend, fetch its identifications from the History API and take the highestscore, skipping values above 100:
offset when an account has more than 100 identifications. Search by device_id, visitor_id or ip the same way for a device, a visitor or an address.
In the analytics dashboard, every user, device, visitor and public IP card shows this band for the selected period, with the split of its identifications across Trusted, Suspicious and Dangerous (User, device, visitor and IP cards).


A user in the analytics dashboard: the worst band of its identifications and a High-Risk Event (red pill: High confidence).
Caveats
The risk scale is 0 to 100. The value 999 is the rate-limit marker, written when a visitor IP is banned after too many identifications: it is not a 0 to 100 Risk Score and not a band. A rate-limit ban also surfaces as HTTP 429 on the identification request. The 999 marker can arrive inrisk_score on the webhook and in score on the History API, so treat any value above 100 as that marker before you read the band. On the webhook the marker comes with one signals entry, { "name": "rate_limited", "weight": 999 }: skip it when you log or sum risk signals.
Next steps
Acting on results
Turn the Risk Score, its risk signals and the user’s band into allow, challenge, review or block in your backend.
Risk Signals
What each risk signal covers, from masking to bots and automation, and how it is surfaced.
Users, devices, visitors and IPs
How each identification links to a user, a device, a visitor and IP addresses, each with its own risk.
Accuracy
How ShieldLabs measures 99.9% identification accuracy and 99.9% risk signal detection accuracy.