Cloudflare Turnstile is the fastest-growing CAPTCHA alternative. To solve it with CaptchaAI, you need the sitekey and page URL. This guide covers every way to find the sitekey — from simple DOM queries to intercepting JavaScript render calls.
Sitekey extraction priority order
flowchart TD
A[Need a Turnstile sitekey] --> B{Page source has<br/>data-sitekey attribute?}
B -- Yes --> M1[Method 1: DOM attribute<br/>fastest, no JS needed]
B -- No --> C{JS contains<br/>turnstile.render call?}
C -- Yes --> M2[Method 2: JavaScript render<br/>regex over rendered HTML or bundle]
C -- No, widget already mounted --> D{Iframe src on the page<br/>matches challenges.cloudflare.com?}
D -- Yes --> M3[Method 3: Iframe src<br/>parse the k= query parameter]
D -- No --> X[Re-load the page with<br/>a headless browser and retry]
M1 --> S[Use sitekey + pageurl with<br/>CaptchaAI method=turnstile]
M2 --> S
M3 --> S
For any given page, at least one of these three methods will work. The DOM attribute method is the cheapest and should always be tried first. Iframe src is the most reliable fallback because by the time the widget has rendered, the sitekey is guaranteed to be in the URL.
Where Turnstile sitekeys live
Turnstile sitekeys appear in three places:
- The
data-sitekeyattribute on.cf-turnstileelements - The
turnstile.render()JavaScript call - The Turnstile iframe
srcURL
Method 1: DOM attribute
// Browser console
document.querySelectorAll('.cf-turnstile').forEach((el, i) => {
console.log(`Turnstile ${i}:`, {
sitekey: el.getAttribute('data-sitekey'),
action: el.getAttribute('data-action'),
cData: el.getAttribute('data-cdata'),
theme: el.getAttribute('data-theme'),
});
});
Python (static HTML)
import re
import requests
html = requests.get("https://example.com/login").text
matches = re.findall(
r'class=["\'][^"\']*cf-turnstile[^"\']*["\'][^>]*data-sitekey=["\']([^"\']+)',
html
)
for sk in matches:
print(f"Sitekey: {sk}")
Python (Selenium)
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get("https://example.com/login")
widgets = driver.find_elements(By.CSS_SELECTOR, ".cf-turnstile")
for w in widgets:
sitekey = w.get_attribute("data-sitekey")
action = w.get_attribute("data-action")
print(f"Sitekey: {sitekey}, Action: {action}")
Method 2: JavaScript render call
Some sites render Turnstile programmatically:
turnstile.render('#captcha-container', {
sitekey: '0x4AAAAAAAB...',
callback: function(token) {
document.getElementById('cf-token').value = token;
},
});
Extract from page source:
# Find turnstile.render calls
render_match = re.search(
r'turnstile\.render\s*\([^,]*,\s*\{([^}]+)\}',
html
)
if render_match:
config = render_match.group(1)
sk = re.search(r'sitekey\s*:\s*["\']([^"\']+)', config)
if sk:
print(f"Sitekey from render: {sk.group(1)}")
Puppeteer interception
// Intercept turnstile.render before page loads
await page.evaluateOnNewDocument(() => {
window.__turnstileParams = [];
const origRender = window.turnstile?.render;
Object.defineProperty(window, 'turnstile', {
set(val) {
this._turnstile = val;
const orig = val.render;
val.render = function(container, params) {
window.__turnstileParams.push(params);
console.log('Turnstile render:', JSON.stringify(params));
return orig.apply(this, arguments);
};
},
get() { return this._turnstile; }
});
});
await page.goto('https://example.com/login', { waitUntil: 'networkidle2' });
const params = await page.evaluate(() => window.__turnstileParams);
console.log('Captured Turnstile params:', params);
Method 3: Iframe src
Turnstile renders an iframe. The sitekey is in its src:
document.querySelectorAll('iframe').forEach(iframe => {
if (iframe.src.includes('challenges.cloudflare.com')) {
console.log('Turnstile iframe:', iframe.src);
const match = iframe.src.match(/sitekey=([A-Za-z0-9_-]+)/);
if (match) console.log('Sitekey:', match[1]);
}
});
Solving Turnstile with CaptchaAI
Python
import requests
import time
API_KEY = "YOUR_API_KEY"
SITEKEY = "0x4AAAAAAAB..."
PAGE_URL = "https://example.com/login"
# Submit
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": "turnstile",
"sitekey": SITEKEY,
"pageurl": PAGE_URL,
"json": "1",
}).json()
if resp["status"] != 1:
raise Exception(f"Submit error: {resp['request']}")
task_id = resp["request"]
# Poll
for _ in range(24):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": task_id, "json": "1"
}).json()
if result["status"] == 1:
token = result["request"]
print(f"Turnstile token: {token[:50]}...")
break
if result["request"] != "CAPCHA_NOT_READY":
raise Exception(f"Error: {result['request']}")
JavaScript
const axios = require('axios');
const submit = await axios.post('https://ocr.captchaai.com/in.php', null, {
params: {
key: 'YOUR_API_KEY',
method: 'turnstile',
sitekey: '0x4AAAAAAAB...',
pageurl: 'https://example.com/login',
json: 1,
}
});
const taskId = submit.data.request;
// Poll for result
let token = null;
for (let i = 0; i < 24; i++) {
await new Promise(r => setTimeout(r, 5000));
const poll = await axios.get('https://ocr.captchaai.com/res.php', {
params: { key: 'YOUR_API_KEY', action: 'get', id: taskId, json: 1 }
});
if (poll.data.status === 1) {
token = poll.data.request;
break;
}
}
console.log(`Token: ${token.substring(0, 50)}...`);
Token injection
Turnstile stores its token in a hidden input named cf-turnstile-response:
# Selenium
driver.execute_script("""
const input = document.querySelector('input[name="cf-turnstile-response"]');
if (input) input.value = arguments[0];
// Also set in the Turnstile widget's callback
const widget = document.querySelector('.cf-turnstile');
const callbackName = widget?.getAttribute('data-callback');
if (callbackName && typeof window[callbackName] === 'function') {
window[callbackName](arguments[0]);
}
""", token)
Troubleshooting
| Problem | Cause | Fix |
|---|---|---|
No .cf-turnstile element found |
Rendered dynamically | Wait for page load or use MutationObserver |
| Sitekey empty | Set via JavaScript API | Search for turnstile.render in scripts |
| Token rejected | Wrong sitekey or page URL | Double-check both values match the target site |
method parameter wrong |
Using userrecaptcha for Turnstile |
Use method=turnstile |
FAQ
Is Turnstile harder to solve than reCAPTCHA?
No. CaptchaAI handles both. Turnstile typically solves in 10-25 seconds, comparable to reCAPTCHA v2.
Does Turnstile have an invisible mode?
Turnstile has "managed" and "non-interactive" modes that don't show a visible widget. The sitekey extraction methods work the same.
Related articles in this series
Part of the Turnstile Mastery series — nine practical guides covering Turnstile end-to-end:
- Cloudflare Turnstile Widget Modes Explained
- Cloudflare Turnstile Implementation Detection
- Cloudflare Turnstile Sitekey Extraction — you are here
- Cloudflare Turnstile Interception Methods
- Cloudflare Turnstile CaptchaAI Solving Guide
- Cloudflare Turnstile Token Handoff with CaptchaAI
- Cloudflare Turnstile Token Expiration and Timing
- Cloudflare Turnstile 403 After Token Fix
- Cloudflare Turnstile Errors and Troubleshooting