Developer Guides

CAPTCHA Solving in Node.js — Quick Start Guide

This guide gets you solving CAPTCHAs in Node.js in under 30 minutes. It covers the core solve-and-inject pattern, then shows integration with Puppeteer and Playwright for browser-based automation. All examples use CaptchaAI (2Captcha-compatible API) with axios.

Prerequisites

npm install axios
# For browser automation:
npm install puppeteer
# or
npm install playwright @playwright/test

No special SDK is required. The CaptchaAI API (and 2Captcha-compatible APIs) use plain HTTP requests.

Core Pattern: Submit → Poll → Inject

All 2Captcha-format solvers follow the same two-step pattern: 1. Submit the task to /in.php → receive a task ID 2. Poll /res.php until the result is ready → receive the token

const axios = require('axios');

/**
 * Generic solver using the 2Captcha-compatible API.
 * Works with CaptchaAI, 2Captcha, Anti-Captcha (in 2Captcha mode), CapMonster Cloud.
 */
async function solve(apiKey, payload, pollStartDelay = 5000, maxAttempts = 30) {
  // Step 1: Submit the task
  const submitRes = await axios.post('https://ocr.captchaai.com/in.php', null, {
    params: { ...payload, key: apiKey, json: 1 },
  });

  if (submitRes.data.status !== 1) {
    throw new Error(`Submit failed: ${JSON.stringify(submitRes.data)}`);
  }
  const taskId = submitRes.data.request;

  // Step 2: Poll for result
  await delay(pollStartDelay);
  for (let i = 0; i < maxAttempts; i++) {
    const pollRes = await axios.get('https://ocr.captchaai.com/res.php', {
      params: { key: apiKey, action: 'get', id: taskId, json: 1 },
    });

    if (pollRes.data.status === 1) {
      return pollRes.data.request;  // The solved token
    }
    if (!['CAPCHA_NOT_READY', 'CAPTCHA_NOT_READY'].includes(pollRes.data.request)) {
      throw new Error(`Unexpected response: ${JSON.stringify(pollRes.data)}`);
    }
    await delay(5000);
  }
  throw new Error('CAPTCHA solve timed out');
}

const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

Solving reCAPTCHA v2

async function solveRecaptchaV2(apiKey, pageUrl, siteKey, invisible = false) {
  const payload = {
    method: 'userrecaptcha',
    googlekey: siteKey,
    pageurl: pageUrl,
    ...(invisible && { invisible: 1 }),
  };
  return solve(apiKey, payload, 5000);
}

// Usage
const token = await solveRecaptchaV2(
  'YOUR_API_KEY',
  'https://example.com/login',
  '6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-'
);
console.log('Token:', token.substring(0, 40), '...');

Solving hCaptcha

async function solveHcaptcha(apiKey, pageUrl, siteKey) {
  const payload = {
    method: 'hcaptcha',
    sitekey: siteKey,
    pageurl: pageUrl,
  };
  return solve(apiKey, payload, 7000);
}

Solving Cloudflare Turnstile

async function solveTurnstile(apiKey, pageUrl, siteKey) {
  const payload = {
    method: 'turnstile',
    sitekey: siteKey,
    pageurl: pageUrl,
  };
  return solve(apiKey, payload, 5000);
}

Integration with Puppeteer

const puppeteer = require('puppeteer');

async function submitFormWithCaptcha(pageUrl, siteKey, apiKey) {
  // Solve before opening the browser to minimize token age
  const token = await solveRecaptchaV2(apiKey, pageUrl, siteKey);

  const browser = await puppeteer.launch({ headless: true });
  const page = await browser.newPage();
  await page.goto(pageUrl, { waitUntil: 'networkidle2' });

  // Inject the token
  await page.evaluate((t) => {
    const fields = document.querySelectorAll(
      '#g-recaptcha-response, [name="g-recaptcha-response"]'
    );
    fields.forEach((f) => {
      f.value = t;
      f.style.display = 'block';  // Make visible for debugging
    });
  }, token);

  // Submit
  await page.click('button[type="submit"]');
  await page.waitForNavigation({ waitUntil: 'networkidle2' });
  await browser.close();
}

Integration with Playwright

const { chromium } = require('playwright');

async function submitFormWithPlaywright(pageUrl, siteKey, apiKey) {
  const token = await solveRecaptchaV2(apiKey, pageUrl, siteKey);

  const browser = await chromium.launch({ headless: true });
  const page = await browser.newPage();
  await page.goto(pageUrl, { waitUntil: 'networkidle' });

  await page.evaluate((t) => {
    document.querySelectorAll('[name="g-recaptcha-response"]')
      .forEach((el) => { el.value = t; });
  }, token);

  await page.click('button[type="submit"]');
  await page.waitForLoadState('networkidle');
  await browser.close();
}

Switching to 2Captcha

// Change one line — all parameters are identical
const submitRes = await axios.post('https://2captcha.com/in.php', null, {
  params: { ...payload, key: apiKey, json: 1 },
});

Error Handling

async function solveWithRetry(apiKey, payload, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await solve(apiKey, payload);
    } catch (err) {
      if (attempt === maxRetries) throw err;
      console.warn(`Attempt ${attempt} failed: ${err.message}. Retrying...`);
      await delay(3000);
    }
  }
}

Common error codes to handle: - ERROR_ZERO_BALANCE — Top up account credits - ERROR_CAPTCHA_UNSOLVABLE — Retry or switch solver; may indicate flagged IP - ERROR_WRONG_GOOGLEKEY — Re-extract site key from page HTML - CAPCHA_NOT_READY / CAPTCHA_NOT_READY — Normal polling response; continue waiting

Async Batching (Multiple Solves in Parallel)

When running multiple solves in parallel, use Promise.allSettled to handle partial failures gracefully:

async function solveBatch(apiKey, tasks) {
  const results = await Promise.allSettled(
    tasks.map(({ pageUrl, siteKey }) =>
      solveRecaptchaV2(apiKey, pageUrl, siteKey)
    )
  );

  return results.map((result, i) => ({
    task: tasks[i],
    token: result.status === 'fulfilled' ? result.value : null,
    error: result.status === 'rejected' ? result.reason.message : null,
  }));
}

Production Readiness Notes

Use CAPTCHA Solving in Node.js — Quick Start Guide as a decision and implementation aid, not just as a one-time reference. The practical test for captcha solving nodejs quick start is whether the same approach behaves reliably when traffic is messy: rotating sessions, expired tokens, changing widget parameters, intermittent solver delays, and target pages that refresh without warning. For Automation developer, the safest rollout is to start with a narrow fixture, record every submitted task, and compare the solver response with the browser state that finally submits the form. That makes failures explainable instead of mysterious, especially when a target alternates between visible challenges, invisible checks, and server-side verification.

Evaluation Criteria

A developer guide should become a reusable integration module with typed configuration, bounded polling, structured errors, and a single place for API credentials. For developer integration work, the most useful scorecard combines technical acceptance with operational cost. A low nominal price is not enough if retries double the real cost per accepted token, and a fast median solve time is not enough if p95 latency stalls the queue. Track these criteria before you standardize the workflow:

  • The challenge subtype, sitekey, action, rqdata, blob, captchaId, or page URL used for each task.
  • Median and p95 solve time, separated by provider and target domain.
  • Accepted-token rate on the target page, not just successful API responses.
  • Retry count, timeout count, zero-balance incidents, and invalid-parameter errors.
  • The exact browser, proxy region, and user-agent that submitted the solved token.

Rollout Checklist

Before this guidance moves into a production job, build a small acceptance suite around the pages that matter most. Run it with a fixed browser profile, then repeat with the proxy and concurrency settings you expect in production. Keep the first release conservative: bounded polling, clear timeout handling, and a fallback path when the solver cannot return a usable answer. For developer integration, treat the code as a production pattern: timeouts, retries, logging, secret storage, and test fixtures matter as much as the solve request itself. That checklist keeps the article useful after the first copy-paste, because the integration is judged by end-to-end completion rather than by whether a code sample returned a string.

Monitoring Signals

Healthy CAPTCHA automation is observable. Log the task id, provider, challenge type, target host, queue time, solve time, final submit status, and normalized error code for every attempt. Review those logs in daily batches at first, then move to alerts once the baseline is stable. Sudden drops usually come from target-side changes: a new sitekey, a changed action name, a stricter hostname check, an added managed challenge, or a proxy pool that no longer matches the expected geography. When you can see those shifts quickly, provider switching becomes a controlled decision instead of a late-night rewrite.

Maintenance Cadence

Revisit the setup whenever the target UI changes, when the solver provider changes task names or pricing, or when benchmark data shows a sustained latency or solve-rate shift. Keep one known-good fixture for each CAPTCHA subtype and rerun it after dependency upgrades, browser updates, and proxy changes. If the article is used for vendor selection, repeat the same fixture across at least two providers before renewing a balance or migrating the whole pipeline. That habit keeps captcha solving nodejs quick start work aligned with the real target behavior rather than with stale assumptions.

Comments are disabled for this article.