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+.
Discussions (0)
Join the conversation
Sign in to share your opinion.
Sign InNo comments yet.