# Cloudflare Turnstile Siteverify Errors: Why a Token Is Rejected

> Every Cloudflare Turnstile siteverify error code with Cloudflare's meaning and fix, and why a site rejects a token when you automate a form.

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

Cloudflare Turnstile's siteverify API rejects a token with one of seven error codes:
`missing-input-secret`, `invalid-input-secret`, `missing-input-response`,
`invalid-input-response`, `bad-request`, `timeout-or-duplicate` and `internal-error`. Only two
are about the token itself: `timeout-or-duplicate` ("Token has already been validated", and also
what an expired token gets) and `invalid-input-response` ("Token is invalid, malformed, or
expired"). A token can also pass siteverify and still be refused, because the site compares the
reply's `action` or `hostname` with what it expects, or never finds the token where it looks.

The site's server makes the siteverify call, so when you automate a form you rarely see these
codes: you see the site's own error message. This article lists each code with Cloudflare's
wording, then maps each cause to what you, the developer automating the form, can change. Only
automate sites you are allowed to; see
[responsible captcha automation](https://zerocaptcha.io/guides/responsible-captcha-automation).

## Every Cloudflare Turnstile siteverify error code

Cloudflare's complete list, with its description and required action verbatim (checked 1
October 2026), and who can act on it:

| Error code | Cloudflare's description | Cloudflare's action | Who can fix it |
| --- | --- | --- | --- |
| `missing-input-secret` | "Secret parameter not provided" | "Ensure secret key is included" | The site |
| `invalid-input-secret` | "Secret key is invalid or expired" | "Check your secret key in the Cloudflare dashboard" | The site |
| `missing-input-response` | "Response parameter was not provided" | "Ensure token is included" | You, if the token was not in the field the site reads |
| `invalid-input-response` | "Token is invalid, malformed, or expired" | "User should retry the challenge" | You: send a new, complete token |
| `bad-request` | "Request is malformed" | "Check request format and parameters" | The site |
| `timeout-or-duplicate` | "Token has already been validated" | "Each token can only be used once" | You: one fresh token per submission |
| `internal-error` | "Internal error occurred" | "Retry the request" | The site retries; you can resubmit with a new token |

Three rules about the call itself explain the site-side codes. Siteverify is
`POST https://challenges.cloudflare.com/turnstile/v0/siteverify`, and "The API accepts both
`application/x-www-form-urlencoded` and `application/json` requests, but always returns JSON
responses", so a GET-style check copied from another CAPTCHA does not fit it. It needs `secret` and
`response`. And it should be called only from the site's backend.
[What a Cloudflare Turnstile token is](https://zerocaptcha.io/blog/cloudflare-turnstile-token) covers the full request
and reply.

## Why a token is rejected when you automate a form

### Expired or reused: timeout-or-duplicate

Cloudflare gives a token a "Validity period: 300 seconds (5 minutes) from generation" and makes
it "Single use". The server-side docs say a replayed token "will be rejected with the
`timeout-or-duplicate` error code", and that a form submitted after the 300 seconds gets the same
code. In automated code, this usually means a token was solved too early, shared between two
requests, or reused on a retry after the site refused the first attempt for another reason, such
as a wrong password.

**What you can change:** create the task when your code reaches the form, submit as soon as the
token is ready, and get a new token for every attempt. ZeroCaptcha's ready reply includes
`expiresAt`, so your code can check the time left before submitting. The patterns are in
[Cloudflare Turnstile token expiry](https://zerocaptcha.io/guides/cloudflare-turnstile-token-expiry).

### A damaged or wrong token

`invalid-input-response`, "Token is invalid, malformed, or expired", covers a token that was cut
short or altered on its way to the site. A token can be up to 2,048 characters, so a field, a
column or a URL that truncates it breaks it; so does URL-encoding it twice. The testing dummy
token `XXXX.DUMMY.TOKEN.XXXX` fails too: "Production secret keys will reject the dummy token."

Each widget has its own sitekey and secret key, and the site verifies with its widget's secret. A
token solved for a different sitekey, such as the sign-up widget's when you submit the login form,
or a sitekey your code cached before the site changed it, is not a token for this widget.
Cloudflare does not document which code that case returns; expect a failure.

**What you can change:** read the sitekey from the page at run time, from the widget inside the
form you submit ([find a Cloudflare Turnstile sitekey](https://zerocaptcha.io/guides/find-cloudflare-turnstile-sitekey)),
and pass the token through unchanged.

### Action and hostname checks on the site's side

Siteverify returns `action`, `cdata` and `hostname` with every result, and Cloudflare's best
practices tell sites to use them: "Check additional fields. Validate the action and hostname when
specified." Its own sample code does exactly that, with lines such as
`if (expectedAction && validation.action !== expectedAction) {`. A site that follows it rejects a
token whose `action` differs from the widget's, even though siteverify said `success: true`.
Siteverify returns no error code for this; the refusal is the site's.

**What you can change:** copy the widget's `data-action` (or the `action` option passed to
`turnstile.render()`) into the task's `action`, and its `data-cdata` into `cdata`. Cloudflare
limits action to 32 and cData to 255 characters, alphanumerics plus `_` and `-`.
[Cloudflare Turnstile action and cData](https://zerocaptcha.io/guides/cloudflare-turnstile-action-and-cdata) shows where
to find both. For the hostname, "Hostname where the challenge was served", use the exact URL of
the page that shows the widget as `websiteURL`, including its subdomain.

### The token never reached siteverify: the field name

`missing-input-response` means the site's server called siteverify without a token. When you
automate, the usual cause is that the token went into a field the server does not read. The
widget's input is named `cf-turnstile-response` by default, but sites can rename it
(`response-field-name`), turn it off (`response-field`) and send the token from JavaScript, or run
Turnstile in reCAPTCHA compatibility mode, where the input is `g-recaptcha-response`.

**What you can change:** submit the form once by hand with the browser's developer tools open and
copy the field name from the real request.
[Submit a Cloudflare Turnstile token](https://zerocaptcha.io/guides/submit-cloudflare-turnstile-token) covers form
fields, callbacks and JSON bodies.

### Errors that are the site's to fix

`missing-input-secret`, `invalid-input-secret` and `bad-request` come from the site's own request,
and nothing in your submission changes them. `internal-error` is Cloudflare's; its action is
"Retry the request", and sites can retry safely with an `idempotency_key`, "A UUID you generate to
safely retry validation requests". If the site shows an error after a Cloudflare problem, submit
again later with a new token.

## When the site is yours

If you run the site and your own users are refused, log the `error-codes` array from every
failed siteverify reply, and test each branch with Cloudflare's testing keys: the secret
`2x0000000000000000000000000000000AA` always fails validation, and
`3x0000000000000000000000000000000AA` returns the "token already spent" error. In our own check on
30 September 2026 (an observation, not Cloudflare's documentation), siteverify answered the dummy
token with `invalid-input-response` under the `2x` secret and `timeout-or-duplicate` under the
`3x` secret. [Test Cloudflare Turnstile in CI](https://zerocaptcha.io/guides/test-cloudflare-turnstile-in-ci) wires the
keys into a test suite.

## What a solving API can and cannot fix

A solving API fixes one thing: getting a valid token for the page when your client cannot produce
one. ZeroCaptcha returns a token for the `websiteURL`, `websiteKey`, `action` and `cdata` you send,
usable once within 300 seconds. It cannot fix a submission under the wrong field name, a token
held past `expiresAt`, or the site's own secret-key errors. Tasks that fail cost nothing; a token
you let expire was solved, so it is charged, and `getTaskResult` then answers
`ERROR_TOKEN_EXPIRED`.

Don't confuse siteverify's codes with the solving API's own codes, such as
`ERROR_CAPTCHA_UNSOLVABLE`: those are covered in
[CAPTCHA API error codes](https://zerocaptcha.io/guides/captcha-api-error-codes) and the
[errors reference](https://zerocaptcha.io/docs/reference/errors). The full solving flow is on the
[Cloudflare Turnstile solver](https://zerocaptcha.io/cloudflare-turnstile-solver) page.

## See the codes for yourself

The [Cloudflare Turnstile token checker](https://zerocaptcha.io/captcha-test/cloudflare-turnstile-token-checker) asks
siteverify about a token from any of this site's demo widgets and explains each code it returns.
Check the same token twice to see `timeout-or-duplicate`.

## Sources

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

## Questions

### What does timeout-or-duplicate mean in Cloudflare Turnstile?

Siteverify received a token that had already been validated, or one older than 300 seconds. Get a new token for every submission and submit it as soon as it is ready.

### Can I call siteverify myself to check a token before I submit it?

No. Siteverify needs the site's secret key, which only the site's server holds, and each token can be validated only once, so a check of your own would spend it.

### Why is a fresh, unused token still rejected?

Siteverify may accept it while the site refuses it: sites compare the action and hostname in the reply with what they expect, or read the token from a different field. Send the widget's action and cData with the task and submit under the field name the page uses.
