Skip to content

Cloudflare Turnstile internals: the challenge token, siteverify, and what it measures

· 24 min read
Copyright: MIT
Turnstile wordmark in monospace with an orange underline over a black background

A reCAPTCHA box asks you to find the traffic lights and you grumble and click them. Turnstile asks you nothing. You load a page, a small widget sits there for a beat, and then a green check appears. No grid of fire hydrants, no slider, often no click at all. From the outside it looks like the puzzle simply went away. It did not. The puzzle moved into the browser and stopped being something you solve with your eyes.

That swap is the whole story of Turnstile, and it raises an obvious question for anyone who has to integrate it or reason about it: if there is no visible challenge, what is the widget actually doing in those few hundred milliseconds, and how does a server on the far side decide the result is trustworthy? This post walks the full path. The widget that loads in the page, the token it produces, the siteverify call that redeems that token server-side, the class of signals Turnstile gathers in place of a picture puzzle, and the privacy posture Cloudflare built the thing around, including the part where it never touches a cookie.

A note on sourcing first. Cloudflare documents the integration surface in detail: the widget attributes, the token lifecycle, the siteverify request and response, the error codes. Those are quoted from primary docs and the launch blog. What Cloudflare does not document is the exact contents of the encrypted challenge payload, the field layout inside the token, or the weighting of individual signals. That is deliberate on their part, and where this post describes the internal mechanism rather than a documented field, it says so plainly. The protocol pieces Turnstile leans on, chiefly Privacy Pass, are specified in public RFCs, and those are cited directly.

The sections below go in the order a request travels. First the widget and the script it loads. Then the token, its shape and its lifetime. Then siteverify, the server-side redemption that is the only thing standing between a token and a trusted result. Then the harder question of what the challenge measures when there is no puzzle. Then Private Access Tokens and the cookie-free design. Then Ephemeral IDs, the fraud-detection hook added later. Then the privacy posture and where it gets argued over. Then a short, honest tour of what is and is not knowable from outside.

The widget and the script

Turnstile starts as one script tag. The page loads https://challenges.cloudflare.com/turnstile/v0/api.js, and Cloudflare is blunt about not touching it: “The api.js file must be fetched from the exact URL shown above. Proxying or caching this file will cause Turnstile to fail.” That constraint is a tell. The script is not static. It is the delivery vehicle for whatever challenge logic Cloudflare wants to run today, and pinning it to a live origin means Cloudflare can rotate that logic without your cooperation.

There are two ways to put the widget on the page. Implicit rendering drops a <div class="cf-turnstile" data-sitekey="..."> into the markup and lets the script find it. Explicit rendering loads api.js?render=explicit and calls turnstile.render() from your own code when you are ready. Either way you get a JavaScript surface with the predictable verbs: turnstile.render() to create a widget, turnstile.reset() to re-run one that “timed out or expired,” turnstile.getResponse() to read the current token, turnstile.remove() to tear the DOM down, and turnstile.execute() to run the challenge on demand.

The sitekey identifies your widget configuration. It is public by design, the way a reCAPTCHA site key always was, and it pairs with a secret key that never leaves your server. The widget configuration also picks one of three modes, and the mode is the single most consequential choice you make. Managed, which Cloudflare recommends, “selects a challenge based on the signals gathered from the visitor’s browser” and “presents an interaction only if it detects potentially automated traffic.” Non-interactive shows the widget but never asks the visitor to do anything. Invisible hides the widget entirely while the challenge “still runs in the background.” All three run the same signal collection. The mode only governs whether, and how visibly, the visitor is ever asked to click.

The round trip a token makes Browser api.js + widget Your backend holds secret key Cloudflare siteverify token + secret success:true/false 1. Widget runs the challenge in the browser and produces a token (cf-turnstile-response). 2. Token rides to your backend with the form post. The browser never sees the secret key. 3. Backend posts token + secret to siteverify. Cloudflare redeems the token, returns a verdict. 4. The token is now spent. A replay of the same token returns timeout-or-duplicate. *The four steps of a Turnstile verification. The token is generated client-side, redeemed server-side, and spent on first use.*

When the widget is inside a <form>, Turnstile injects a hidden input automatically. Its name is cf-turnstile-response, and its value is the token. Submit the form and the token rides along with everything else. That is the entire client-side contract: load a script, let it run, collect a string out of a hidden field. The interesting part is what that string is and what you are supposed to do with it.

The token

The thing the widget hands you is called the challenge token, sometimes the response token. It is a string, up to 2,048 characters long, that Cloudflare generates the moment the visitor’s browser passes the signal analysis. Two properties matter more than anything else about it, and both are short.

It expires after 300 seconds. Five minutes from generation, the token is dead, and siteverify will reject it. That window exists because the token is supposed to attest to a fresh assessment of the client, not a permanent verdict. If you sit on a token for ten minutes before redeeming it, the assessment is stale and Cloudflare wants you to run the challenge again. The turnstile.reset() method exists precisely for this case: a form the user left open past the expiry needs a new token before submission.

It can only be validated once. The first siteverify call that redeems a token consumes it. Every subsequent call with the same token comes back with success: false and the error code timeout-or-duplicate. This single-use property is the entire anti-replay story. A token stolen off the wire and replayed is worthless because the legitimate redemption already burned it, and if the attacker redeems first, the legitimate request fails and the server knows something is wrong. The same error code covers both the expired case and the already-used case, which is occasionally annoying to debug but reflects that, from the server’s point of view, both mean the same thing: this token is no good, get a new one.

What is inside the token? The honest answer is that the exact field layout is not public. The token is opaque to you and to the site integrating it. It is meaningful only to Cloudflare’s siteverify endpoint, which holds the key material to decrypt and validate it. What can be said from the documented behaviour is that the token must encode enough for Cloudflare to confirm three things at redemption time: that it issued this token, that the token has not expired, and that the token has not already been spent. The single-use property in particular implies server-side state, since a self-contained signed token cannot know on its own whether it has been seen before. Beyond that, any claim about specific bytes inside the token is inference, not documented fact, and this post will not invent a field layout for it.

That opacity is a deliberate design choice with a real consequence. Because the token means nothing without siteverify, a Turnstile integration that forgets to call siteverify is not partially secure. It is not secure at all. The presence of a token in the form proves only that some code ran in some browser and produced a string of the right shape. Cloudflare is emphatic about this in the docs, calling server-side validation mandatory and warning that “it is critical to enforce Turnstile tokens with the Siteverify API.” The most common Turnstile mistake in the wild is treating the green checkmark as the security boundary. It is not. Siteverify is.

Siteverify, the only thing that matters server-side

The redemption endpoint is https://challenges.cloudflare.com/turnstile/v0/siteverify, and you reach it with a POST. The request is small. Two parameters are required: secret, your widget’s secret key from the dashboard, and response, the token you pulled out of cf-turnstile-response. Two are optional: remoteip, the visitor’s IP address, and idempotency_key, a UUID you generate so you can safely retry the validation request without accidentally double-spending the token on a flaky network. The endpoint accepts both form-encoded and JSON bodies and always answers in JSON.

The response is where the verdict lives. A successful one looks like this, quoted from Cloudflare’s own example:

{
"success": true,
"challenge_ts": "2022-02-28T15:14:30.096Z",
"hostname": "example.com",
"error-codes": [],
"action": "login",
"cdata": "sessionid-123456789",
"metadata": {
"ephemeral_id": "x:9f78e0ed210960d7693b167e"
}
}

success is the boolean you branch on. Everything else is context you are expected to check rather than ignore. challenge_ts is the ISO 8601 timestamp of when the challenge was solved, which lets you reason about freshness beyond the blunt five-minute expiry. hostname is the hostname where the challenge was served, and verifying it matches your own site is how you stop a token minted on a different domain from being replayed against yours. action and cdata echo back values you set on the widget: action is a label like login or signup that you can assert against the operation actually being performed, and cdata is an opaque customer-data string you can use to bind the token to a session. error-codes is empty on success and carries the reason on failure.

siteverify response: the verdict and what to check it against success the boolean you branch on; false means do not trust challenge_ts when it was solved; reason about freshness hostname assert it equals your own domain action assert it equals the operation in progress cdata opaque value you set; bind it to a session metadata.ephemeral_id Enterprise: short-lived device id for fraud links *The siteverify response is not just a yes/no. The supporting fields are checks Cloudflare expects you to perform, not decoration.*

The error codes on the failure path are worth knowing by name because they map to distinct problems. missing-input-secret and invalid-input-secret are configuration faults on your side, a missing or wrong secret key. missing-input-response means you forgot to send the token. invalid-input-response means the token is malformed or expired. bad-request is a malformed request overall. internal-error is Cloudflare’s side and is the one you retry, which is exactly why idempotency_key exists. And timeout-or-duplicate, the one you will see most in normal operation, means the token already expired or was already redeemed.

A subtle and important point: a valid, successful siteverify response tells you the token was real and fresh and unspent. It does not by itself tell you the visitor was human. Turnstile’s whole verdict is folded into whether a token was issued at all and what assessment underlay it. The success: true you get back is Cloudflare saying “we issued this, it checks out,” not “we certify this user is a person.” For the harder question of how confident Turnstile was, Enterprise customers get the metadata block, and that is where the story gets more interesting. For everyone else, the security model is: trust that Cloudflare would not have issued a token to traffic it was confident was automated, and stack siteverify with your own application-level checks.

If you want the broader picture of how Cloudflare turns signals into a confidence number rather than a binary, that is a separate machine, covered in Cloudflare Bot Management scoring. Turnstile and Bot Management share signal collection but expose very different surfaces.

What the challenge measures when there is no puzzle

Here is the part everyone actually wants to understand. If Turnstile does not show a puzzle, what is it measuring? The launch blog from September 2022 gives the clearest public answer Cloudflare has offered. Turnstile “runs a series of small non-interactive JavaScript challenges” and the listed categories are “proof-of-work, proof-of-space, probing for web APIs, and various other challenges for detecting browser-quirks and human behavior.” That single sentence is the most specific public description of the challenge content, and it is worth taking apart slowly because Cloudflare has not expanded on it much since.

Proof-of-work is a small computational puzzle the browser must solve, the same family of idea as Hashcash: spend a measurable amount of CPU on something that is cheap to verify. On its own it does not prove humanity. What it does is impose a cost. A browser solving one proof-of-work to load a page does not notice. A bot farm trying to mint millions of tokens pays that cost a million times, and the economics shift. Proof-of-space is the memory-bound cousin, a puzzle that requires allocating and touching a chunk of memory, which is harder to cheaply parallelize on the kind of hardware a scraper operation runs.

Probing for web APIs is the fingerprinting half. The challenge script asks the browser questions, and a real browser answers them consistently the way that browser always does, while a headless or instrumented or faked environment answers them in ways that do not add up. The launch blog phrases this as “detecting browser-quirks.” Every browser has a personality in how it exposes and implements web platform APIs, and the gap between what a User-Agent string claims and how the runtime actually behaves is where automation falls down. A client that says Chrome but is missing a Chrome-specific quirk, or that has the WebDriver flag set, or whose JavaScript engine timing does not match the claimed platform, is exactly what this layer is built to catch. Third-party security analysis describes the widget making background POST requests to Cloudflare endpoints carrying “encrypted data in the payload that can include browser fingerprints,” covering things like the WebDriver flag and hardware configuration.

The exact contents of that encrypted payload are not public. What follows about its structure is inference from observed traffic and vendor analysis rather than documented mechanism. The widget collects a bundle of browser-environment signals, encrypts it, and posts it to Cloudflare, which assesses it server-side and decides whether to issue a token, escalate to a visible interaction, or block. Cloudflare deliberately does not publish the field list, and the rotation property of api.js means the field list is not even stable over time. That instability is the point. The blog frames the whole platform as a way to “rotate new challenges in and out as they become more or less effective.” A documented, frozen challenge is a solved challenge.

What the widget runs in place of a picture puzzle Proof-of-work CPU cost Proof-of-space memory cost Web API probing browser quirks Behavior signals interaction, timing Encrypted payload POST to Cloudflare token or interaction The exact field contents of the encrypted payload are not public, and api.js rotates, so the signal set is not even stable over time. The categories above come from the launch blog. *The four signal categories Cloudflare named publicly, feeding an encrypted payload whose exact fields it does not document.*

There is one more layer the widget does not control, and it matters even though it happens before any JavaScript runs. Turnstile sits on Cloudflare’s network, and Cloudflare reads the TLS ClientHello and the HTTP/2 frame profile of every connection. A client whose User-Agent claims Chrome but whose TLS stack fingerprints as a Python library is contradicting itself at the transport layer, and that contradiction is visible before api.js even loads. How that protocol-level fingerprinting works is its own subject, covered in how Cloudflare uses TLS and HTTP/2 fingerprints and in the wider TLS fingerprinting walk from JA3 to JA4. For Turnstile’s purposes, the relevant fact is that the JavaScript challenge does not run in a vacuum. It runs on top of a network that has already formed an opinion.

The honest assessment of what all this measures: Turnstile is good at separating a genuine, untouched consumer browser from an obviously automated or instrumented one, and it does so without a puzzle most of the time. It is not magic. A sufficiently faithful real-browser automation that carries a consistent TLS fingerprint, a real JavaScript runtime, and plausible behavior can produce tokens, which is why the recent research on harder cases has moved past generic fingerprinting toward whether the surrounding application actually finished booting. That is also why Cloudflare pairs Turnstile with the rest of its detection stack rather than treating a token as the last word.

The most distinctive design decision in Turnstile is one of absence. It uses no cookies. Cloudflare states it flatly: Turnstile “never looks for cookies (like a login cookie), or uses cookies to collect or store information of any kind.” For a CAPTCHA replacement that is unusual, since the obvious way to remember “this browser already passed” is a cookie, and reCAPTCHA leaned on Google’s ambient cookies heavily. Turnstile gives that up on purpose, and the replacement is partly a cryptographic protocol called Private Access Tokens.

Private Access Tokens are “built directly into Turnstile.” On supporting Apple platforms, macOS 13, iOS 16, iPadOS 16 and newer, the device itself can vouch for the user. Cloudflare describes the trade: instead of looking at “some session data (like headers, user agent, and browser characteristics) to validate users without challenging them,” PATs let Cloudflare “minimize data collection by asking Apple to validate the device.” The user proves they are on a genuine, non-jailbroken device without solving anything and without handing over identifying data, and the proof is cryptographic rather than behavioral.

The protocol underneath is Privacy Pass, now standardized in IETF RFCs. It splits the job across four roles, and the split is the whole privacy argument. The Origin is the site presenting the challenge. The Client is the browser or device. The Attester verifies an attribute of the client, in Turnstile’s case the ability to pass its challenge or the validity of the device, and when satisfied requests a token. The Issuer signs the token. The cryptography that makes this private is RSA Blind Signatures, standardized in RFC 9474, which lets the Issuer sign a token without seeing its final form, so the token it signed cannot later be linked back to the redemption. The challenge and redemption ride on standard HTTP authentication headers: WWW-Authenticate: PrivateToken challenge="...",token-key="..." to present a challenge, and Authorization: PrivateToken token="..." to redeem one.

Privacy Pass: knowledge is split so no party sees the whole picture Client device / browser Attester knows device, not site Issuer knows site, not device Origin the protected site RSA blind signatures (RFC 9474) keep issuance unlinkable from redemption. *The split-knowledge architecture of Privacy Pass. The Attester and Issuer each hold one half, and neither can reconstruct who visited what.*

The practical reach of PATs is limited. They work on supporting Apple platforms and require the right combination of OS and browser, so for the large share of traffic that is not on a recent Apple device with PAT support, Turnstile falls back to the JavaScript challenge and signal collection described earlier. PATs are the privacy-maximal path, not the universal one. But they shape the whole design, because once you commit to a cookie-free, PAT-capable system, you cannot lean on the ambient-cookie tricks that made older CAPTCHAs work, and you are forced into exactly the kind of ephemeral, per-request assessment Turnstile does.

This cookie-free stance is what separates Turnstile cleanly from the rest of Cloudflare’s challenge machinery. The managed challenge, the JS challenge, and the interactive challenge that protect a zone do issue a cf_clearance cookie, and that cookie is the proof of passage Cloudflare’s edge looks for. Turnstile, by default, issues no such cookie. It hands back a token and nothing more. The two worlds connect through an opt-in: with pre-clearance enabled on a Turnstile widget, “a cf_clearance cookie is issued to the visitor in addition to the default Turnstile token,” and the clearance level granted depends on the widget configuration, from no_clearance up through managed and interactive. The mechanics of that cookie, its issuance and scope, are covered in Cloudflare’s cf_clearance cookie, and the wider family of challenge types it belongs to in managed vs JS vs interactive challenges. The thing to hold onto is that Turnstile’s no-cookie default is a real architectural commitment, and the cookie only shows up when you ask for it.

Ephemeral IDs, the fraud hook added later

Turnstile launched cookie-free and proud of it, and then ran into the obvious limitation: if you remember nothing about a visitor, you cannot notice when the same actor hammers you from a thousand rotating IP addresses. In September 2024 Cloudflare addressed that with Ephemeral IDs, and the design is a careful attempt to get the fraud-detection benefit without giving up the privacy stance.

An Ephemeral ID is a short-lived identifier that “links behavior to a specific client instead of an IP address.” It is generated from the same client-side signals Turnstile already collects, with no cookies and no client-side storage involved. The ID surfaces only on the server side, in the siteverify response, under metadata.ephemeral_id, the same metadata block shown earlier with a value like x:9f78e0ed210960d7693b167e. Cloudflare’s guidance is to log it with every protected action so you can spot the case where one device, behind many IPs, is doing something it should not, the signature of credential stuffing and bulk account creation.

The privacy engineering around it is the interesting part. The IDs are “intentionally not 100% unique,” which sounds like a bug and is actually the design. A perfectly unique identifier is a tracking cookie by another name. An ID with deliberate collisions is good enough to cluster abusive behavior in aggregate while being useless for following an individual. They are also per-customer: the same visitor hitting Turnstile widgets on two different Cloudflare customers gets two different Ephemeral IDs, so the ID cannot be used to track someone across sites. And they are short-lived, changing frequently enough that they cannot follow a visitor across multiple days. Ephemeral IDs are an Enterprise feature, available to Enterprise Bot Management customers with the Turnstile add-on or to standalone Enterprise Turnstile customers, so the free-tier verdict remains the plain success boolean.

That per-customer, deliberately-fuzzy, no-cookie identifier is a different philosophy from the cross-customer telemetry other vendors build their detection on. For contrast, the way one competitor pools signals across its entire customer base is the subject of HUMAN’s collective signal network, and it is worth reading next to Turnstile precisely because it goes the other direction: maximize cross-customer linkage rather than deliberately break it.

The privacy posture, and where people argue about it

Cloudflare markets Turnstile as the privacy-respecting CAPTCHA, and the technical design backs a lot of that up. No cookies, no client-side storage, Private Access Tokens where supported, Ephemeral IDs engineered to resist cross-site tracking, and a stated goal in the launch blog of letting people prove humanity “without completing a CAPTCHA or giving up personal data.” Compared to a CAPTCHA that depends on an advertising company’s ambient cookies, that is a meaningfully different posture, and the no-cookie property in particular has a pleasant side effect: it sidesteps the cookie-consent banner question entirely, since there is no cookie to consent to.

The argument starts where the documentation stops. Turnstile still has to look at session data, the launch blog says so, “headers, user agent, and browser characteristics,” and the encrypted challenge payload’s exact contents are not published. European data protection officers, particularly in Germany, have pushed on the fact that Cloudflare does not exhaustively enumerate what the challenge collects, how long it retains it, or precisely how it is used beyond bot detection. There is also a data-residency wrinkle. Challenge signals are processed across Cloudflare’s global anycast network, which includes United States points of presence, so a European visitor’s challenge data may be assessed outside the EU even when they connected to a European edge. None of that makes Turnstile non-compliant on its own, and it can be deployed defensibly, but the gap between “no cookies, therefore private” and “the full data flow is documented and EU-resident” is real, and that gap is where the compliance conversations happen.

The fair summary is that Turnstile’s privacy claims are strongest exactly where they are most concrete, the cookie-free design and the Privacy Pass cryptography, and softest where Cloudflare keeps things opaque, the challenge payload and the signal retention. Both can be true at once. The cookie-free design is a genuine improvement on what came before, and the opacity that makes the anti-bot side effective is the same opacity that makes the privacy side hard to fully audit.

What is and is not knowable from outside

To close, a clean line between documented fact and inference, because the two get blurred constantly in writing about Turnstile.

Documented and quotable: the widget script URL and its no-proxy rule, the three widget modes, the cf-turnstile-response hidden field, the token’s 2,048-character cap and 300-second expiry and single-use property, the entire siteverify request and response surface including every field and error code, the metadata.ephemeral_id field and its privacy properties, the no-cookie design, and the Private Access Tokens and Privacy Pass integration down to the RFC for the blind-signature scheme. All of that comes from Cloudflare’s own docs and blog or from public standards.

Not documented, and where this post stayed in inference: the exact byte layout inside the challenge token, the precise field list and encoding of the encrypted challenge payload, the specific browser APIs probed on any given day, the weighting of individual signals, and how server-side state tracks single-use redemption. Cloudflare keeps these opaque on purpose, and the rotation of api.js means some of them are not even fixed long enough to document. Anyone telling you they have the definitive field list of the Turnstile payload is describing a snapshot of something built to change, not a specification.

The thing that stays constant through all the rotation is the shape of the contract, and it is smaller than the machinery suggests. A token is issued in the browser, redeemed exactly once at siteverify, and means nothing until it is. Get that one server-side call right and check the hostname and action it returns, and you have used Turnstile correctly. Skip it, and the green checkmark in the corner is decoration. The puzzle did not disappear when Turnstile removed the traffic lights. It moved to a place you cannot see, ran against signals you cannot fully enumerate, and left you with one string and one instruction: redeem it on the server, or you have verified nothing.


Sources & further reading

Frequently asked questions

Why does a Turnstile token still need siteverify if the widget already shows a green checkmark?

The token the widget produces is opaque and means nothing without server-side redemption. Its presence only proves that some code ran in a browser and emitted a string of the right shape, not that the visitor passed any assessment. Only the siteverify endpoint holds the key material to decrypt and validate the token. Cloudflare calls server-side validation mandatory, so an integration that skips siteverify is not partially secure, it is not secure at all.

How long is a Cloudflare Turnstile token valid and how many times can it be redeemed?

A Turnstile token is a string up to 2,048 characters that expires 300 seconds after it is generated, since it attests to a fresh assessment rather than a permanent verdict. It can be validated only once. The first siteverify call consumes it, and any later call with the same token returns success false with the error code timeout-or-duplicate. That single-use property is the entire anti-replay defense, because a stolen token is worthless once the legitimate redemption has burned it.

What does Turnstile actually measure when it does not show a visible puzzle?

Turnstile runs small non-interactive JavaScript challenges instead of a picture puzzle. The publicly named categories are proof-of-work, which imposes a CPU cost, proof-of-space, which imposes a memory cost, probing of web APIs to catch browser quirks that headless or faked environments get wrong, and behavior signals. These are bundled into an encrypted payload posted to Cloudflare, which assesses it server-side and decides whether to issue a token, escalate to an interaction, or block.

How does Turnstile remember that a browser passed without using any cookies?

Turnstile uses no cookies and no client-side storage by default. Where supported, it relies on Private Access Tokens built on the Privacy Pass protocol, in which an Attester verifies an attribute of the client and an Issuer signs a token. RSA Blind Signatures, standardized in RFC 9474, let the Issuer sign without seeing the final token, so issuance cannot be linked to redemption. PATs work on recent Apple platforms, and other traffic falls back to the JavaScript challenge.

What is the ephemeral_id field in the siteverify response and how does it avoid tracking users?

The metadata.ephemeral_id field is a short-lived identifier that links behavior to a specific client instead of an IP address, derived from signals Turnstile already collects with no cookies or client-side storage. It is intentionally not fully unique so it clusters abusive behavior in aggregate without following an individual, and it is per-customer and short-lived, so it cannot track someone across sites or across days. It is an Enterprise feature, useful for spotting credential stuffing and bulk account creation.

Further reading