Technical Guide8 min readPython 3.9+ & Playwright

How to Bypass Cloudflare Turnstile in Python: Playwright & 2Captcha API Guide

Cloudflare Turnstile has become the dominant alternative to legacy puzzle captchas. This tutorial breaks down how Turnstile works under the hood and provides production-grade Python scripts using Playwright and the 2Captcha API.

Recommended Turnstile SolverEditorial Rating: 9.8 / 10

Why 2Captcha Ranks #1 for Cloudflare Turnstile

In our standardized telemetry benchmarks, 2Captcha proved the most consistent Turnstile solver, solving challenges in an average of 2.98 seconds at $0.50 per 1,000 solves with a 99.8% resolution rate. It natively supports optional parameters (action, cData, and pagedata) required by hardened Turnstile implementations.

Get Free 2Captcha API Key ↗Compare All Turnstile Solvers →

Understanding How Cloudflare Turnstile Works

Unlike legacy image puzzles that prompt users to select traffic lights, Cloudflare Turnstile executes client-side cryptographic challenges within an embedded iframe. The browser sends device fingerprints, execution timing metrics, and proof-of-work calculations back to Cloudflare's edge nodes (challenges.cloudflare.com).

When the challenge verifies successfully, Turnstile generates an ephemeral verification token (typically 300+ characters) and writes it into a hidden form input named cf-turnstile-response. When the user submits the form, the backend server validates this token with Cloudflare's siteverify API.

Critical Turnstile Parameters

  • Sitekey: Public identifier for the Turnstile widget (e.g., 0x4AAAAAAADnPIDROrmt1Wwj). Always visible in the page source.
  • Page URL: The exact origin and path where the widget is embedded.
  • Action (Optional): A contextual tag (e.g., login, checkout) defined by the website owner to segment traffic.
  • cData (Optional): Custom customer payload passed to the widget for server-side correlation.

Step 1: Install Dependencies

Install the official 2Captcha Python SDK and Microsoft Playwright:

pip install 2captcha-python playwright
playwright install chromium

Step 2: Basic Turnstile API Call in Python

Before integrating into full browser automation, test your 2Captcha API connection directly. The SDK manages polling, task dispatching, and response decoding automatically:

import os
from twocaptcha import TwoCaptcha

api_key = os.getenv("TWOCAPTCHA_API_KEY")
solver = TwoCaptcha(api_key)

try:
    print("Sending Turnstile task to 2Captcha...")
    result = solver.turnstile(
        sitekey="0x4AAAAAAADnPIDROrmt1Wwj",
        url="https://target-website.com/login",
        action="login",          # Optional: captured from data-action
        data="custom-cdata-val"  # Optional: captured from data-cdata
    )
    token = result["code"]
    print("Turnstile solved successfully!")
    print(f"Token: {token[:45]}...")
except Exception as e:
    print(f"Turnstile solving error: {e}")

Step 3: Complete Playwright Automation Script

In production scraping or headless form submission, you must extract the parameters dynamically from the live DOM, request the token from 2Captcha asynchronously, inject it into the hidden input, and trigger any attached callback functions.

Here is the end-to-end Python script:

import asyncio
import os
from playwright.async_api import async_playwright
from twocaptcha import TwoCaptcha

API_KEY = os.getenv("TWOCAPTCHA_API_KEY")
solver = TwoCaptcha(API_KEY)

TARGET_URL = "https://peet.ws/turnstile-test"

async def bypass_turnstile():
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=False)
        context = await browser.new_context()
        page = await context.new_page()

        print(f"Navigating to {TARGET_URL}...")
        await page.goto(TARGET_URL, wait_until="domcontentloaded")

        # 1. Locate the Turnstile container and extract sitekey
        turnstile_elem = await page.wait_for_selector(".cf-turnstile, [data-sitekey]", timeout=15000)
        sitekey = await turnstile_elem.get_attribute("data-sitekey")
        action = await turnstile_elem.get_attribute("data-action") or ""
        cdata = await turnstile_elem.get_attribute("data-cdata") or ""

        print(f"Found Turnstile: sitekey={sitekey}, action={action}")

        # 2. Request solution from 2Captcha in a separate worker thread
        loop = asyncio.get_running_loop()
        solution = await loop.run_in_executor(
            None,
            lambda: solver.turnstile(
                sitekey=sitekey,
                url=page.url,
                action=action if action else None,
                data=cdata if cdata else None
            )
        )
        token = solution["code"]
        print(f"Received solution token from 2Captcha in {solution.get('solve_time', 'N/A')}s")

        # 3. Inject token into cf-turnstile-response and fire callback
        await page.evaluate("""(token) => {
            // Populate all matching hidden inputs
            const inputs = document.querySelectorAll('input[name="cf-turnstile-response"]');
            inputs.forEach(input => {
                input.value = token;
                input.dispatchEvent(new Event('input', { bubbles: true }));
                input.dispatchEvent(new Event('change', { bubbles: true }));
            });

            // If a global callback is registered, trigger it
            if (window.turnstileCallback && typeof window.turnstileCallback === 'function') {
                window.turnstileCallback(token);
            }
        }""", token)

        # 4. Submit form or verify challenge resolution
        submit_btn = await page.query_selector('button[type="submit"], input[type="submit"]')
        if submit_btn:
            await submit_btn.click()
            print("Form submitted successfully with Turnstile token!")
        
        await page.wait_for_timeout(5000)
        await browser.close()

if __name__ == "__main__":
    asyncio.run(bypass_turnstile())

Troubleshooting Common Turnstile Traps

1. Token Expired or Invalid

Cloudflare Turnstile tokens have a strict 300-second (5 minute) TTL. Ensure your script injects and submits the token immediately after 2Captcha returns it.

2. Action or cData Mismatch

If the target site uses data-action or data-cdata on the widget element and you omit them in the API request, the site's backend validation will reject the token even if 2Captcha solved it successfully. Always extract both attributes if present.

3. Interstitial 5-Second Challenge Pages

When Cloudflare presents a full-page "Checking your browser before accessing..." interstitial, you need a cf_clearance cookie rather than a form submission token. 2Captcha provides the Turnstile Clearance Task type specifically for interstitial mode.

Advanced Evasion: TLS & IP Fingerprints Matter

Cloudflare Turnstile doesn't just inspect DOM interactions—it audits your TLS ClientHello handshake (JA4 fingerprint) and client IP reputation score before deciding whether to trigger interactive challenges.

Related Resources