Cloudflare Turnstile solver using Playwright stealth browser automation. Replaces broken VM-based approach with a real Chromium browser that passes Cloudflare's fingerprinting checks.
Client | | POST /solve {sitekey, pageUrl} v HTTP Server (server.js) | v TurnstileSolver (solver.js) | |-- Spawn local HTTP server (random port) | serves minimal HTML with cf-turnstile widget | |-- Launch Playwright Chromium (headless=new, stealth plugin) | |-- Navigate to local page, intercept Cloudflare requests | fake Origin/Referer headers to match target domain | |-- Poll document.title for TOKEN: | v Return token string (~500-900 chars, starts with 0.)
Tokens expire after 300 seconds. No caching is implemented.
npm install npx playwright install chromium
node src/server.js
node test_solver.js
const TurnstileSolver = require('./src/solver'); const solver = new TurnstileSolver({ debug: true, timeout: 90000 }); const result = await solver.solve('YOUR_SITEKEY', 'https://yoursite.com/page'); console.log(result.token); await solver.close();
GET /health Returns: { status: 'ok', version: '1.0.0' }
POST /solve Body: { sitekey: string, pageUrl: string, proxy?: string, timeout?: number } Returns: { token: string, sitekey: string, pageUrl: string, elapsed: number } Errors: 400 if sitekey missing, 500 on solver failure
PORT HTTP listen port (default: 3000) TURNSTILE_API_KEY If set, requires X-API-Key header on /solve requests
1x00000000000000000000AA Always passes (any origin, no challenge) 2x00000000000000000000AB Always blocks 3x00000000000000000000FF Forces interactive challenge
- Real sitekeys require pageUrl to match the domain registered with Cloudflare. The solver fakes Origin/Referer headers on Cloudflare requests to satisfy this.
- The reqflow-sidecar binary in sidecar/ is used for downstream HTTP requests after a token is captured.
- Tokens are single-use and expire in 300 seconds.