Explainer
Cloudflare Turnstile Token: What It Is and What It Proves
What a Cloudflare Turnstile token is, where the widget puts it, how siteverify checks it, what it proves, and how a solving API produces one.
By ZeroCaptcha Engineering6 min readPublished Updated
A Cloudflare Turnstile token is the string the Turnstile widget produces when its challenge
passes: at most 2,048 characters, valid for 300 seconds from generation, and good for one
verification. The page sends it to the site’s server, in a hidden cf-turnstile-response input or
through the widget’s JavaScript callback, and the server asks Cloudflare’s siteverify API whether
it is genuine. Until that call succeeds, the token proves nothing.
This article explains where the token comes from, what the site does with it, what it does and does not prove, and how a solving API produces one. If you automate pages that show the widget, do it only on sites you are allowed to automate: see responsible captcha automation.
Where a Cloudflare Turnstile token comes from
When the widget loads, Cloudflare runs “a series of small non-interactive JavaScript challenges to gather signals about the visitor or browser environment”, such as proof-of-work and probes for web APIs. When those pass (and, in Managed mode, after a checkbox click if one is asked for), the widget has a token. It hands the token to the page in one of three ways:
- A hidden form field. “When you embed a Turnstile widget inside a
<form>element, an invisible input field with the namecf-turnstile-responseis automatically created.” A normal form post sends it with the other fields. The site can rename the field withresponse-field-name, or turn it off withresponse-field. - A callback. The
callbackoption is “invoked upon success of the challenge. The callback is passed a token that can be validated.” Single-page apps often send it in a JSON body under a name of their own. - On request. Page code can read it with
turnstile.getResponse(widgetId).
The token is the same kind of string in Managed, Non-Interactive and Invisible mode; see Cloudflare Turnstile widget modes. Cloudflare documents its maximum length, 2,048 characters, so size any column or field that stores one for that. Treat it as opaque: send it as it is, and never trim or re-encode it.
What the site does with it: siteverify
The site’s server sends the token to
POST https://challenges.cloudflare.com/turnstile/v0/siteverify. The endpoint “accepts both
application/x-www-form-urlencoded and application/json requests, but always returns JSON
responses”, and it does not accept GET. It takes four parameters:
| Parameter | Required | Cloudflare’s description |
|---|---|---|
secret |
Yes | “Your widget’s secret key from the Cloudflare dashboard” |
response |
Yes | “The token from the client-side widget” |
remoteip |
No | “The visitor’s IP address” |
idempotency_key |
No | “A UUID you generate to safely retry validation requests” |
The reply carries these fields:
| Field | Cloudflare’s description |
|---|---|
success |
“Boolean indicating if validation was successful” |
challenge_ts |
“ISO 8601 timestamp when the challenge was solved” |
hostname |
“Hostname where the challenge was served” |
error-codes |
“Array of error codes (if validation failed)” |
action |
“Custom action identifier from client-side” |
cdata |
“Custom data payload from client-side” |
metadata.ephemeral_id |
“Device fingerprint ID (Enterprise only)” |
A site’s own check, in Python with requests, looks like this. It runs on the server, because
Cloudflare says: “Only call the Siteverify API in your backend environment.”
import os
import requests
SITEVERIFY = "https://challenges.cloudflare.com/turnstile/v0/siteverify"
def verify_turnstile(token, remote_ip=None, expected_action=None, expected_hostname=None): data = {"secret": os.environ["TURNSTILE_SECRET_KEY"], "response": token} if remote_ip: data["remoteip"] = remote_ip result = requests.post(SITEVERIFY, data=data, timeout=10).json() if not result.get("success"): return False, result.get("error-codes", []) if expected_action and result.get("action") != expected_action: return False, ["action-mismatch"] if expected_hostname and result.get("hostname") != expected_hostname: return False, ["hostname-mismatch"] return True, []The last two checks follow Cloudflare’s best-practice list: “Check additional fields. Validate the
action and hostname when specified.” The action-mismatch and hostname-mismatch strings are
this example’s own labels, not Cloudflare error codes. The seven codes siteverify can return, and
why a token is refused, are in
Cloudflare Turnstile siteverify errors.
What a token proves, and what it does not
A token that passes siteverify tells the site that Cloudflare accepted it for the widget whose
secret key made the call, that it had not been validated before and was still inside its 300
seconds, and, through the reply’s fields, when the challenge was solved (challenge_ts), on which
hostname, and with which action and cdata. The site decides what to do with those facts.
A token that has not been through siteverify proves nothing. Cloudflare says so directly:
“Tokens can be forged. An attacker can submit any string to your form endpoint without completing
a challenge.” A site that only checks that cf-turnstile-response is present, or checks it in
the browser, is not protected at all.
Nor does a token identify a person or a device. Apart from metadata.ephemeral_id, which is for
Enterprise customers only, the documented reply names no device, and remoteip is an optional
input.
Lifetime and single use
Cloudflare’s limits are “Validity period: 300 seconds (5 minutes) from generation” and “Single
use: Each token can only be validated once”. Both failures come back from siteverify as
timeout-or-duplicate. In the browser, the widget’s expired-callback is “invoked when the token
expires and does not reset the widget”, and the page gets a new token with turnstile.reset().
When you hold tokens in your own code, the rules for spending them promptly are in
Cloudflare Turnstile token expiry.
The test dummy token
Cloudflare’s testing sitekeys, such as 1x00000000000000000000AA (always passes, visible),
“generate a dummy token: XXXX.DUMMY.TOKEN.XXXX”, and “Production secret keys will reject the
dummy token.” So a dummy token that reaches a live site fails. The testing secret
3x0000000000000000000000000000000AA returns the “token already spent” error, so a site can test
its timeout-or-duplicate handling. If the site is yours, those keys are the way to test it; see
test Cloudflare Turnstile in CI.
How a solving API produces a token
A solving API returns the same kind of token for a page you name, and the site verifies it with
siteverify like any other. With ZeroCaptcha, you send createTask with the page’s URL and the
widget’s sitekey, plus the widget’s action and cData in metadata when it sets them, copied
from its data-action and data-cdata (or the action and cData options of
turnstile.render()):
{ "clientKey": "zc_live_…", "task": { "type": "TurnstileTaskProxyless", "websiteURL": "https://shop.example.com/login", "websiteKey": "0x4AAAAAAAB1cD2eF3gH4iJ5", "metadata": { "action": "login", "cdata": "session-7f3a9c2e" } }}The reply is a taskId. Poll getTaskResult with it until status is ready:
{ "errorId": 0, "taskId": "0192f3a4-7b1c-7d2e-9f10-3c4d5e6f7a8b", "status": "ready", "solution": { "token": "0.xT4…", "type": "turnstile" }, "cost": "0.000800", "createTime": 1790762400, "endTime": 1790762404, "solveCount": 1, "expiresAt": "2026-09-30T10:05:04Z"}cost is what the task was charged, in US dollars. The token in solution.token works once, within 300
seconds; expiresAt is the moment it stops working, in UTC. A task is charged only when its token
is ready; one that fails or times out costs nothing (see pricing). The
createTask and getTaskResult guide covers the
polling loop, and find a Cloudflare Turnstile sitekey
shows where the sitekey sits in the page.
ZeroCaptcha solves Cloudflare Turnstile, not reCAPTCHA or hCaptcha. A Turnstile token is also not
a cf_clearance cookie: a full-page “Just a moment…” challenge is a different mechanism,
explained in Cloudflare challenge page vs Cloudflare Turnstile.
Code samples for ten languages and tools are on the
Cloudflare Turnstile solver page.
Inspect and check a token
Paste a token into the cf_clearance and Cloudflare Turnstile token inspector to see its format and length, or get one from a Cloudflare Turnstile demo and check it with the Cloudflare Turnstile token checker.
Sources
- Cloudflare Turnstile: server-side validation (checked 1 October 2026).
- Cloudflare Turnstile: client-side rendering and widget configurations (checked 1 October 2026).
- Cloudflare Turnstile overview (checked 1 October 2026).
- Cloudflare Turnstile: testing (checked 1 October 2026).
The team that builds and runs the ZeroCaptcha API. Articles are drafted with AI tools, then checked against the API's code and the primary sources each one cites.