Skip to content

Fraud Prevention Data API

The Data API is the server-side counterpart to the Web SDK. Use it to pull verdicts, aggregate statistics, and export data for reporting and reconciliation — pulling the numbers your own systems agree on.

Base URL

https://apiv1.captcha.la

Authentication

All Data API requests are authenticated with your Fraud Prevention application credentials, sent as headers:

X-App-Key:    YOUR_APP_KEY
X-App-Secret: YOUR_APP_SECRET

WARNING

X-App-Secret is server-side only. Never expose it to browsers, mobile apps, or public repositories. The page SDK only ever uses the public appKey.

Endpoints

Fetch a verdict

Retrieve the verdict for a single visit (e.g. to reconcile a specific visit).

bash
GET /v1/bot/verdict?cid=CID_OF_THE_VISIT
X-App-Key: YOUR_APP_KEY
X-App-Secret: YOUR_APP_SECRET

The response data is a BotVerdict object:

json
{
  "code": 0,
  "data": {
    "is_bot": true,
    "score": 87,
    "level": "high",
    "action": "flag",
    "consistency": { "ok": false },
    "degraded": false
  }
}

Aggregated stats

Pull bucketed counts over a time range — totals, bot share, and the breakdown by action/level — for dashboards and quality reports.

bash
GET /v1/bot/stats?from=2026-06-01&to=2026-06-30
X-App-Key: YOUR_APP_KEY
X-App-Secret: YOUR_APP_SECRET
json
{
  "code": 0,
  "data": {
    "from": "2026-06-01",
    "to": "2026-06-30",
    "total": 124500,
    "bots": 18230,
    "bot_rate": 0.146,
    "by_action": { "record_only": 102100, "flag": 19800, "challenge": 2600 },
    "by_level":  { "low": 100300, "medium": 16900, "high": 6200, "critical": 1100 }
  }
}

Export

Export per-visit verdict rows for a time range, for offline reconciliation.

bash
GET /v1/bot/export?from=2026-06-01&to=2026-06-30&format=csv
X-App-Key: YOUR_APP_KEY
X-App-Secret: YOUR_APP_SECRET

Each row carries the visit's identifier, timestamp, and verdict fields (is_bot, score, level, action), so you can join it back to your own logs.

Per-click reconciliation

For paid-traffic scenarios, a single visit can be tied back to a specific delivered click so two parties can settle on it. That uses a click token and is covered in the Ad fraud guide.

Click token reference (for traffic providers)

A traffic provider signs a click token on its own server and adds it to the destination URL, so the verdict for the resulting visit is attributed back to the provider. Signing is offline — no API call.

Get your signing credentials

In the dashboard, open your app → Fraud Prevention → Issue signing key. You receive:

  • bot_kid — your public key id (goes into the token as pkid).
  • bot_hmac_secret — your signing secret, shown once. Keep it server-side.

Token format

ct.<base64url(payload)>.<base64url(HMAC_SHA256(body, bot_hmac_secret))>

body is the base64url of the JSON payload; the signature is computed over that body string.

Payload fields

fieldrequireddescription
pkidyesyour bot_kid; the backend uses it to look up your secret and verify the signature
cidyesunique id for this click; the reconciliation key. Generate a fresh, unique value per click
audnothe target advertiser's app_key. When set, the token is only honored on that advertiser's page. Omit to let any advertiser page accept it
click_tsnounix seconds when the click happened
expnounix seconds expiry; expired tokens are rejected

Append the token as the _ctk query parameter on the advertiser's destination URL:

https://advertiser.example/lp?_ctk=ct.<...>.<...>

The advertiser's SDK reads _ctk automatically (configurable via tokenParam). A cid can be claimed only once (replay-protected). See the Security model.

Example (pseudo-code)

js
const payload = { pkid, cid, aud: advertiserAppKey, click_ts: now, exp: now + 900 }
const body  = base64url(JSON.stringify(payload))
const sig   = base64url(hmacSha256(body, botHmacSecret))
const token = `ct.${body}.${sig}`
const url   = `${destination}?_ctk=${encodeURIComponent(token)}`

Postback (server-to-server)

Instead of polling the Data API, you can have verdicts pushed to your own endpoint as they happen — handy for affiliate trackers and advertiser back-ends that want to record or reconcile each visit in real time.

Enable it

In the dashboard, open your app → Fraud Prevention → set Postback URL (bot_postback_url). Once set, every verdict is POSTed to that URL shortly after it is produced. Delivery is asynchronous and isolated — a slow or failing endpoint never affects the verdict already returned to the page SDK.

When it fires

On each verify that produces a verdict for the app. Delivery is best-effort with a few automatic retries; treat it as at-least-once and dedupe on cid.

Request

POST <your bot_postback_url>
Content-Type: application/json
X-Bot-Signature: t=<unix_ts>,v=<hex>

Body (JSON):

json
{
  "cid": "CID_OF_THE_VISIT",
  "is_bot": 1,
  "score": 87,
  "level": "high",
  "action": "flag",
  "advertiser_app_id": 12,
  "provider_app_id": 34,
  "created_at": "2026-06-25 00:00:00"
}
fielddescription
cidreconciliation key for the visit (may be null if no click token was present)
is_bot1 / 0
scorerisk score
levellow / medium / high / critical
actionserver-authoritative action (record_only / flag / challenge)
advertiser_app_idid of the app the verify ran for
provider_app_idtraffic provider id when attributed via a click token, else null
created_atverdict time (UTC)

Verify the signature

The X-Bot-Signature header lets you confirm the request really came from us. It is t=<unix_ts>,v=<hex> where:

hex = HMAC_SHA256( "<unix_ts>." + raw_request_body , bot_hmac_secret )

bot_hmac_secret is the same signing secret you issue under Fraud Prevention → Issue signing key. To verify, recompute the HMAC over the raw body you received (do not re-serialize the JSON) and compare in constant time. Optionally reject requests whose t is too far from your clock to bound replay.

js
// Express example
const [tsPart, vPart] = req.get('X-Bot-Signature').split(',')
const ts = tsPart.slice(2)             // "t=" prefix
const v  = vPart.slice(2)              // "v=" prefix
const expected = crypto
  .createHmac('sha256', BOT_HMAC_SECRET)
  .update(ts + '.' + rawBody)          // rawBody is the unparsed request body
  .digest('hex')
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v))

Next steps

MIT-licensed examples · CaptchaLa is operated independently