# Cloudflare Turnstile Token Expired? Lifetime and Reuse Explained

> Cloudflare Turnstile tokens last 300 seconds and work once. Why tokens expire, what timeout-or-duplicate means, and how to use each token before it runs out.

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

A Cloudflare Turnstile token has two limits: it expires 300 seconds after it is issued, and it
can be verified once. Most "the site rejected my token" problems come down to one of the two.
This guide explains both, the error the site sees, and how to structure your code so a token is
always used while it is fresh.

## The two rules

1. **Lifetime.** A token is valid for 300 seconds, five minutes, from the moment the widget or a
   solver obtains it. The clock does not start when you receive it, and it does not pause while
   your job waits in a queue.
2. **Single use.** The site's server redeems a token by sending it to Cloudflare's siteverify
   endpoint. The first call succeeds; any later call with the same token fails.

When either rule is broken, siteverify answers with the error code `timeout-or-duplicate`. The
site then treats the submission as if the widget had not been solved, which usually looks like a
generic "please try again" message.

## How ZeroCaptcha shows the lifetime

Every solved task tells you when its token expires, so you never have to guess. In the compatible
format, `getTaskResult` answers:

```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 cost in US dollars, and `expiresAt` the moment the token stops working, in
UTC. A REST task shows the same as
`tokenIssuedAt` and `tokenExpiresAt`. If you read a solved task after its token has expired,
`getTaskResult` answers `ERROR_TOKEN_EXPIRED`: the task was solved and charged, and the token can
no longer be used. See the [errors reference](https://zerocaptcha.io/docs/reference/errors#ERROR_TOKEN_EXPIRED).

## Patterns that waste tokens

- **Solving ahead.** Creating tasks at the start of a batch and submitting forms minutes later.
  By the time the fifth form is sent, its token may be old.
- **Retrying the form with the same token.** If the site answers with an error for another
  reason, such as a wrong password, the token has already been redeemed. The next attempt needs a
  new token.
- **Sharing a token between requests.** One token, one form submission.
- **Slow polling.** Asking for the result every 30 seconds adds up to half a minute of the
  token's life. Poll every one or two seconds, or use a
  [callback](https://zerocaptcha.io/guides/captcha-solver-callbacks) so the result is pushed to you.

## A pattern that works

Create the task at the moment your code reaches the form, wait for the token, and submit
straight away.

```python
import os, time, uuid, requests

API, KEY = os.environ["ZEROCAPTCHA_API"], os.environ["ZEROCAPTCHA_KEY"]

def fresh_token(page_url: str, sitekey: str, action: str = "", cdata: str = "") -> str:
    task = {"type": "TurnstileTaskProxyless", "websiteURL": page_url, "websiteKey": sitekey}
    # The widget's data-action and data-cdata (or turnstile.render()'s action and cData), sent
    # only when it sets them: many sites check both when they verify the token.
    task["metadata"] = {name: value for name, value in (("action", action), ("cdata", cdata)) if value}
    # One Idempotency-Key per task: a retried create with it returns the same task.
    created = requests.post(f"{API}/createTask", timeout=15, headers={"Idempotency-Key": str(uuid.uuid4())},
                            json={"clientKey": KEY, "task": task}).json()
    if created["errorId"]:
        raise RuntimeError(created["errorCode"])
    deadline = time.monotonic() + 180
    while time.monotonic() < deadline:
        time.sleep(2)
        result = requests.post(f"{API}/getTaskResult", timeout=15,
                               json={"clientKey": KEY, "taskId": created["taskId"]}).json()
        if result["errorId"]:
            raise RuntimeError(result["errorCode"])
        if result["status"] == "ready":
            return result["solution"]["token"]
    raise TimeoutError("no token within 180 seconds")

token = fresh_token("https://shop.example.com/login", "0x4AAAAAAAB1cD2eF3gH4iJ5",
                    action="login", cdata="session-7f3a9c2e")
# Submit the form now, with token as cf-turnstile-response.
```

If the form submission fails for a reason other than the token, call `fresh_token` again before
the next attempt. [Submit a Cloudflare Turnstile token](https://zerocaptcha.io/guides/submit-cloudflare-turnstile-token) shows how the token
goes into the form.

## Budget the five minutes

Measure the time from `expiresAt` minus 300 seconds (when the token was issued) to your form
submission. If it is often more than a minute, something in your pipeline is holding tokens: a
queue, a batch step, or a slow page load after the solve. The median solve time over the last 24
hours is on the [status page](https://zerocaptcha.io/status), which helps you size deadlines.

## Cost

A task is charged when its token is ready, not when you use it. An expired token is therefore
money spent for nothing. Tasks that fail or time out are never charged, but a solved task whose
token you let expire is. Using tokens promptly is the simplest way to keep the cost per useful
token at the [listed price](https://zerocaptcha.io/pricing).

More on the solving flow is on the [Cloudflare Turnstile solver](https://zerocaptcha.io/cloudflare-turnstile-solver) page.

## Questions

### How long is a Cloudflare Turnstile token valid?

300 seconds from the moment it is issued, and for one verification only. After that the site's siteverify call answers timeout-or-duplicate.

### Am I charged for a token that expired before I used it?

Yes. The task succeeded, so it was charged; getTaskResult then answers ERROR_TOKEN_EXPIRED. Use each token as soon as it is ready to avoid paying for tokens you cannot use.

### Can I ask for a token in advance and keep it for later?

Not for long. A token is valid for 300 seconds, so create the task when you are about to submit the form, not minutes before.
