Verdict Reference
Every Fraud Prevention verdict — whether delivered to your onVerdict callback in the Web SDK or pulled from the Data API — is the same BotVerdict object. This page documents each field and how to act on it.
{
"is_bot": false,
"score": 18,
"level": "low",
"action": "record_only",
"reasons": [],
"consistency": { "ok": true },
"degraded": false,
"verdict_id": "vd_3f9c0a7e5b1d4c2a9e8f7b6a",
"verdict_token": "vt1.eyJ2Ijoi...",
"verdict_token_exp": 1767225600
}Fields
| Field | Type | Description |
|---|---|---|
is_bot | boolean | The headline verdict. true = the visit is judged to be automated or invalid traffic; false = it looks like a real person. This is the single field most integrations branch on. |
score | number (0–100) | How suspicious the visit is. Higher means more suspicious. Use it to set your own thresholds — e.g. exclude visits above a cut-off you choose. The exact value is produced by our risk model and is not meant to be reverse-engineered. |
level | enum | Coarse band of score: low, medium, high, critical. Convenient when you want buckets instead of a raw number. |
action | enum | Our recommended handling for the visit: record_only, challenge, flag, or deny. See below. Treat unknown values as flag, so your integration keeps working if new values are added. |
reasons | string[] | Up to three broad reason categories explaining a risky verdict. Empty for clean visits. |
consistency | object | A cross-signal consistency conclusion. Read consistency.ok (boolean): true = the visit's signals line up as expected; false = something didn't add up and the visit deserves extra scrutiny. The internal breakdown behind this conclusion is not exposed. |
degraded | boolean | true when the verdict could not be fully computed (service unreachable, error, etc.) and a conservative fallback was returned. Treat degraded verdicts as "unknown", not "clean". |
verdict_id | string | Unique id of this verdict. Use it to look the verdict up later with the Data API. |
verdict_token | string | Signed proof of this verdict, for your server to verify. See Server-side verification. Absent on fallback verdicts. |
verdict_token_exp | number | Unix time (seconds) at which verdict_token expires. |
action — what to do
action is our recommendation; you stay in control of the final decision.
action | Meaning | What to do |
|---|---|---|
record_only | The visit looks normal. | Proceed as usual. Just log the verdict for reporting and reconciliation. |
flag | The visit is suspicious but not conclusively bad. | Keep serving the page, but mark the visit as low quality — exclude it from your metrics, segment it out of downstream flows, and weigh it in your own decisions (e.g. conversion attribution or how much you trust the visit). |
challenge | The visit reached your challenge threshold (high risk by default). | Ask for one extra verification before treating the visitor as human. When your app's handling action is set to challenge, the Web SDK runs it for you (see Escalation); otherwise apply your own gate. |
deny | The visit matched a block rule — for example a blocklist entry or a policy you configured to block. | Do not serve protected content. No challenge is offered. Older SDK versions pass this value through unchanged, so handle it in onVerdict. |
Configurable handling action
What action reports for a high-risk visit is per-app configuration, set in the dashboard under your application's details. The verdict is server-authoritative: action only becomes challenge or flag when the visit reaches the configured risk threshold (high risk by default, or medium in strict mode — see below) and your app is configured with the matching action. Below the threshold the verdict stays record_only.
| Setting | action returned at high risk | Meaning |
|---|---|---|
record_only (default) | record_only | Record only. The verdict is logged for reporting and reconciliation; nothing is asked of the visitor. |
challenge | challenge | Trigger a re-verification CAPTCHA. The Web SDK pops a challenge (styling is controlled by the SDK's challengeConfig). |
flag | flag | Mark the visit. The verdict carries flag; your integration decides how to act on it (block / segment / handle). |
Because the decision is made server-side, your code stays simple: read verdict.action in onVerdict and act on it — proceed, block, or let the SDK present a challenge. You do not need to re-derive the risk yourself.
Challenge threshold and type
When the handling action is challenge, two more settings appear in the same dashboard section:
- Challenge threshold — which risk level returns the configured action. The default is high risk and above. Strict mode also applies it to medium-risk visits: more scrapers and AI agents are stopped, but more real visitors see a challenge.
- Challenge type — the minimum CAPTCHA type shown on escalation: slider (default), rotate, click or 3D click. Stronger types are harder for automated and AI tools to solve.
escalate_result
Present after a managed captcha escalation runs — i.e. when action was challenge and the SDK ran the captcha. null when no escalation ran. Also delivered to onEscalateDone(passed, detail).
| field | description |
|---|---|
passed | whether the visitor passed the captcha |
token | the captcha pass token — verify it server-side to confirm the pass (do not trust the client passed alone) |
challenge_id | the captcha challenge id |
cid | the click id this escalation is tied to, if a click token was present |
reasons — reason categories
reasons tells you, in broad terms, why a visit looked risky. It holds at most three categories, strongest first. It never contains individual signals or score breakdowns.
| Category | Meaning |
|---|---|
automation | Signs of automated tooling, such as scripted or headless browsers. |
network | The network looks risky or inconsistent, such as a proxy, data-center or anonymizing network, or a blocked address. |
abnormal_requests | Unusual request patterns, such as too many requests, repeated failures, or a history of automated traffic. |
device | The device or its environment could not be trusted, or could not be recognized. |
behavior | The visit did not behave like a person, for example no interaction at all or a reused link. |
other | Any other reason, including your own rules and fallback verdicts. |
Treat the list as an explanation for reporting, not as a signal to build rules on. Branch on is_bot, action or score instead. New categories may be added later.
Server-side verification
Anything the page receives can be changed by the visitor. If you gate content (only real people may see a page, an API response or a download), do not trust the is_bot flag in the browser. Send verdict_token to your own server and verify it there:
- In
onVerdict, sendverdict.verdict_tokento your backend, for example in a header or a cookie. - Your backend calls
POST /v1/bot/verdict/verifywith the token and the visitor's IP address. - Serve the content only when the response has
valid: trueand the verdict is acceptable to you (for exampleverdict.is_botisfalse).
The token is bound to your app, to this verdict and to the visitor's network, and expires after a short time (10 minutes by default). By default a token can be checked many times, so one token can cover the page views of a visit. Pass consume: true when a token must be used only once, such as for a form submission.
If the visitor then passes an escalation challenge, the token still shows the original verdict. Verify the captcha pass token from escalate_result with the captcha server-side API as well.
level vs score
level is just a banding of score, provided for convenience:
level | Use it when |
|---|---|
low | Treat as clean traffic. |
medium | Borderline — fine to record, consider excluding from high-value funnels. |
high | Strongly suspicious — flag and exclude. |
critical | Almost certainly invalid — flag/exclude and, ideally, escalate. |
Pick the field that fits your pipeline
- Simple yes/no gate → branch on
is_bot. - Recommendation-driven → branch on
action. - Custom thresholds → branch on
score(orlevelfor buckets).
Handling degraded verdicts
When degraded is true, the verdict was not fully computed. The fallback is deliberately conservative (is_bot: false, action: record_only) so real users are never blocked. For reporting, treat these as unknown rather than counting them as confirmed-clean traffic.