Skip to content

Cloudflare Turnstile solver · Selenium

Solve Cloudflare Turnstile in Selenium

In a Selenium script on a site you are allowed to automate, a Turnstile widget takes one call to ZeroCaptcha: read the site key, action and cData, solve, set the token in the form and submit. The sample uses the Python client, which is coming to PyPI; until then, the Python page makes the same calls with requests. The same steps work in any Selenium binding.

Plain HTTP today; the official Python client is coming

The steps in Selenium

  1. Get an API key

    Sign up with an email and a password, create your key in the dashboard and add funds in crypto, from $10.

  2. Read the widget's site key, action and cData

    Open the page in Selenium and read data-sitekey, data-action and data-cdata from the Turnstile element, or the sitekey, action and cData options of its turnstile.render() call.

  3. Solve it with ZeroCaptcha

    Send the page's URL, the site key, and the action and cData when the widget sets them, to ZeroCaptcha and wait for the token. Many sites check both when they verify it.

  4. Put the token in the form

    Set the cf-turnstile-response field to the token, then submit the form as a person would, within 300 seconds.

New to the API? The quickstart walks through sign-up, the key and the first task.

# Fill a Cloudflare Turnstile widget in Selenium with a token from ZeroCaptcha, on a page you may automate.
# Needs Python 3.9 or later, selenium and the zerocaptcha package:
#   ZEROCAPTCHA_API=https://api.zerocaptcha.io ZEROCAPTCHA_KEY=zc_live_... python fill_turnstile.py
import os

from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from zerocaptcha import TaskFailedError, ZeroCaptcha

zerocaptcha = ZeroCaptcha(api_key=os.environ["ZEROCAPTCHA_KEY"], base_url=os.environ["ZEROCAPTCHA_API"])

# A widget rendered by script takes its settings from turnstile.render(container, options):
# note each call's options before the page's own scripts run (Chrome's DevTools protocol).
NOTE_RENDERS = """
let api;
Object.defineProperty(window, "turnstile", {
  configurable: true,
  get: () => api,
  set(value) {
    const render = value.render;
    value.render = (container, options = {}) => {
      window.__turnstileRenders = [...(window.__turnstileRenders || []), options];
      return render.call(value, container, options);
    };
    api = value;
  },
});
"""
# The widget's site key, action and cData: its data-sitekey, data-action and data-cdata
# attributes, or the sitekey, action and cData it was rendered with.
READ_WIDGET = """
const element = document.querySelector("[data-sitekey]");
const rendered = (window.__turnstileRenders || [])[0] || {};
if (!element && !window.__turnstileRenders) return null;
return {
  sitekey: element ? element.dataset.sitekey : rendered.sitekey,
  action: (element && element.dataset.action) || rendered.action || null,
  cdata: (element && element.dataset.cdata) || rendered.cData || null,
  callback: element ? element.dataset.callback || null : null,
};
"""

driver = webdriver.Chrome()
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {"source": NOTE_RENDERS})
driver.get("https://shop.example.com/login")
widget = WebDriverWait(driver, 15).until(lambda d: d.execute_script(READ_WIDGET))

task = {"website_url": driver.current_url, "website_key": widget["sitekey"]}
# Many sites check the action and cData when they verify the token: send them exactly as the
# widget sets them, and leave out any it does not.
if widget["action"]:
    task["action"] = widget["action"]
if widget["cdata"]:
    task["cdata"] = widget["cdata"]
# task["proxy"] = "http://user:pass@proxy.example.net:8080"  # to solve through your own proxy

try:
    # The client sends an Idempotency-Key with the create, so its own retries never make two tasks.
    token = zerocaptcha.solve(**task)
except TaskFailedError as failure:
    # A task that fails or expires costs nothing; its code says why.
    driver.quit()
    raise SystemExit(f"Not solved: {failure.code}")

# Put the token where the widget would: its cf-turnstile-response field, then its callback.
driver.execute_script(
    "for (const input of document.querySelectorAll('[name=\"cf-turnstile-response\"]'))"
    " input.value = arguments[0];"
    " if (arguments[1] && typeof window[arguments[1]] === 'function') window[arguments[1]](arguments[0]);",
    token,
    widget["callback"],
)
driver.find_element("css selector", "button[type=submit]").click()
driver.quit()

Good to know

Read next

Selenium questions

Does it work with Selenium in Java or C#?

Yes. Create the task over HTTP as the Java and C# pages show, then set cf-turnstile-response with executeScript before you submit.

How long does a Cloudflare Turnstile token last?

A Cloudflare Turnstile token works once and expires 300 seconds after it is issued, so solve right before you submit. Every result tells you when its token expires.

What does a failed task cost?

Nothing. The price is held when you create a task and released at once if it fails or expires, and a refused task holds nothing; you pay only when a token is ready.