Skip to main content

Troubleshooting

A fast path from a symptom to its fix. Each entry names the likely cause and the one change that resolves it, then points you to the page with the full detail. For the meaning of a specific status code, the Errors reference is the canonical table; for short answers to common questions, start with the FAQ.

Install and data flow

Symptom. Nothing reaches your dashboard or webhook, and the browser never posts a snapshot.Cause. One of three things is blocking the snippet before it can run:
  • A Content Security Policy is refusing the hosts the snippet needs. The module and its dependency load under script-src, and the snapshot and network checks post under connect-src. A policy missing those hosts stops the snippet cold.
  • An ad blocker or content blocker is dropping the CDN host, so the module never downloads.
  • The page is not served over HTTPS. The snippet collects signals in a secure context only, so it does not run on plain http:// or a non-secure origin.
Fix. Open the browser dev tools. A CSP block shows a Refused to load or Refused to connect error naming the directive and host, which is your signal to add that host. The exact directives and host list live on the CSP setup page, and the snippet install guide shows the HTML and framework methods. Confirm the page loads over HTTPS, then load it with any blockers disabled to rule the extension in or out. Loading the module is not enough on its own: you must also call checkAnonymous() or checkAuthenticatedUser(hashedId).
Symptom. The check runs and bills, the score lands in the dashboard, but your endpoint never receives the POST.Cause. Either no callback URL is set for the domain, or the delivery was dropped. Webhook delivery is at-most-once with no retries and a short timeout, so a slow, down, or non-2xx endpoint silently loses that delivery, and there is no resend.Fix. Confirm a callback URL is set for the domain, from the dashboard or by posting it to the Server API /callback route, as the webhook setup covers. Make your handler return 200 fast, then do slow work asynchronously so you stay inside the timeout. Because a single delivery can always be lost, treat the History API as the guaranteed read: look the result up by request_id whenever it must not be missed.
Symptom. The payload looks correct, but your HMAC check rejects it.Cause. You are hashing a re-serialized copy of the JSON. Parsing the body and re-encoding the Data object reorders keys or changes spacing, so the bytes you hash no longer match the bytes that were signed.Fix. Compute the HMAC over the raw Data bytes exactly as received, not over a parsed-then-re-encoded object, and not over the whole envelope. Keep the raw body around, slice out the Data object bytes, and compare with a constant-time check. The webhook setup page has working Node, Go, and Python examples that do this correctly.

Reading the result

Symptom. A result carries DeviceID of 00000000-0000-0000-0000-000000000000, and that visit scores 90.Cause. No stable device characteristics reached the server, so no identity could be built. This happens when the snippet was blocked or JavaScript was disabled. The visit still scores, and a visit with nothing to identify scores 90.Fix. Route a null or all-zero DeviceID to review rather than auto-allowing it. You cannot recognize a returning person from an identity that was never collected, so treat the absence as a signal in its own right. The handling and the all-zero case are described on the Identification page.
Symptom. A real customer lands in the Medium or High band with no wrongdoing.Cause. A corporate VPN, a proxy, or a privacy-focused browser raises the Risk Score on its own. The signals are real, but they describe the connection, not the person’s intent.Fix. Decide on the Score plus its Details plus the action context, never the number alone. A withdrawal warrants a stricter line than a page view, and the Details tell you which signals fired so you can weigh them. Working from the four bands, the guide to acting on the Risk Score walks through tuning thresholds gradually so legitimate VPN users are not punished.
Symptom. You want the history for a device, visitor, account, or IP, or its earliest and latest sighting.Cause. There is no dedicated first-seen field on a result. History is a list of point-in-time snapshots, and each one carries LastRequestTime, the moment that snapshot was recorded.Fix. Read the History API by device_id, visitor_id, user_hid, or ip. Rows come back newest first, so the first row is the most recent activity and the last row in a full result is the earliest you have stored. The LastRequestTime on any row is when that visit was seen. Each returned row bills 1 request, so set limit to the smallest value that answers your question.

Status codes and limits

Symptom. The snippet’s snapshot post, or a Server API call, returns 402.Cause. Your request balance is exhausted. A 402 appears in two places when you run out: when the snippet posts an identification to be scored, and on the Server API. The History part of the Server API bills one request per returned row, so a wide search can drain a low balance quickly.Fix. Top up your request balance or upgrade your plan, as the Billing page lays out. A 402 is a billing state and is unrelated to rate limiting.
Symptom. The gateway returns 429, or a webhook or History row arrives with Score of 999.Cause. Both come from the per-IP rate limit, a gateway protection rather than a verdict. The browser receives the 429, and a rate-limit-banned request can also surface a 999 marker on a webhook delivery or a History row. That 999 is not capped to 100, so it arrives as-is.Fix. Guard for it at the very top of your handler so it never reaches your decision logic:
// 999 is the rate-limit ban marker, never a customer score.
if (Data.Score > 100) return;
The Risk Score is always 0 to 100. Read 999 as “this IP was rate-limited,” and spread traffic across client IPs rather than proxying through one server. The thresholds and the ban window are on the rate limits page.
Symptom. You want a liveness probe for monitoring or a load balancer health check.Cause. You need a lightweight endpoint that confirms a gateway is serving, without spending a request or running a scoring path.Fix. Each gateway exposes a GET /health endpoint that returns 200 with { "status": "ok" }. Point your uptime monitor or orchestrator liveness probe at it. It does not authenticate and does not bill.

Still stuck

If a status code is the question, the Errors page is the full per-surface reference, and the FAQ answers the questions developers ask most about keys, requests, identifiers, and a score of 0.