How to Detect Cloudflare Turnstile Parameters: sitekey, cData, action
Advanced Bot Mitigation Engineer
TL;DR:
- Cloudflare Turnstile is configured by a handful of parameters, and they are readable from the page. The
sitekeyis always present;action,cData, and the callback are set by the site when it uses them. - Implicit-mode widgets carry the parameters as
data-*attributes. Readdata-sitekey,data-action,data-cdata, anddata-callbackstraight off the.cf-turnstileelement. - Explicit-mode widgets pass the parameters to
turnstile.render(). Hook that call before the page runs it and you capture the full options object —sitekey,action,cData, andchlPageData. - In a live run the widget exposed
sitekey0x4AAAAAAAW9zqSPAlyOSMfOand acallback. The render hook stayed empty because that page uses implicit mode — the parameters were in the DOM instead. - Do this in a real browser. The parameters only exist once the widget script has run, so read them from a rendered page, not raw HTML.
- Free to start. New Scrapeless accounts include free Scraping Browser runtime — sign up at app.scrapeless.com.
Introduction: the parameters that configure a Turnstile widget
Cloudflare Turnstile is the widget a site embeds to verify a visitor is human — the successor to a checkbox CAPTCHA. When it loads, it is configured by a small set of parameters: a public site key that identifies the widget, an optional action label that names the protected event, an optional cData payload the site attaches, and a callback that receives the token once the widget passes. Knowing how to read those parameters off a page is the first step to understanding, testing, or automating any Turnstile-protected flow.
There are two ways a site wires Turnstile up, and each exposes the parameters differently. This guide covers both — reading them from the DOM in implicit mode, and capturing them from the turnstile.render() call in explicit mode — with a live run against a real Turnstile widget.
The parameters, and what each one is
| Parameter | Attribute / option | What it is |
|---|---|---|
| Site key | data-sitekey / sitekey |
Public key identifying the widget; always present (format 0x…). |
| Action | data-action / action |
Optional label naming the protected event (e.g. login). |
| cData | data-cdata / cData |
Optional customer data string the site attaches to the challenge. |
| Callback | data-callback / callback |
The function that receives the token when the widget passes. |
| chlPageData | chlPageData |
Page data passed in explicit renders on some managed flows. |
The site key is public by design — it's meant to be in the page. The action and cData values exist only when the site chooses to set them.
Why Scrapeless Scraping Browser
Scrapeless Scraping Browser is a customizable, anti-detection cloud browser designed for web crawlers and AI agents. For reading widget parameters specifically, it brings:
- Real cloud-side rendering — the widget script actually runs, so the parameters exist to read.
- A standard Puppeteer connection —
puppeteer.connect()returns an ordinaryBrowser; you inspect the page normally. evaluateOnNewDocumentsupport — inject a hook before the page's own scripts run, which is what explicit-mode capture needs.- Residential proxies in 195+ countries — reach pages that only serve their real widget to clean egress.
Get your API key on the free plan at app.scrapeless.com.
Prerequisites
- Node.js 18 or newer
- A Scrapeless account and API key — sign up at app.scrapeless.com
- Basic familiarity with Puppeteer and the DOM
Install
bash
npm install puppeteer-core
bash
export SCRAPELESS_API_KEY="your_api_token_here"
Implicit mode: read the parameters from the DOM
The common case is an implicit widget: a <div class="cf-turnstile" data-sitekey="…"> element that Turnstile auto-renders. The parameters are the element's data-* custom attributes, so read them once the widget has mounted:
javascript
import puppeteer from 'puppeteer-core';
const params = new URLSearchParams({
token: process.env.SCRAPELESS_API_KEY,
sessionTTL: '180',
proxyCountry: 'US',
});
const browser = await puppeteer.connect({
browserWSEndpoint: `wss://browser.scrapeless.com/api/v2/browser?${params}`,
});
const page = await browser.newPage();
await page.goto('https://www.scrapingcourse.com/login/cf-turnstile', {
waitUntil: 'domcontentloaded',
timeout: 60000,
});
await new Promise((r) => setTimeout(r, 6000)); // let the widget script mount
const widget = await page.evaluate(() => {
const el = document.querySelector('.cf-turnstile, [data-sitekey]');
if (!el) return null;
const attrs = {};
for (const a of el.attributes) attrs[a.name] = a.value;
return attrs;
});
console.log(widget);
// {
// id: "waf",
// class: "cf-turnstile",
// "data-sitekey": "0x4AAAAAAAW9zqSPAlyOSMfO",
// "data-callback": "javascriptCallback"
// }
await browser.close();
In a live run this returned the widget's data-sitekey (0x4AAAAAAAW9zqSPAlyOSMfO) and its data-callback. This particular page does not set data-action or data-cdata — those keys simply aren't present, which is normal: they show up only when the site configures them, and you read them the same way when they do.
Get your API key on the free plan: app.scrapeless.com
Explicit mode: capture the parameters from turnstile.render()
When a site renders Turnstile explicitly, it calls turnstile.render(container, options) in JavaScript, and the parameters live in that options object — not in the DOM. To read them, hook turnstile.render before the page's own script runs, using evaluateOnNewDocument and a property setter via Object.defineProperty:
javascript
await page.evaluateOnNewDocument(() => {
window.__tsParams = [];
Object.defineProperty(window, 'turnstile', {
configurable: true,
get() { return this.__ts; },
set(v) {
this.__ts = v;
if (v && v.render && !v.__wrapped) {
const orig = v.render.bind(v);
v.render = (el, opts) => { window.__tsParams.push(opts || {}); return orig(el, opts); };
v.__wrapped = true;
}
},
});
});
await page.goto('https://example.com/turnstile-page', { waitUntil: 'domcontentloaded' });
await new Promise((r) => setTimeout(r, 7000));
const captured = await page.evaluate(() => window.__tsParams);
console.log(captured);
// explicit render → [{ sitekey: "0x…", action: "login", cData: "…", callback: [Function] }]
The hook wraps render the moment the site assigns window.turnstile, so every explicit render is captured with its full options — sitekey, action, cData, and chlPageData when present. On an implicit-mode page this array stays empty, because the site never calls render() itself — a reliable signal that you should read the parameters from the DOM attributes instead. Run both and use whichever one is populated.
Notes
- The site key is public; treat
actionandcDataas optional. Guard for their absence rather than assuming every widget sets them. - Read from a rendered page, not raw HTML. In explicit mode the parameters never appear in the served markup — they exist only after the widget script runs, which is why a real browser is required.
- Prefer a durable selector.
.cf-turnstileand[data-sitekey]are stable anchors; the surrounding markup is not. - The token is a separate step. Reading the parameters is not the same as passing the widget — the
cf-turnstile-responsetoken is produced by the callback after the widget validates, and is verified server-side per Cloudflare's Turnstile server-side validation docs.
Conclusion: the widget's configuration, read from the page
Turnstile's parameters are not hidden — the site key is meant to be public, and the action and cData values are readable wherever the site sets them. Read them from the data-* attributes in implicit mode, or hook turnstile.render() to capture the options object in explicit mode, and run both so whichever the page uses is covered. For the mechanics of driving the cloud browser, the Scraping Browser docs cover the full flow, and the anti-bot side is unpacked in the guide on clearing Cloudflare challenges; the docs cover the rest.
Ready to Build Your AI-Powered Data Pipeline?
Join our community to claim a free plan and connect with developers working on anti-bot flows: Discord · Telegram.
Sign up at app.scrapeless.com for free Scraping Browser runtime, and see pricing for scale.
FAQ
Q: What parameters does a Turnstile widget have?
A public sitekey (always present, format 0x…), an optional action label, an optional cData string, and a callback that receives the token. Explicit renders may also pass chlPageData.
Q: Where do I find the site key?
On the widget element as data-sitekey in implicit mode, or in the options passed to turnstile.render() in explicit mode. In a live run it read 0x4AAAAAAAW9zqSPAlyOSMfO.
Q: Why is data-action or data-cdata missing?
Because the site didn't set them — both are optional. When a site uses them, they appear as data-action / data-cdata (implicit) or action / cData (explicit), read the same way.
Q: Why do I need a real browser instead of fetching the HTML?
In explicit mode the parameters are passed in JavaScript and never appear in the served markup; they exist only after the widget script runs. A cloud browser renders the page so the values are present.
Q: Does reading the parameters pass the challenge?
No. The parameters configure the widget; the cf-turnstile-response token is produced separately by the callback after the widget validates, and is checked server-side.
At Scrapeless, we only access publicly available data while strictly complying with applicable laws, regulations, and website privacy policies. The content in this blog is for demonstration purposes only and does not involve any illegal or infringing activities. We make no guarantees and disclaim all liability for the use of information from this blog or third-party links. Before engaging in any scraping activities, consult your legal advisor and review the target website's terms of service or obtain the necessary permissions.



