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:
mkdir turnstile-solver && cd turnstile-solver
npm init -yNo 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:
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:
// 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
// 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:
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.