CAPTCHA Types

Cloudflare Turnstile: The Widget, the Token, and the Thing It Is Not

Two completely different Cloudflare products get called "the Cloudflare CAPTCHA", and conflating them is the source of most wasted debugging hours in this corner of automation.

Key takeaway: Turnstile is a widget the site owner embedded in a form. It produces a short-lived, single-use token in a field named cf-turnstile-response, which the site's own backend then validates against Cloudflare's siteverify endpoint. The full-page interstitial that says "Checking your browser" is a different mechanism entirely — that is a WAF action on the zone, and it resolves into a clearance cookie, not a form field. If you are handling one as though it were the other, nothing you do will work.

What Turnstile Replaces, and Why It Looks Like Nothing

Turnstile is Cloudflare's CAPTCHA alternative, launched in 2022 and offered free to any site whether or not the site is proxied through Cloudflare. It has no image grids, no traffic lights, no distorted text. In its usual configuration the visitor sees a checkbox-sized card that ticks itself after a moment.

That absence of a puzzle is the design. Instead of asking a human to prove something, Turnstile runs a battery of non-interactive browser probes — lightweight proof-of-work, checks on standard web APIs, signals about the client environment — and decides whether to pass the visitor silently or escalate. The site owner picks the posture in the Cloudflare dashboard:

  • Managed — Cloudflare decides whether a visible interaction is required.
  • Non-interactive — a visible widget that never asks the user to click.
  • Invisible — no visible widget at all.

For anyone automating a form, the practical consequence is that there is nothing to look at and nothing to read — this is not a perception problem. That places it in the token-challenge family described in every CAPTCHA type and what solving each one means, alongside reCAPTCHA v2 and v3 rather than alongside image OCR.

The Token Lifecycle

The mechanism is small enough to hold in your head.

1. The page loads the script. Either implicitly, via https://challenges.cloudflare.com/turnstile/v0/api.js plus a <div class="cf-turnstile" data-sitekey="0x...">, or explicitly with ?render=explicit and a turnstile.render() call. The sitekey is public and sits in the page source — that is where automation reads it from.

2. The widget runs and produces a token. On success it injects a hidden input named cf-turnstile-response into the enclosing form, and fires data-callback with the token as its argument. turnstile.getResponse() returns the same value.

3. The form is submitted with that field in the body.

4. The backend validates it by POSTing to Cloudflare:

curl -X POST "https://challenges.cloudflare.com/turnstile/v0/siteverify" \
  -d "secret=YOUR_SECRET_KEY" \
  -d "response=<cf-turnstile-response value>" \
  -d "remoteip=203.0.113.10"
{
  "success": true,
  "challenge_ts": "2026-08-19T09:14:02.000Z",
  "hostname": "staging.example.com",
  "action": "signup",
  "cdata": "session-4f21",
  "error-codes": []
}

Three properties of that token drive every real-world failure:

  • It is single-use. Presenting it twice fails.
  • It expires quickly — the documented lifetime is 300 seconds. A token parked in a queue for six minutes is dead weight.
  • It is bound to context. The hostname, action, and cdata values come back in the verification response precisely so the backend can check them, and a backend that checks them will reject a token minted with the wrong ones.

The Failure Mode Everyone Hits: timeout-or-duplicate

siteverify returns machine-readable error-codes, and the ones that matter for automation are:

Code What it actually means
missing-input-response The token never reached the backend — the field was empty at submit time
invalid-input-response Malformed or not a Turnstile token at all
timeout-or-duplicate The token was already spent, or it was older than its lifetime
invalid-input-secret Server-side key problem, nothing to do with your client
internal-error Retryable on Cloudflare's side

timeout-or-duplicate is the one that eats afternoons, because it is the code you get for a perfectly valid token used badly. The two causes look identical from the outside:

  • Your pipeline obtained a token, then did other work — a page navigation, a retry loop, a queue hop — and submitted after the window closed.
  • Your retry logic replayed the same request body, including the same token, after a network blip.

The fix for the first is ordering: acquire the token as the last step before submission, not the first step of the flow. The fix for the second is to treat the token as non-idempotent state — a retry must re-acquire, never replay. If you build one retry rule for Turnstile, make it that one.

action and cdata: The Two Attributes That Silently Invalidate a Solve

The widget accepts data-action and data-cdata. Both are echoed back in the siteverify response, and both are optional for the site owner — but when the site sets them and checks them, a token that carries the wrong values is rejected even though it solved cleanly.

This matters because data-cdata frequently carries per-session state: a session ID, a form nonce, a request identifier. Read the widget's attributes off the live page at the moment you handle it, rather than hardcoding what you saw last week. A stale cdata produces a token that passes Cloudflare and fails the site, and the resulting error surfaces in the site's response, not in the solver's — so the instinct to blame the solver is exactly backwards.

The same applies to data-appearance and data-execution: with execution="execute" the widget does not run on load at all, it runs when the page's own JavaScript calls turnstile.execute(). If your automation waits for a token that the page never asked for, it waits forever.

Turnstile Is Not the Cloudflare Interstitial

Worth stating plainly, because the naming invites the mistake.

Turnstile widget Managed Challenge / interstitial
Who put it there The site developer, in a form Cloudflare WAF rules on the zone
Where it appears Inline on a page you requested Full page, before the content
What it produces cf-turnstile-response form token A cf_clearance cookie
How it is consumed POSTed with the form, checked at siteverify Sent on subsequent requests, paired with the exact user agent
Scope That one submission The session against that zone

Handling the interstitial means carrying a cookie and the matching user agent on every following request — a clearance cookie presented under a different UA is worthless, because the pairing is the point. Handling Turnstile means putting one string in one field within five minutes. Why a zone serves one or the other is the subject of how anti-bot detection actually decides you're a bot.

Automating a Form That Carries Turnstile

Assuming a legitimate target — your own staging environment, a QA suite for an application you own, an accessibility check, uptime monitoring, or data collection you are permitted to perform — the shape is the standard submit-and-poll flow described in wiring a solver into your stack, with type-specific parameters.

You submit the public sitekey scraped from the page and the page URL, plus action and cdata when the widget carries them:

curl -X POST "https://ocr.captchaai.com/in.php" \
  -d "key=YOUR_API_KEY" \
  -d "method=turnstile" \
  -d "sitekey=0xSITEKEY_FROM_THE_PAGE" \
  -d "pageurl=https://staging.example.com/signup" \
  -d "json=1"

Then poll until the task lands, and set the returned token into cf-turnstile-response. CaptchaAI publishes a Turnstile envelope of 100% in under 10 seconds; treat that as the vendor's published planning figure rather than a guarantee for any individual task, and set your own deadline with room above it.

Two implementation notes that are specific to Turnstile:

Deadline arithmetic against the 300-second window. Solve time, network, and any subsequent page work all consume the same five minutes. A solver deadline of 60 seconds plus a slow multi-step form is how a token that arrived fine still arrives late.

Assigning .value may not be enough. If you are driving a real browser, set the field and then dispatch the event the page's listener is bound to — or, better, call the page's own data-callback if it is reachable. Many modern forms only enable the submit button from inside that callback, so a form with a populated hidden field and a disabled button is a form that never submits.

On proxies, the usual answer applies: Turnstile evaluates the client environment, so solving from an egress that looks nothing like the one you submit from introduces a mismatch a strict site can act on.

Staying On the Right Side of It

Turnstile exists because form endpoints get abused. Automating past it is defensible for testing systems you own, for accessibility and monitoring work, and for permitted data collection — and it is not defensible for bulk account creation, credential stuffing, spam, or anything touching authentication or payments. Honour robots.txt, stay inside published rate limits, and read the terms of the site you are automating.

FAQ

Where do I find the Turnstile sitekey? It is public and in the page source — the data-sitekey attribute on the cf-turnstile element, or the first argument to an explicit turnstile.render() call. Read it live rather than caching it; site owners rotate keys.

Why does my token fail with timeout-or-duplicate when I only used it once? Either the token aged past its 300-second lifetime before the form was submitted, or a retry replayed the same request body. Acquire the token immediately before submission, and make retries re-acquire rather than resend.

Can I test my integration without hitting a real site? Yes. Cloudflare publishes dummy sitekeys with deterministic behaviour for exactly this — 1x00000000000000000000AA always passes, and there is a matching always-blocking key — with corresponding dummy secret keys for the server side. Wire your error paths against those before you touch anything live.

The page shows a full-screen "Checking your browser" instead of a widget. Same thing? No — that is the zone-level interstitial, and it resolves into a cf_clearance cookie that must be sent alongside the exact user agent it was issued to. Nothing about the Turnstile form-token path applies.

Closing

Turnstile is one of the simpler challenges to handle correctly and one of the easiest to handle almost correctly. Read the sitekey, action and cdata off the live page; acquire the token last; never replay it; respect the five-minute window; and remember that the full-page interstitial is a separate problem.

The Turnstile method parameters, documented error codes, and working examples in cURL, Python, Node.js, PHP, Go and the rest are at getcaptchaai.com.

Comments are disabled for this article.