Tutorials

Solving reCAPTCHA v2 with Node.js Fetch API and CaptchaAI

Node 18+ includes the native fetch API — no axios or node-fetch needed. This guide covers the full reCAPTCHA v2 solve flow with CaptchaAI using only built-in Node.js APIs.


Prerequisites

  • Node.js 18+ (for native fetch)
  • CaptchaAI API key

No external packages required.


Step 1: Submit the reCAPTCHA task

const API_KEY = "YOUR_API_KEY";

async function submitRecaptcha(sitekey, pageurl) {
  const params = new URLSearchParams({
    key: API_KEY,
    method: "userrecaptcha",
    googlekey: sitekey,
    pageurl: pageurl,
    json: "1",
  });

  const response = await fetch("https://ocr.captchaai.com/in.php", {
    method: "POST",
    body: params,
  });

  const data = await response.json();

  if (data.status !== 1) {
    throw new Error(`Submit error: ${data.request}`);
  }

  console.log(`Task submitted: ${data.request}`);
  return data.request;
}

Step 2: Poll for the result

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function pollResult(taskId) {
  const params = new URLSearchParams({
    key: API_KEY,
    action: "get",
    id: taskId,
    json: "1",
  });

  for (let i = 0; i < 30; i++) {
    await sleep(5000);

    const response = await fetch(
      `https://ocr.captchaai.com/res.php?${params}`
    );
    const data = await response.json();

    if (data.status === 1) {
      console.log("Solved!");
      return data.request;
    }

    if (data.request === "ERROR_CAPTCHA_UNSOLVABLE") {
      throw new Error("CAPTCHA unsolvable");
    }

    console.log(`Polling... (${i + 1}/30)`);
  }

  throw new Error("Solve timed out");
}

Step 3: Complete solve function

async function solveRecaptchaV2(sitekey, pageurl) {
  const taskId = await submitRecaptcha(sitekey, pageurl);
  const token = await pollResult(taskId);
  return token;
}

// Usage
const token = await solveRecaptchaV2(
  "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
  "https://example.com/login"
);
console.log(`Token: ${token.substring(0, 50)}...`);

Step 4: Submit the token to the target site

async function submitForm(url, formData) {
  const params = new URLSearchParams(formData);

  const response = await fetch(url, {
    method: "POST",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
      "User-Agent":
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36",
    },
    body: params,
    redirect: "follow",
  });

  return {
    status: response.status,
    url: response.url,
    body: await response.text(),
  };
}

// Full flow
async function loginWithRecaptcha(loginUrl, sitekey, credentials) {
  // Solve reCAPTCHA
  const token = await solveRecaptchaV2(sitekey, loginUrl);

  // Submit login form with token
  const result = await submitForm(loginUrl, {
    ...credentials,
    "g-recaptcha-response": token,
  });

  console.log(`Login result: ${result.status} → ${result.url}`);
  return result;
}

Extracting the sitekey from HTML

async function extractSitekey(url) {
  const response = await fetch(url, {
    headers: {
      "User-Agent":
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36",
    },
  });
  const html = await response.text();

  // Pattern 1: data-sitekey attribute
  const match = html.match(/data-sitekey=["']([A-Za-z0-9_-]{40})["']/);
  if (match) return match[1];

  // Pattern 2: render parameter in script src
  const renderMatch = html.match(/render=([A-Za-z0-9_-]{40})/);
  if (renderMatch) return renderMatch[1];

  // Pattern 3: grecaptcha.render call
  const renderCall = html.match(
    /grecaptcha\.render\s*\([^,]+,\s*\{[^}]*sitekey\s*:\s*["']([A-Za-z0-9_-]{40})["']/
  );
  if (renderCall) return renderCall[1];

  return null;
}

Production-ready solver class

class RecaptchaV2Solver {
  constructor(apiKey) {
    this.apiKey = apiKey;
  }

  async solve(sitekey, pageurl, options = {}) {
    const taskId = await this.#submit(sitekey, pageurl, options);
    return await this.#poll(taskId);
  }

  async #submit(sitekey, pageurl, options) {
    const body = new URLSearchParams({
      key: this.apiKey,
      method: "userrecaptcha",
      googlekey: sitekey,
      pageurl: pageurl,
      json: "1",
      ...(options.invisible && { invisible: "1" }),
      ...(options.data_s && { "data-s": options.data_s }),
    });

    const resp = await fetch("https://ocr.captchaai.com/in.php", {
      method: "POST",
      body,
    });
    const data = await resp.json();

    if (data.status !== 1) {
      throw new Error(`Submit: ${data.request}`);
    }
    return data.request;
  }

  async #poll(taskId) {
    const params = new URLSearchParams({
      key: this.apiKey,
      action: "get",
      id: taskId,
      json: "1",
    });

    for (let i = 0; i < 30; i++) {
      await new Promise((r) => setTimeout(r, 5000));

      const resp = await fetch(`https://ocr.captchaai.com/res.php?${params}`);
      const data = await resp.json();

      if (data.status === 1) return data.request;
      if (data.request === "ERROR_CAPTCHA_UNSOLVABLE") {
        throw new Error("CAPTCHA unsolvable");
      }
    }
    throw new Error("Timed out");
  }
}

// Usage
const solver = new RecaptchaV2Solver("YOUR_API_KEY");
const token = await solver.solve(
  "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
  "https://example.com/login"
);

Handling cookies and sessions

async function solveWithSession(loginUrl, sitekey, credentials) {
  // Node.js fetch doesn't handle cookies automatically.
  // Extract Set-Cookie headers manually.

  // Step 1: Get the login page (capture cookies)
  const pageResp = await fetch(loginUrl, { redirect: "manual" });
  const cookies = pageResp.headers.getSetCookie?.() || [];
  const cookieHeader = cookies.map((c) => c.split(";")[0]).join("; ");

  // Step 2: Solve CAPTCHA
  const solver = new RecaptchaV2Solver(API_KEY);
  const token = await solver.solve(sitekey, loginUrl);

  // Step 3: Submit with cookies
  const result = await fetch(loginUrl, {
    method: "POST",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
      Cookie: cookieHeader,
    },
    body: new URLSearchParams({
      ...credentials,
      "g-recaptcha-response": token,
    }),
    redirect: "manual",
  });

  return {
    status: result.status,
    location: result.headers.get("location"),
    cookies: result.headers.getSetCookie?.() || [],
  };
}

Troubleshooting

Symptom Cause Fix
fetch is not defined Node.js < 18 Upgrade Node or use node-fetch
ERROR_WRONG_GOOGLEKEY Invalid sitekey Re-extract from the page HTML
Token rejected by server Token expired (>2 min) Submit form faster after solving
Login returns same page Missing CSRF or cookies Extract hidden form fields and send cookies
TypeError: resp.json() Non-JSON response Check response status first

Frequently asked questions

Can I use axios instead of fetch?

Yes. Replace fetch() with axios.post() / axios.get(). The CaptchaAI API works the same regardless of HTTP client.

How long until the token expires?

reCAPTCHA v2 tokens expire after approximately 2 minutes. Solve and submit as quickly as possible.

Does this work with reCAPTCHA v2 Invisible?

Yes. Add invisible: "1" to the submit parameters. The API flow is identical.


Summary

Solve reCAPTCHA v2 with Node.js native fetch and CaptchaAI: extract the sitekey, submit to the API, poll for the token, and inject it into the form POST. No external packages needed on Node 18+.

Full Working Code

Complete runnable examples for this article in Python, Node.js, PHP, Go, Java, C#, Ruby, Rust, Kotlin & Bash.

View on GitHub →

Discussions (0)

No comments yet.