Cloudflare Turnstile
Challenge service
Also known as: Turnstile, siteverify
A token verifier rather than a gatekeeper. Its API always answers 200, so the verdict lives in a JSON body and never in the status code.
Turnstile is different in kind from everything else in this glossary. It does not sit in the request path and refuse traffic. It issues a token to a client, and your server exchanges that token for a verdict by calling the siteverify API. The enforcement decision is yours.
The single most important operational fact is that siteverify always returns HTTP 200. Success and failure are both 200 responses; the difference is the success boolean and the error-codes array in the JSON body. Any integration that checks response.ok and proceeds is accepting every failed and forged token it is handed.
Cloudflare is blunt about this: it warns that it is critical to enforce Turnstile tokens with the siteverify API, and that not verifying the token will leave major vulnerabilities in your implementation. Skipping verification also produces zeroes in Turnstile analytics, which is the usual way people discover they never wired it up.
Tokens are single use and valid for five minutes. Both constraints produce the same error code, which is a frequent source of confusion in support threads.
How to identify it
- A Turnstile widget on the page, rendering a cf-turnstile element.
- A cf-turnstile-response field submitted with a form or JSON payload.
- Server-side calls to challenges.cloudflare.com/turnstile/v0/siteverify.
- With pre-clearance enabled, a cf_clearance cookie issued by the widget in addition to the token.
Status codes
200 siteverify success
Allowed. Documented by the vendor.
The token was valid. The body carries success true along with the hostname and timestamp of the challenge.
What triggers it
- A first-time submission of an unexpired, unredeemed token.
How to confirm it
- A JSON body with success set to true.
What to do about it
- Check the success field, never the status code. This is the entire lesson of this entry.
- Validate the hostname in the response against your own, so a token minted for another site cannot be replayed against yours.
POST https://challenges.cloudflare.com/turnstile/v0/siteverify
HTTP/2 200
content-type: application/json
{"success": true, "challenge_ts": "2026-08-28T12:00:00.000Z",
"hostname": "example.com", "error-codes": []}
200 siteverify failure
Blocked. Documented by the vendor.
The token was rejected. Still an HTTP 200; the body carries success false and an error-codes array naming the reason.
What triggers it
- timeout-or-duplicate: the token was already validated, or the five-minute validity window elapsed.
- invalid-input-response: the token is invalid, malformed, or expired.
- missing-input-response: no token was included in the request.
- invalid-input-secret or missing-input-secret: a problem with your secret key rather than with the visitor.
- bad-request: the request itself is malformed.
- internal-error: a transient fault, and the one case Cloudflare says to retry.
How to confirm it
- A JSON body with success false and a populated error-codes array.
What to do about it
- Read error-codes and branch. The secret-key errors are your bug, timeout-or-duplicate is usually a real user submitting late or a double submit, and internal-error is the only one worth retrying.
- timeout-or-duplicate on a token that feels fresh is nearly always a double submission, or a page left open for more than five minutes.
- When a genuine user hits it, reset the widget with turnstile.reset so they get a new token rather than a dead end.
HTTP/2 200
content-type: application/json
{"success": false, "error-codes": ["timeout-or-duplicate"]}
If you run a site behind it
- Verify server-side, every time. A widget with no siteverify call is decoration.
- Tokens are single use. If your form retries a submission, or your front end submits twice, the second attempt fails legitimately and your users will report it as a bug in your site.
- Pre-clearance is the answer to challenge pages breaking fetch and XHR calls: verify once on an HTML page, issue cf_clearance, and let subsequent API requests through without an interstitial. Clearance levels run interactive, managed, then non-interactive, and a higher level satisfies the levels below it.
If your traffic is being caught by it
- If you consume an API that uses Turnstile, remember that a 200 from siteverify says nothing on its own.
- The five-minute window is short. A slow form, a distracted user, or a long validation round trip can all expire a token that was genuinely honest.