guides · undetected-chromedriver

undetected-chromedriver and Cloudflare Turnstile

undetected-chromedriver does one narrow thing well: it patches the window.cdc_… marker out of the chromedriver binary so a page cannot read the most obvious sign of automation. It contains no Turnstile-specific code, its README states plainly that it does not hide your IP address, and the package has had one maintenance merge in eighteen months. Knowing that up front saves the week people spend tuning options.

What the patch actually is

It is worth seeing, because it is smaller than its reputation. patcher.py opens the chromedriver executable, finds the injected window.cdc_… code block by regular expression, and overwrites it with a harmless console.log padded to the same length:

python
match_injected_codeblock = re.search(rb"\{window\.cdc.*?;\}", content)
if match_injected_codeblock:
    target_bytes = match_injected_codeblock[0]
    new_target_bytes = (
        b'{console.log("undetected chromedriver 1337!")}'.ljust(
            len(target_bytes), b" "
        )
    )
    new_content = content.replace(target_bytes, new_target_bytes)

That is the mechanism. Chromedriver normally injects variables named cdc_… into every document, which any page can read in one line; the patch removes that tell. Chrome is a subclass of Selenium's own WebDriver, so everything else is ordinary Selenium.

There is no Turnstile-specific code anywhere in the package — no widget detection, no clicking, no token handling. Nothing about it targets Cloudflare in particular.

Two things the README tells you that most guides skip

The project is unusually direct about its own limits, and both statements matter more than anything in a tutorial.

"THIS PACKAGE DOES NOT, and i repeat DOES NOT hide your IP address, so when running from a datacenter (even smaller ones), chances are large you will not pass! Also, if your ip reputation at home is low, you won't pass!"

That is the maintainer, in the README, naming the exact failure people report as a mystery. If a script clears the challenge on a laptop and loops forever in Docker, the package's own documentation predicted it — Turnstile fails on a datacenter IP goes through what Cloudflare documents about that.

The second, on headless mode: "just to mention it another time, since some people have hard time reading: headless is still WIP. Raising issues is needless". So a headless server deployment is running an explicitly unfinished path.

The issue tracker is closed, and the links you will find are dead

Search for this problem and you will land on issue numbers. They no longer resolve. Issues are disabled on the repository — the API returns 410 Issues are disabled for this repo and the web URLs return 404. The README explains why:

"I will be putting limits on the issue tracker. It has beeen abused too long. any good news? Yes, i've opened Undetected-Discussions which i think will help us better in the long run."

Three of the four issues most often cited are recoverable from archive snapshots, and it is worth correcting a claim that circulates about them. They are not all titled "Turnstile challenge failed":

IssueActual titleOpened
827*Cloudflare turnstile*October 2022
1462*Detected: CloudFlare Turnstile Challenge Failed*August 2023
1175*Not bypassing cloudflare turnstile on VPS*April 2023
2171No archive snapshot exists — unverifiable

Note what #1175 is actually about: a configuration that "works great on my local computer" and does not on a VPS. It is the datacenter-IP problem, filed as a library bug. The discussion has since moved to GitHub Discussions, where several Turnstile threads are open.

Maintenance status, stated plainly

SignalValue
Latest PyPI release3.5.5, uploaded February 2024
GitHub releasesNone published
Last commit to masterJuly 2025 — a single merge
Issue trackerDisabled
Stars12,806

This is not a criticism of the maintainer, who has said what they are doing: nodriver is described in its own README as "the official successor of the Undetected-Chromedriver python package". It is a fact to weigh before building on a package whose last release predates the current Turnstile widget by a long way. See nodriver and Turnstile for what the successor does and does not do.

Using it, and where the token comes from

The basic form is one import and one line — that simplicity is genuinely the appeal:

python
import undetected_chromedriver as uc

driver = uc.Chrome()
driver.get("https://nowsecure.nl")

Because Chrome subclasses Selenium's WebDriver, everything else is standard Selenium — find_element(By.CSS_SELECTOR, …), send_keys, execute_script. That also means the token handling is the same as any Selenium script: write the token into the hidden field the widget would have filled, then submit.

python
import os
import json
import urllib.request
import undetected_chromedriver as uc

SITEKEY = "0x4AAAAAAA_target"
PAGE = "https://app.example.com/login"

req = urllib.request.Request(
    "https://api.solvegate.io/v1/solve",
    data=json.dumps({"gate": "turnstile", "sitekey": SITEKEY, "url": PAGE}).encode(),
    headers={
        "Authorization": f"Bearer {os.environ['SOLVEGATE_KEY']}",
        "Content-Type": "application/json",
    },
)
with urllib.request.urlopen(req, timeout=30) as res:
    token = json.load(res)["token"]

driver = uc.Chrome()
driver.get(PAGE)

# The widget writes into a hidden input; write into the same one, then fire the
# events a framework would be listening for.
driver.execute_script(
    """
    const [name, value] = arguments;
    let el = document.querySelector(`[name="${name}"]`);
    if (!el) {
        el = document.createElement("input");
        el.type = "hidden";
        el.name = name;
        document.querySelector("form").appendChild(el);
    }
    el.value = value;
    el.dispatchEvent(new Event("input", { bubbles: true }));
    el.dispatchEvent(new Event("change", { bubbles: true }));
    """,
    "cf-turnstile-response",
    token,
)

The field name is cf-turnstile-response unless the page renamed it with data-response-field-nameour checker reports the real one along with the sitekey. Tokens are single-use and expire in minutes, so fetch one at the moment you submit; token lifetime has the rules.

A token typically comes back in under 1.5 seconds. Prepaid credits start at $0.40 per 1,000 and fall to $0.075 at volume, failed solves are never billed, and the first 1,000 are free. Solve only against properties you own or are authorised to test.

Common questions

It has no Turnstile-specific code at all. What it does is binary-patch the window.cdc_ marker out of the chromedriver executable, which removes one obvious automation tell. Whether a challenge then passes depends on everything else — IP reputation above all, which the README says explicitly the package does not address.

The README answers this directly: it does not hide your IP address, and running from a datacenter means you will probably not pass. A residential address with good reputation buys margin that a server address does not have, so the same code behaves differently for reasons that have nothing to do with the code.

No. Issues are disabled on the repository, so the URLs return 404 and the API returns 410. Three of the four commonly cited ones are recoverable from web archives, and only one of them is actually titled "Turnstile Challenge Failed" — the others are "Cloudflare turnstile" and "Not bypassing cloudflare turnstile on VPS". The conversation moved to GitHub Discussions.

Barely. The last PyPI release is 3.5.5 from February 2024, there are no GitHub releases, and master has had a single merge since. The maintainer describes nodriver as the official successor, so treat this as a package in maintenance rather than one tracking a moving target.

You can, and the README says headless is still work in progress and asks people not to file issues about it. On a server the usual approach is a virtual display such as Xvfb rather than true headless — that keeps you on the supported path and avoids one whole class of detection.

Related

Sources

More in Guides

Playwright and Cloudflare TurnstileStop Cloudflare Turnstile breaking Playwright tests: use Cloudflare's official test sitekeys on staging, or solve a real widget and inject the token.Python Cloudflare Turnstile SolverSolve Cloudflare Turnstile from Python with plain requests or the solvegate SDK. Complete runnable code, async usage, retries, and error handling.Node.js Turnstile solverSolve Cloudflare Turnstile from Node.js: a zero-dependency global fetch version, the solvegate npm SDK, async/await error handling and TypeScript types.How to find a Turnstile sitekey on a pageFind a Cloudflare Turnstile sitekey in seconds: the data-sitekey attribute, keys starting 0x4, explicit-render calls, and Playwright/Node code that extracts it.How to get the cf-turnstile-response tokenWhat the cf-turnstile-response token is, the hidden input it lives in, how siteverify validates it, and how to get one in your own automated tests.Puppeteer Turnstile bypassHow to handle Cloudflare Turnstile in Puppeteer: Cloudflare's official test sitekeys for pages you own, plus a runnable token-injection script.Selenium and Cloudflare Turnstile in PythonHandle Cloudflare Turnstile in Selenium 4 and Python: test sitekeys, explicit waits, execute_script token injection, and what changes in headless.Turnstile fails on a datacenter IPThe same code passes on your laptop and loops forever on a server. What Cloudflare documents about IP reputation, what it does not, and what actually changes.curl_cffi and Turnstilecurl_cffi impersonates TLS and HTTP/2 fingerprints and has no JavaScript runtime. Its own FAQ says so. What that solves, what it cannot, and where the line is.Scrapy and TurnstileScrapy has no JavaScript engine, so a Turnstile page returns markup and no data. The three documented routes, and which one Scrapy's own docs recommend first.SeleniumBase and TurnstileThe uc_gui captcha methods use PyAutoGUI and raise in headless mode — the check is in the source. solve_captcha uses CDP and does not. Which to use where.Camoufox and TurnstileCamoufox is a Firefox fork that patches fingerprints at the C++ level. It ships no Turnstile solver, and the issue asking for one was closed as not planned.nodriver and Turnstilenodriver's README documents a tab.cf_verify() that clicks the Cloudflare checkbox. It is not in the shipped code. What the library does do instead.Patchright and TurnstilePatchright is a drop-in Playwright that closes the Runtime.enable leak and reaches into closed shadow roots — which is exactly where a Turnstile checkbox lives.DrissionPage and TurnstileDrissionPage reaches into non-open shadow roots and switches between browser and HTTP mode. It also forbids commercial use, and its docs are Chinese-only.Go, Colly and TurnstileColly has no JavaScript engine — its dependency graph proves it. What that means for Turnstile in Go, and how chromedp and rod compare when you need a browser.

Automating a gate you own or are authorised to test?

// SolveGate clears Cloudflare Turnstile and WAF challenges through one REST call · first 1,000 solves free