--- title: How verification works --- # How verification works CaptchaLa is risk-engine-first. Every request is scored before any challenge is shown. Most requests never see an interactive challenge — the engine decides how much friction (if any) a given request needs. This page describes that flow and the tokens it produces. ## The graduated model When the SDK initializes a challenge, the server scores the request from device, network, and behavioral signals (and, where available, attestation signals — see [Attestation & Privacy Pass](./attestation-and-privacy-pass)). The score then selects a verification tier: | Risk | What the user experiences | | --- | --- | | Low / attested | Invisible. A lightweight proof-of-work runs in the background; no interaction. | | Medium | A single image challenge (slide, click, select). | | High | A harder challenge, stricter rate limits, or rejection. | The goal is that legitimate, low-risk traffic passes without friction, and only the requests the engine cannot clear automatically are asked to solve something. The tier is decided server-side; the client cannot raise its own trust level. ## Token types A completed verification yields a **pass token** that your frontend forwards to your backend. The prefix tells you how it was produced and how to verify it. | Prefix | Produced when | How to verify | Notes | | --- | --- | --- | --- | | `pt_` | Verification passed normally | `POST /v1/validate` (server-side) | Single-use. The common case. | | `sct_` | Issued by **your** server ahead of time | n/a — it is an input to challenge init, not a pass token | Server-issued challenge token; binds a challenge to your trusted backend. See below. | | `dg_` | App quota exhausted (degraded mode) | `/v1/validate` returns `valid: false, degraded: true` | HMAC-signed, 5-min expiry. Accept/reject is your decision. | | `offline_` | Main API unreachable; issued by the bypass worker | Verified against the backup API (server SDKs do this automatically) | Continuity fallback. | | `co_` | Main **and** backup unreachable; generated client-side | Cannot be verified server-side | Apply your own risk controls. | `sct_` is not a pass token — it is a short-lived credential **you** issue from your backend (`POST /v1/server/challenge/issue`) and the browser forwards into challenge initialization, proving the request originated from your server. It can be bound to an IP, device id, or fingerprint. See the [API Reference](../api-reference#server-token). `pt_` is the token you actually validate after a user solves a challenge. ::: info Publicly-verifiable tokens All tokens above are verified by calling CaptchaLa (or, for `offline_`, the backup API). A token you can verify **offline** against a published issuer public key — no call back to us — is a roadmap item, not available today. See the [roadmap note](./attestation-and-privacy-pass#roadmap). ::: ## Single-use and anti-replay `pt_` tokens are **single-use**. The first successful `/v1/validate` consumes the token; a second validation of the same token returns `token_already_used`. This is what prevents a captured token from being replayed against your endpoint. Each token also carries the context recorded at solve time (`action`, the end-user IP, solve timestamp, platform, risk score). Two checks matter on your side: - **`valid === true`** — the token verified and has not been consumed. - **`action` matches the scene you expected** — reject a token minted for one flow (e.g. `pay`) if it is presented at another (e.g. `/login`). The `action` is bound into the token at issue time and echoed back at validation. Tokens are also short-lived; an expired token returns `token_expired`. ## Server-side verification flow ``` Browser/App Your backend CaptchaLa API | | | | 1. (optional) ask server | | | for a server_token |--- POST /v1/server/ | |<------ sct_ ---------------| challenge/issue --------->| | |<------ sct_ -----------------| | | | | 2. init + solve challenge |------------------------------>| | (sct_ forwarded) | (risk scoring + tier) | |<------ pt_ ----------------|------------------------------| | | | | 3. submit form w/ pt_ ---->| | | | 4. POST /v1/validate | | | X-App-Key / X-App-Secret | | | { pass_token, client_ip }-->| | |<--- { valid, action, ... } --| | | 5. check valid && action | ``` Validation is always done **server-to-server** with your `X-App-Secret`, which must never reach the browser. Sending the end-user's `client_ip` is optional but recommended — it lets the engine apply an extra consistency check at validation time. For request/response schemas and error codes, see the [API Reference](../api-reference).