Skip to content

How to Bypass Cloudflare Turnstile with Node.js

NSLSolver TeamUpdated 4 min read

Cloudflare Turnstile is a popular CAPTCHA alternative used on millions of sites. If you're working with Node.js and need to bypass Turnstile programmatically, this guide shows you how using the NSLSolver API.

No puppeteer, no headless browser, no dependencies beyond Node.js 18+ with built-in fetch().

Prerequisites

  • Node.js 18+ (for native fetch support)
  • An NSLSolver API key (sign up free at nslsolver.com)

Step 1: Set up your project

Create a new directory and initialize a Node.js project:

terminal
mkdir turnstile-solver && cd turnstile-solver
npm init -y

No additional packages needed — we use the built-in fetch() API.

Step 2: Solve the Turnstile challenge

Send a POST request to the NSLSolver API with the target site's Turnstile sitekey and URL:

solve.mjs
const response = await fetch('https://api.nslsolver.com/solve', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-api-key': process.env.NSLSOLVER_API_KEY,
  },
  body: JSON.stringify({
    type: 'turnstile',
    site_key: '0x4AAAAAAXXXXXXXXXXXXXXX',
    url: 'https://example.com/login',
  }),
});

const data = await response.json();
console.log('Token:', data.token);

The API responds synchronously — no polling needed. Average response time is ~250ms.

Step 3: Use the token

Pass the returned token in your form submission or API call to the target site:

submit.mjs
// Use the token in your form submission or API call
const result = await fetch('https://example.com/api/login', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    username: process.env.LOGIN_EMAIL,
    password: process.env.LOGIN_PASSWORD,
    'cf-turnstile-response': data.token,
  }),
});

Complete example

Here's a self-contained script you can save and run directly:

solve-turnstile.mjs
// solve-turnstile.mjs
// Run: NSLSOLVER_API_KEY=nsl_... node solve-turnstile.mjs

const API_KEY = process.env.NSLSOLVER_API_KEY;
const SITE_KEY = '0x4AAAAAAXXXXXXXXXXXXXXX';
const TARGET_URL = 'https://example.com/login';

async function solveTurnstile() {
  const res = await fetch('https://api.nslsolver.com/solve', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-api-key': API_KEY,
    },
    body: JSON.stringify({
      type: 'turnstile',
      site_key: SITE_KEY,
      url: TARGET_URL,
    }),
  });

  if (!res.ok) {
    throw new Error(`Solve failed: ${res.status} ${await res.text()}`);
  }

  const { token } = await res.json();
  console.log('Solved! Token:', token.slice(0, 40) + '...');
  return token;
}

solveTurnstile().catch(console.error);

Async/Await helper pattern

Wrap the solve logic in a reusable function for cleaner code:

helper.mjs
async function withTurnstileToken(siteKey, url, callback) {
  const res = await fetch('https://api.nslsolver.com/solve', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-api-key': process.env.NSLSOLVER_API_KEY,
    },
    body: JSON.stringify({ type: 'turnstile', site_key: siteKey, url }),
  });

  const { token } = await res.json();
  return callback(token);
}

// Usage
await withTurnstileToken(
  '0x4AAAAAAXXXXXXXXXXXXXXX',
  'https://example.com/login',
  async (token) => {
    // your logic here
    console.log('Got token, submitting form...');
  }
);

Understanding the request parameters

The /solve endpoint accepts a small JSON body. For Turnstile, three fields matter:

  • site_key — the public Turnstile sitekey rendered in the target page's HTML (the value beginning with 0x4...). Read it from the data-sitekey attribute of the .cf-turnstile element.
  • url — the full URL of the page where the widget appears. Turnstile binds tokens to the host, so this must match where you submit the token.
  • action / cdata — optional. If the site sets a custom action or cData on its widget, pass the same values so the returned token validates server-side.

How Turnstile detection works

Turnstile runs a lightweight, invisible browser challenge that fingerprints the client (canvas, timing, and JS execution signals) and returns a single-use token. Because the checks run in a real browser environment, naive HTTP clients and most headless-browser stealth patches fail intermittently. NSLSolver executes the challenge in a managed browser farm and returns only the resulting token, so your Node.js code stays a plain fetch() call.

Tokens are short-lived — typically valid for about 300 seconds (5 minutes) and single-use. Solve the widget immediately before you submit the form, and request a fresh token for each attempt rather than caching one across requests.

Prefer Python? The same flow works with the requests library — read the Python tutorial.

Start solving Turnstile with Node.js

Create a free account, grab your API key, and paste the code above. You'll be solving in under a minute.

Cloudflare Turnstile with Node.js — FAQ

How do I bypass Cloudflare Turnstile in Node.js?

POST the site key and page URL to the NSLSolver API using the built-in fetch() — no extra dependencies. You get back a valid Turnstile token in ~250ms to submit with your request.

Do I need Puppeteer?

No. NSLSolver returns a token via a single REST call, so you don't run Puppeteer or any headless browser — native fetch() on Node 18+ is all you need.

What does it cost?

$0.40 per 1,000 Turnstile solves; failed solves are never charged.

Can I reuse a token?

No — Turnstile tokens are single-use and expire within minutes. Fetch a fresh token per submission.