glossary · cloudflare challenge types

Turnstile vs Cloudflare challenge: what actually differs

Turnstile is a widget a site owner embeds in their own HTML to gate one action, such as a form submission; a Cloudflare challenge page is a full-page interstitial that Cloudflare's network serves in place of the page you requested, before the request reaches the origin. Both run on the same Cloudflare Challenge Platform, but they are issued by different parties, sit at different points in the request path, and hand back different credentials — a one-time token you POST to siteverify, versus a cf_clearance cookie that clears later requests to the zone.

Two products, one platform

Cloudflare's own docs describe Turnstile as running on "the same underlying technology powering" the Challenge Platform, offered "anywhere on your site in a less-intrusive way". That shared engine is why the two get conflated. The deployment model is where they diverge, and the difference is not cosmetic.

A Turnstile widget does not mean the site is behind Cloudflare. The docs are explicit: Turnstile "can be embedded into any website without sending traffic through Cloudflare". An interstitial challenge page is the opposite — it can only exist on a zone proxied through Cloudflare, because Cloudflare has to intercept the request to serve it.

Turnstile widgetInterstitial challenge page
Who puts it thereThe site owner, in their own HTMLCloudflare, from a WAF custom rule, rate limiting rule, IP Access rule, Bot Fight Mode or Under Attack mode
Position in the request pathInside the page that was already deliveredIn front of it — "a gate between the visitor and your website or application"
Site must be proxied by CloudflareNoYes
Blocks the page loadNo. The visitor "reaches the destination URL and views the page"; only the gated action is heldYes. Nothing renders until the challenge resolves
Credential producedOne-time token, valid 300 secondscf_clearance cookie
Where the credential is redeemedThe site's own backend, via the siteverify APICloudflare's edge, on subsequent requests
Scope of the credentialThe single action it was issued forThe zone
Failure outcomeYour backend rejects the submissionCloudflare serves another interstitial challenge page

Who issues each

Cloudflare documents three issuance paths, and only one of them is Turnstile.

  • **WAF products** — custom rules, rate limiting rules and IP Access rules issue interstitial challenge pages. Bot Fight Mode and Super Bot Fight Mode do the same.
  • **Bot Management** — JavaScript Detections inject a snippet into HTML responses to gather client-side signals. This one does not interrupt the visitor at all; it runs on every HTML request and produces a pass/fail that WAF rules can then act on.
  • **Turnstile** — the site owner drops a sitekey into a <div> and decides for themselves what to do with the resulting token.

The practical read: a challenge page is a decision the *infrastructure* made about a request. A Turnstile token is a decision the *application* asked for about a user action. Nothing forces those two decisions to agree, and a site can run both at once.

What a token attaches to, and for how long

This is where handling actually diverges, and where most integration bugs live.

**Turnstile token.** When the widget sits inside a <form>, it writes a hidden input named cf-turnstile-response. Your server takes that value and POSTs it to https://challenges.cloudflare.com/turnstile/v0/siteverify with your secret key. The response carries success, challenge_ts, hostname, error-codes, and echoes back the action and cdata the widget was configured with — so you can confirm the token was minted for the form you think it was. Tokens expire after 300 seconds and each can be validated exactly once; replaying one returns timeout-or-duplicate.

bash
curl -s https://challenges.cloudflare.com/turnstile/v0/siteverify \
  -d "secret=$TURNSTILE_SECRET" \
  --data-urlencode "response=$CF_TURNSTILE_RESPONSE" \
  -d "remoteip=203.0.113.10"

# {"success":true,"challenge_ts":"2026-08-13T09:14:02.000Z",
#  "hostname":"staging.example.com","action":"login","cdata":""}

The token is inert until you redeem it. If your backend never calls siteverify, the widget is decoration — Cloudflare's docs call enforcing tokens with siteverify "critical", because an unverified token proves nothing.

**Challenge page clearance.** Passing an interstitial does not give you a value to submit. It gives you a cf_clearance cookie, which Cloudflare describes as storing "the proof of challenge passed" and which "is used to no longer issue a challenge if present". It is set with SameSite=None; Secure; Partitioned, is tied to the specific visitor and device it was issued to, and its lifetime is the zone's Challenge Passage setting — 30 minutes by default, with Cloudflare recommending 15 to 45. Challenge Passage does not apply to rate limiting rules.

Cloudflare states the constraint plainly: "If you receive the Challenge from one IP and solve it using another IP, the solve is not valid and you may encounter a Challenge loop." Any automation that rotates egress between receiving and clearing a challenge will loop forever, and the loop looks like a detection problem when it is a routing problem.

Telling them apart in a response

You rarely need to guess. Each leaves a distinct signature.

  • **Challenge page** — the response carries the header cf-mitigated: challenge. That header is present for every challenge page type and challenge is its only valid value. The response body is text/html regardless of what you originally requested, which is why an interstitial firing on an XHR endpoint corrupts a single-page app instead of merely delaying it.
  • **Turnstile** — the page HTML loads https://challenges.cloudflare.com/turnstile/v0/api.js, contains a div.cf-turnstile carrying a public data-sitekey, and (inside a form) grows a hidden cf-turnstile-response input. The sitekey is public by design; the secret key never leaves your server.
js
fetch("/my-api-endpoint").then((response) => {
  if (response.headers.get("cf-mitigated") === "challenge") {
    // Interstitial challenge, not your API's response body.
  } else {
    // Process response as usual.
  }
});

One more note on the api.js URL: Cloudflare requires it be fetched from that exact origin. "Proxying or caching this file will cause Turnstile to fail when future updates are released." Mirroring it into your own bundle is a slow-motion outage.

Where the line genuinely blurs: pre-clearance

Pre-clearance is the one feature that crosses the boundary, and it is worth knowing about precisely because it breaks the tidy split above.

With pre-clearance enabled on a widget, Turnstile issues a cf_clearance cookie *in addition to* the usual one-time token. A visitor who passes the embedded widget is then exempt from WAF challenges on the same zone for the Challenge Passage duration. The clearance level is set at widget creation or modification through the Turnstile API's clearance_level field, with values interactive, managed, jschallenge and no_clearance — higher levels bypass more challenge types.

The requirement is that "the hostname of the Turnstile widget matches the zone with the WAF rules". A widget on a domain that is not proxied through Cloudflare cannot pre-clear anything, because there is no edge in the path to honour the cookie. Cloudflare recommends this pattern for API endpoints and SPAs specifically because interstitials cannot interrupt an XHR without breaking it.

"Managed" and "non-interactive" mean two different things

A large share of the confusion in this space is one overloaded vocabulary. The same two adjectives name a Turnstile widget mode and an interstitial challenge type, and they are not the same setting.

TermAs a Turnstile widget modeAs an interstitial challenge type
Managed"Automatically chooses between non-interactive or checkbox challenge based on visitor risk level." Cloudflare's recommended widget mode."Cloudflare dynamically chooses the appropriate type of challenge served to the visitor based on the characteristics of a request." Cloudflare's recommended WAF rule action.
Non-interactiveShows a widget with a loading spinner; visitors "will never be required or prompted to interact with the widget".A full-page interstitial that runs injected JavaScript and needs no interaction. The visitor waits — "typically takes less than five seconds".
InvisibleRuns "completely in the background with no visible widget or loading indicators". Sites using it must reference Cloudflare's Turnstile Privacy Addendum.No equivalent. An interstitial is by definition visible; it replaces the page.
InteractiveNot a separate mode — Managed shows a checkbox when the risk signals call for it.A challenge page that requires interaction to pass. Cloudflare recommends Managed instead.

So "we use a managed challenge" is ambiguous on its own. Ask whether it renders in the page or in place of the page. That single question resolves it.

Why they need different handling in automation

If you are automating against properties you own or are authorised to test — QA suites, CI, uptime probes, staging environments, verifying your own anti-bot configuration actually fires — the two shapes need different code paths.

**Turnstile** is a value problem. You know the sitekey (it is in the page) and the page URL. You need a token, and then you place it wherever your own application expects it: the cf-turnstile-response field, a JSON body, a header. Nothing is set at the network layer, and the token is dead after 300 seconds or one redemption, whichever comes first.

**A challenge page** is a session problem. There is no field to fill. The clearance rides on subsequent requests to the zone, so the thing that receives the challenge and the thing that makes the follow-up request have to look like the same client — same egress IP above all, per Cloudflare's own constraint.

SolveGate covers both shapes behind one endpoint. POST /v1/solve takes a gate of turnstile or waf, plus sitekey and url, authenticated with a bearer secret key. Tokens typically come back in under 1.5 seconds. GET /v1/solve/{id} polls an async solve and is never billed.

bash
# Turnstile widget on a staging login form you own.
curl -s https://api.solvegate.io/v1/solve \
  -H "Authorization: Bearer $SOLVEGATE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "gate": "turnstile",
        "sitekey": "0x4AAAAAAABbbbCCCCddddEE",
        "url": "https://staging.example.com/login",
        "action": "login"
      }'
js
import { SolveGate } from "solvegate";

const sg = new SolveGate(process.env.SOLVEGATE_KEY);

// Interstitial challenge page in front of a host you control.
const solve = await sg.solve({
  gate: "waf",
  sitekey: "0x4AAAAAAABbbbCCCCddddEE",
  url: "https://staging.example.com/",
  proxy: process.env.EGRESS_PROXY, // keep egress stable across the exchange
});

console.log(solve.token, solve.solve_ms, solve.billed);

Pass the same egress proxy to the solve that your follow-up requests will use. Splitting them across two IPs is the challenge-loop failure Cloudflare documents, not a solver defect.

For wiring either path up before you point it at anything real, Cloudflare publishes dummy keys that work on every domain including localhost: sitekey 1x00000000000000000000AA always passes, 2x00000000000000000000AB always fails, 3x00000000000000000000FF forces an interactive challenge; secret 1x0000000000000000000000000000000AA always passes validation and 3x0000000000000000000000000000000AA returns a token-already-spent error. SolveGate's own sk_test_ keys return a deterministic SANDBOX.-prefixed response so you can exercise your error handling without spending anything.

Pricing is prepaid credits: $0.40 per 1,000 solves, down to $0.075 per 1,000 at volume. Failed solves are never billed. The first 1,000 solves are free. SDKs are published as solvegate on npm (Node 18+) and PyPI (Python 3.9+).

Common questions

No. Under Attack mode issues interstitial challenge pages from Cloudflare's edge, in front of your site. Turnstile is a widget you place inside a page you already serve. They share the Challenge Platform underneath, but one gates the whole request and the other gates a single action.

No. Cloudflare states Turnstile "can be embedded into any website without sending traffic through Cloudflare". An interstitial challenge page is the reverse — it requires a proxied zone, because Cloudflare has to intercept the request to serve one.

300 seconds (5 minutes), and each token can be validated exactly once against the siteverify API. Validating the same token twice returns the timeout-or-duplicate error code.

Check for the cf-mitigated response header. It is present on every challenge page type and its only valid value is challenge. The body will be text/html regardless of what you requested, so treat that header as authoritative rather than parsing HTML.

The most common structural cause is an IP change mid-exchange. Cloudflare documents it directly: "If you receive the Challenge from one IP and solve it using another IP, the solve is not valid and you may encounter a Challenge loop." Blocked cookies, disabled JavaScript and non-HTTPS origins produce the same symptom.

Yes, with pre-clearance enabled. The widget then issues a cf_clearance cookie alongside its token, exempting the visitor from WAF challenges on the same zone for the Challenge Passage duration. It requires the widget's hostname to match the zone carrying the WAF rules.

Related

More in Glossary

Automating a gate you own or are authorised to test?

// SolveGate clears Cloudflare Turnstile and WAF challenges through one REST call · first 1,000 solves free