# 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.

- Source: https://zerocaptcha.io/blog/cloudflare-turnstile-token
- Published: 2026-09-30
- Updated: 2026-10-01
- Author: ZeroCaptcha Engineering

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](https://zerocaptcha.io/guides/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](https://zerocaptcha.io/guides/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."

```python
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](https://zerocaptcha.io/blog/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](https://zerocaptcha.io/guides/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](https://zerocaptcha.io/guides/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()`):

```json
{
  "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`:

```json
{
  "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](https://zerocaptcha.io/pricing)). The
[createTask and getTaskResult guide](https://zerocaptcha.io/guides/createtask-gettaskresult-explained) covers the
polling loop, and [find a Cloudflare Turnstile sitekey](https://zerocaptcha.io/guides/find-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](https://zerocaptcha.io/guides/cloudflare-challenge-vs-turnstile).
Code samples for ten languages and tools are on the
[Cloudflare Turnstile solver](https://zerocaptcha.io/cloudflare-turnstile-solver) page.

## Inspect and check a token

Paste a token into the [cf_clearance and Cloudflare Turnstile token inspector](https://zerocaptcha.io/tools/cf-clearance-token-inspector)
to see its format and length, or get one from a [Cloudflare Turnstile demo](https://zerocaptcha.io/captcha-test) and check it with
the [Cloudflare Turnstile token checker](https://zerocaptcha.io/captcha-test/cloudflare-turnstile-token-checker).

## Sources

- [Cloudflare Turnstile: server-side validation](https://developers.cloudflare.com/turnstile/get-started/server-side-validation/)
  (checked 1 October 2026).
- [Cloudflare Turnstile: client-side rendering](https://developers.cloudflare.com/turnstile/get-started/client-side-rendering/)
  and [widget configurations](https://developers.cloudflare.com/turnstile/get-started/client-side-rendering/widget-configurations/)
  (checked 1 October 2026).
- [Cloudflare Turnstile overview](https://developers.cloudflare.com/turnstile/) (checked 1
  October 2026).
- [Cloudflare Turnstile: testing](https://developers.cloudflare.com/turnstile/troubleshooting/testing/)
  (checked 1 October 2026).

## 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.
