Solve Cloudflare Turnstile in Node.js and TypeScript: a tested example that gets a Cloudflare Turnstile token from the ZeroCaptcha API with fetch and posts it with a form. No runtime dependencies, Node.js 22.18+, typed, with retries and a deadline.
Website · Docs · Quickstart · API reference · Pricing
src/solve-turnstile.ts gets a valid Cloudflare Turnstile token for a page you are allowed to automate, and src/submit-form.ts posts a form with it, as a browser would. You give it the page's URL and the widget's sitekey; it creates a task on the ZeroCaptcha API, waits for it, and returns the token.
- No runtime dependencies:
fetch,AbortSignalandcrypto.randomUUID, as Node.js 22.18 and later have them. Node runs the TypeScript directly;typescriptis only there for the type check. - Safe to retry: every task is created with its own
Idempotency-Key, so a retry after a lost reply returns the same task instead of paying for a second one. - Waits sensibly: polls every 2 seconds, retries 429, 502, 503 and 504 after the wait the API asks for, cuts off a slow request, and stops at a deadline (3 minutes by default) or when your
AbortSignalfires. - Typed failures: a refusal or a failed task throws
ZeroCaptchaErrorwith the API'scode, such asinsufficient_fundsorERROR_CAPTCHA_UNSOLVABLE, and therequestIdto quote to support.
-
Create an account on the ZeroCaptcha website, create an API key on the dashboard and add funds (crypto, from $10). A task is charged only when it succeeds.
-
Put the API's address and your key in your environment, never in your code:
export ZEROCAPTCHA_API=https://api.zerocaptcha.io export ZEROCAPTCHA_KEY=zc_live_...
-
Read the widget's
data-sitekey, and itsdata-actionanddata-cdataif it sets them (the sitekey guide shows where else they hide, such as the options ofturnstile.render()), then run:node src/cli.ts https://shop.example.com/login 0x4AAAAAAAB1cD2eF3gH4iJ5 login session-7f3a9c2e
Many sites check the action and cData when they verify the token, so pass both whenever the widget sets them.
It prints the token. Set
PROXY_URL=http://user:pass@proxy.example.net:8080to solve through your own proxy.
import { solveTurnstile, ZeroCaptchaError } from "./src/solve-turnstile.ts";
try {
const token = await solveTurnstile(
{
websiteURL: "https://shop.example.com/login", // the page with the widget
websiteKey: "0x4AAAAAAAB1cD2eF3gH4iJ5", // its data-sitekey
// The widget's data-action and data-cdata, or turnstile.render()'s action and cData options;
// leave out any the widget does not set.
action: "login",
cdata: "session-7f3a9c2e",
// proxy: "http://user:pass@proxy.example.net:8080", // to solve through your own proxy
},
{ api: process.env.ZEROCAPTCHA_API!, key: process.env.ZEROCAPTCHA_KEY!, timeoutMs: 120_000 },
);
console.log(token);
} catch (error) {
if (error instanceof ZeroCaptchaError) console.error(error.code, error.requestId);
else throw error;
}The widget sends its token in the cf-turnstile-response form field, and the site checks it with Cloudflare's siteverify when the form arrives. submitWithToken does both steps:
import { submitWithToken } from "./src/submit-form.ts";
const answer = await submitWithToken(
{
pageURL: "https://shop.example.com/login",
actionURL: "https://shop.example.com/login",
websiteKey: "0x4AAAAAAAB1cD2eF3gH4iJ5",
action: "login", // the widget's data-action, if it sets one
cdata: "session-7f3a9c2e", // the widget's data-cdata, if it sets one
fields: { email: "me@example.com", password: process.env.SHOP_PASSWORD! },
},
{ api: process.env.ZEROCAPTCHA_API!, key: process.env.ZEROCAPTCHA_KEY! },
);
console.log(answer.status);With got-scraping or Crawlee it is the same field: see the got-scraping tutorial and the Crawlee tutorial.
POST /v1/taskswith the page, the sitekey, and the action and cData if the widget sets them. The task's price is held on your balance.GET /v1/tasks/{id}every 2 seconds while the task isqueuedorrunning.succeededcarriessolution.token, and the held price is charged.failedorexpiredcarries anerrorCode, and the hold is released: nothing is charged.
The task lifecycle and the errors and retries guide have every detail.
- A token works once, for 300 seconds. Get it just before you submit, and a new one for the next submission.
- The action and cData must match the widget's. A token made without them can be refused by the site's siteverify check.
- Proxies are
httporhttps, with the port in the URL. SOCKS is not supported. - Only for sites you own or are allowed to automate. The Acceptable Use Policy applies to every task.
- This is an example, not a library. For a maintained client with callbacks, challenge pages and signature checks, use the official JavaScript SDK.
Does it run on Node.js 20?
The code does; running .ts files directly needs Node.js 22.18 or later. On Node.js 20, compile it with tsc first, or copy the functions into a .mjs file without the types.
Does it work in Deno, Bun or a browser? The solver uses only web-standard APIs, so it runs in Deno and Bun. Do not call it from a browser page: that would put your API key in front of your users.
What does a solve cost? The pricing page lists the price per 1,000 solved tasks. Only a task that succeeds is charged.
Why is my token refused by the site? Most often it was used twice, used after 300 seconds, or made without the widget's action or cData. The siteverify errors article explains each code.
How do I drive a real browser instead? See the Playwright and Puppeteer examples, which fill the widget's field in the page.
npm ci
npm run typecheck
npm testThe tests run the solver, the form helper and the command against a stand-in API on your machine: no key, no real task, nothing spent.
- The website: ZeroCaptcha, the docs, the guides, the blog and the status page
- Start here: zerocaptcha, cloudflare-turnstile-solver, cloudflare-challenge-solver
- Examples by language: cloudflare-turnstile-solver-python, cloudflare-turnstile-solver-nodejs, cloudflare-turnstile-solver-go, cloudflare-turnstile-solver-php, cloudflare-turnstile-solver-java, cloudflare-turnstile-solver-csharp, cloudflare-turnstile-solver-rust
- Browser automation: cloudflare-turnstile-solver-playwright, cloudflare-turnstile-solver-puppeteer, cloudflare-turnstile-solver-selenium
- SDKs, MCP server and migration: zerocaptcha-js, zerocaptcha-python, zerocaptcha-go, zerocaptcha-mcp, createtask-api-migration
- Lists: awesome-cloudflare-turnstile
MIT: see LICENSE.
ZeroCaptcha is an independent service, not affiliated with or endorsed by Cloudflare. Cloudflare and Turnstile are trademarks of Cloudflare, Inc. Use ZeroCaptcha only on sites you own or are allowed to automate, as the Acceptable Use Policy says; any site owner can opt out.