Cloudflare Turnstile is a CAPTCHA alternative that runs invisible or widget-based challenges. It returns a cf-turnstile-response token — the same concept as a g-recaptcha-response but for Cloudflare's system. This tutorial covers how to solve Turnstile with a solver API and inject the token automatically.
For background on Cloudflare's protection types, see the Cloudflare CAPTCHA Guide.
Prerequisites
pip install requests playwright
playwright install chromium
Cloudflare Turnstile vs Cloudflare Challenge
| Type | What it is | How to solve |
|---|---|---|
| Turnstile | Embeds in a page form | Solver API + token injection |
| Managed Challenge | Full-page JS challenge | Requires full browser + CF solver or special mode |
| JS Challenge | Full-page JS challenge | Requires cf_clearance cookie approach |
This tutorial covers Turnstile (embedded widget). For the managed challenge, see the Cloudflare CAPTCHA Guide.
Step 1 — Extract the Turnstile Site Key
Turnstile site keys start with 0x4A (production) or 1x0000000000000000000000000000000AA (test/demo).
import re
import requests
def extract_turnstile_sitekey(page_url: str) -> str:
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"}
html = requests.get(page_url, headers=headers, timeout=15).text
patterns = [
r'data-sitekey=["\']([0-9A-Za-z_\-]{25,})["\']',
r'"sitekey"\s*:\s*"([0-9A-Za-z_\-]{25,})"',
r'turnstile\.render\s*\(\s*[^,]+,\s*\{[^}]*sitekey\s*:\s*["\']([^"\']+)["\']',
]
for p in patterns:
m = re.search(p, html)
if m:
return m.group(1)
raise ValueError(f"Turnstile site key not found on {page_url}")
Step 2 — Solve Turnstile with CaptchaAI
import time
def solve_turnstile(
api_key: str,
page_url: str,
site_key: str,
) -> str:
"""Submit Turnstile task to CaptchaAI. Returns cf-turnstile-response token."""
payload = {
"key": api_key,
"method": "turnstile",
"sitekey": site_key,
"pageurl": page_url,
"json": 1,
}
r = requests.post("https://ocr.captchaai.com/in.php", data=payload, timeout=30)
r.raise_for_status()
data = r.json()
if data.get("status") != 1:
raise RuntimeError(f"Turnstile submit failed: {data}")
task_id = data["request"]
# Turnstile typically resolves in 5–15s
time.sleep(5)
for _ in range(24):
r = requests.get(
"https://ocr.captchaai.com/res.php",
params={"key": api_key, "action": "get", "id": task_id, "json": 1},
timeout=30,
)
data = r.json()
if data.get("status") == 1:
return data["request"] # cf-turnstile-response token
if data.get("request") not in ("CAPCHA_NOT_READY", "CAPTCHA_NOT_READY"):
raise RuntimeError(f"Unexpected response: {data}")
time.sleep(5)
raise TimeoutError("Turnstile solve timed out")
Step 3 — Inject and Submit with Playwright
from playwright.sync_api import sync_playwright
def submit_turnstile_form(page_url: str, api_key: str) -> None:
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto(page_url, wait_until="networkidle")
site_key = page.get_attribute("[data-sitekey]", "data-sitekey")
if not site_key:
raise ValueError("Turnstile site key not found on live page")
token = solve_turnstile(api_key, page_url, site_key)
# Inject cf-turnstile-response
page.evaluate(f"""
// Standard hidden input injection
const field = document.querySelector('[name="cf-turnstile-response"]');
if (field) {{ field.value = '{token}'; }}
// Override Turnstile's render callback to fire with the solved token
if (window.turnstile) {{
window.turnstile.getResponse = () => '{token}';
}}
""")
page.click('button[type="submit"]')
page.wait_for_load_state("networkidle")
browser.close()
With requests (no browser)
def submit_form_direct(
form_url: str,
api_key: str,
site_key: str,
page_url: str,
extra_fields: dict,
) -> requests.Response:
token = solve_turnstile(api_key, page_url, site_key)
payload = {**extra_fields, "cf-turnstile-response": token}
r = requests.post(form_url, data=payload, timeout=30)
return r
Turnstile "Action" and "CData" Parameters
Some Turnstile deployments use action and cdata (customer data) parameters that are validated server-side. Passing incorrect values causes token rejection even if the challenge is solved.
# Extended payload with action and cdata
payload = {
"key": api_key,
"method": "turnstile",
"sitekey": site_key,
"pageurl": page_url,
"action": "login", # match the site's Turnstile action if set
"data": "customer_data", # match the site's cdata if set
"json": 1,
}
Extract from page source:
action_m = re.search(r'data-action=["\']([^"\']+)["\']', html)
cdata_m = re.search(r'data-cdata=["\']([^"\']+)["\']', html)
action = action_m.group(1) if action_m else None
cdata = cdata_m.group(1) if cdata_m else None
Common Errors
| Error | Cause | Fix |
|---|---|---|
ERROR_WRONG_CAPTCHA_ID |
Wrong method | Use method=turnstile, not userrecaptcha or hcaptcha |
| Token rejected at target | Wrong action or cdata |
Extract these from the live page and pass to solver |
| Token expired | > 300s since solve | Solve immediately before submit |
400 Bad Request at target |
Token not injected | Check field name — may be cf-turnstile-response or custom |
| Site uses Managed Challenge | Not a Turnstile widget | Need full browser CF challenge solver |
Related Guides
- Cloudflare CAPTCHA Guide — all Cloudflare challenge types explained
- Best Cloudflare Challenge Solver — solver comparison for Turnstile + Managed Challenge
- CAPTCHA Solving in Python: Quick Start — multi-type overview
Production Readiness Notes
Use How to Solve Cloudflare Turnstile in Python as a decision and implementation aid, not just as a one-time reference. The practical test for how to solve cloudflare turnstile python is whether the same approach behaves reliably when traffic is messy: rotating sessions, expired tokens, changing widget parameters, intermittent solver delays, and target pages that refresh without warning. For Automation developer, the safest rollout is to start with a narrow fixture, record every submitted task, and compare the solver response with the browser state that finally submits the form. That makes failures explainable instead of mysterious, especially when a target alternates between visible challenges, invisible checks, and server-side verification.
Evaluation Criteria
A how-to should be exercised against staging first, then promoted with feature flags so failed solves can fall back without blocking the entire workflow. For Cloudflare challenge work, the most useful scorecard combines technical acceptance with operational cost. A low nominal price is not enough if retries double the real cost per accepted token, and a fast median solve time is not enough if p95 latency stalls the queue. Track these criteria before you standardize the workflow:
- The challenge subtype, sitekey, action, rqdata, blob, captchaId, or page URL used for each task.
- Median and p95 solve time, separated by provider and target domain.
- Accepted-token rate on the target page, not just successful API responses.
- Retry count, timeout count, zero-balance incidents, and invalid-parameter errors.
- The exact browser, proxy region, and user-agent that submitted the solved token.
Rollout Checklist
Before this guidance moves into a production job, build a small acceptance suite around the pages that matter most. Run it with a fixed browser profile, then repeat with the proxy and concurrency settings you expect in production. Keep the first release conservative: bounded polling, clear timeout handling, and a fallback path when the solver cannot return a usable answer. For Cloudflare challenge, separate Turnstile widgets from managed challenges, track clearance cookies, and confirm that the success selector proves access to the protected page. That checklist keeps the article useful after the first copy-paste, because the integration is judged by end-to-end completion rather than by whether a code sample returned a string.
Monitoring Signals
Healthy CAPTCHA automation is observable. Log the task id, provider, challenge type, target host, queue time, solve time, final submit status, and normalized error code for every attempt. Review those logs in daily batches at first, then move to alerts once the baseline is stable. Sudden drops usually come from target-side changes: a new sitekey, a changed action name, a stricter hostname check, an added managed challenge, or a proxy pool that no longer matches the expected geography. When you can see those shifts quickly, provider switching becomes a controlled decision instead of a late-night rewrite.
Maintenance Cadence
Revisit the setup whenever the target UI changes, when the solver provider changes task names or pricing, or when benchmark data shows a sustained latency or solve-rate shift. Keep one known-good fixture for each CAPTCHA subtype and rerun it after dependency upgrades, browser updates, and proxy changes. If the article is used for vendor selection, repeat the same fixture across at least two providers before renewing a balance or migrating the whole pipeline. That habit keeps how to solve cloudflare turnstile python work aligned with the real target behavior rather than with stale assumptions.