Skip to content
ZeroCaptcha

Solving Cloudflare Turnstile

Cloudflare Turnstile is the widget that shows “Verify you are human”, or runs unseen, and puts a token in the page’s form. A Turnstile task gets you that token for a page you name, so your code can submit the form as a browser would. Send tasks only for sites you are allowed to automate.

Field Required What it is
type Yes TurnstileTaskProxyless, or TurnstileTask to solve through your proxy. CapSolver’s AntiTurnstileTaskProxyLess works too, and AntiTurnstileTask for the proxy variant, and case does not matter.
websiteURL Yes The full address of the page with the widget, such as https://example.com/login: http or https, at most 2,048 characters, without a username or password, on the scheme’s default port, on a public domain name (not an IP address, localhost or a .local or .internal name).
websiteKey Yes The widget’s site key: 1 to 100 letters, digits, _ and -, such as 0x4AAAAAAAB1cD2eF3gH4iJ5.
action When the widget sets one The widget’s action: up to 32 letters, digits, _ and -. See action and cData.
cdata When the widget sets one The widget’s cData: up to 255 letters, digits, _ and -.
proxy With TurnstileTask Your proxy as a URL with its port: http://user:pass@proxy.example.net:8080. http or https; SOCKS is not supported yet. Its host must be public, its port not one another protocol reserves (such as 25), and its login and password at most 255 bytes each. TurnstileTaskProxyless takes none.
callbackUrl No Where to POST the result when the task ends. See Polling and callbacks.

Spaces around a value are trimmed, and an empty optional field counts as absent. A field outside these, or a value out of bounds, is refused with validation_failed (HTTP 422), whose detail names the field; nothing is held. The same fields have other spellings in the createTask format and the 2Captcha format.

A proxyless task is solved from our network. Choose TurnstileTask when the site should see the solve come from your own address, such as when it checks that the token’s solver and the form’s sender match. Your proxy’s password is never logged, and is deleted when the task finishes. Prices differ by type: see pricing.

Open the page in a browser, then its source or the developer tools’ Elements panel, and look for the widget. It is written one of two ways:

  • In the HTML, as an element with the class cf-turnstile:

    <div class="cf-turnstile" data-sitekey="0x4AAAAAAAB1cD2eF3gH4iJ5"
    data-action="login" data-cdata="sess_91f2c0" data-callback="onTurnstile"></div>

    data-sitekey is the websiteKey; data-action and data-cdata, when present, are action and cdata.

  • In a script, as a call to turnstile.render:

    turnstile.render("#captcha", {
    sitekey: "0x4AAAAAAAB1cD2eF3gH4iJ5",
    action: "login",
    cData: "sess_91f2c0",
    callback: (token) => submitLogin(token),
    });

    Search the page’s scripts for turnstile.render or sitekey. sitekey, action and cData are the three values.

A live site key usually starts with 0x4 (Cloudflare’s testing site keys start with 1x, 2x or 3x). Send action and cdata exactly as the page sets them, or leave them out when it sets none: the site sees them in its verification, and may refuse a token whose action or cData does not match. If the page makes the cData new on each visit, read it from the page you will submit, just before creating the task. Cloudflare Turnstile action and cData says when they are required, where to find them and what happens without them.

Create the task with the widget’s site key, action and cData, then read it every 2 seconds until it ends:

Terminal window
# Create the task; the reply is the task, with its id, or a problem document whose code says why
# not. action and cdata are the widget's data-action and data-cdata, or the action and cData
# options of turnstile.render(): leave out any the widget does not set. For your own proxy, make
# the type TurnstileTask and add "proxy": "http://user:pass@proxy.example.net:8080"; to be called
# when the task ends, add "callbackUrl": "https://hooks.example.com/zerocaptcha".
reply=$(curl -sS --fail-with-body "$ZEROCAPTCHA_API/v1/tasks" \
-H "Authorization: Bearer $ZEROCAPTCHA_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"type": "TurnstileTaskProxyless", "websiteURL": "https://example.com/login",
"websiteKey": "0x4AAAAAAAB1cD2eF3gH4iJ5", "action": "login", "cdata": "session-7f3a9c2e"}') ||
{ echo "refused: $reply" >&2; exit 1; }
TASK_ID=$(jq -r .id <<<"$reply")
# Then, every 2 seconds, until "status" is succeeded (with solution.token), failed or expired:
curl "$ZEROCAPTCHA_API/v1/tasks/$TASK_ID" -H "Authorization: Bearer $ZEROCAPTCHA_KEY"

These are short on purpose. Production code also retries a 429 or 5xx after Retry-After, sends the same Idempotency-Key when it retries a create, and stops waiting at a deadline: the quickstart’s samples, the SDKs and the AI brief’s reference clients do all of it. See Errors and retries.

A token works once, for 300 seconds from tokenIssuedAt. Submit it straight away.

The widget puts its token in a hidden field named cf-turnstile-response in the form around it. Send the token in that field with the rest of the form, as the browser would:

Terminal window
curl https://example.com/login \
--data-urlencode "email=you@example.com" \
--data-urlencode "password=$PASSWORD" \
--data-urlencode "cf-turnstile-response=$TOKEN"

Some sites name the field differently with the widget’s data-response-field-name, or send the token in a JSON body or a header from their own script. Look at the request the page makes when you submit it by hand (the developer tools’ Network panel) and send the token the same way.

When the page reacts to the token in JavaScript, through the widget’s data-callback attribute or the callback option of turnstile.render, put the token where the widget would, then call that function with it. In a browser you control, as in browser automation:

// Run in the page: fill the hidden field, then hand the token to the page's own callback.
(token) => {
for (const input of document.querySelectorAll('[name="cf-turnstile-response"]')) input.value = token;
const widget = document.querySelector(".cf-turnstile[data-callback]");
const callback = widget && window[widget.dataset.callback];
if (typeof callback === "function") callback(token);
};

If the callback was passed to turnstile.render as an inline function, find what it does in the page’s script, such as submitLogin(token), and call that.

Every kind of Cloudflare Turnstile widget has a live demo page to point a task at: the managed widget, the invisible widget, the widget with action and cData and more, on the Cloudflare Turnstile demo and CAPTCHA test pages. Each shows its sitekey and the exact request, and checks the token you bring with Cloudflare’s siteverify; the Cloudflare Turnstile token checker checks one on its own.

A task that is not solved after every attempt fails with ERROR_CAPTCHA_UNSOLVABLE, and one not solved by its deadline expires with ERROR_TASK_TIMEOUT. Neither is charged. If one keeps failing, check the websiteURL (the page with the widget, not the form’s target), the websiteKey, and, with TurnstileTask, that your proxy works. A site on our blocklist is refused with domain_blocked and costs nothing.