Skip to content

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 6 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 name cf-turnstile-response is automatically created.” A normal form post sends it with the other fields. The site can rename the field with response-field-name, or turn it off with response-field.
  • A callback. The callback option 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

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.

Questions

What is a Cloudflare Turnstile token?

It is the string the Turnstile widget produces when its challenge passes, up to 2,048 characters long. The page sends it to the site's server in the cf-turnstile-response field or through a callback, and the server checks it with Cloudflare's siteverify API.

Can a Cloudflare Turnstile token be used twice?

No. A token is valid for 300 seconds from generation and can be validated once; siteverify answers timeout-or-duplicate for a replayed or expired token.

Does a Turnstile token prove the visitor is human?

Not on its own. Cloudflare warns that tokens can be forged, since anyone can post any string to a form. Only a successful siteverify call with the widget's secret key shows that Cloudflare accepted the token.

Read next

This article is part of the Cloudflare Turnstile solver hub. Every task is charged only when a token is ready.

Get an API key