Para quem integra a API, a diferença entre o reCAPTCHA v2 padrão e o v2 Enterprise cabe em um parâmetro: enterprise=1. O widget é o mesmo checkbox "Não sou um robô", o campo de resposta continua sendo g-recaptcha-response e o método enviado à CaptchaAI continua sendo userrecaptcha. O que muda é o back-end que valida o token — o do Google Enterprise, mais rigoroso com o contexto da sessão que gerou o desafio.
Isso afeta três pontos do seu código Node.js: como confirmar que a página é Enterprise, o que acrescentar na requisição e o que fazer com o user_agent devolvido. Os quatro passos abaixo cobrem esse caminho; o script completo está no fim.
Antes de começar: o que ter em mãos
| Requisito | Detalhes |
|---|---|
| Chave de API da CaptchaAI | Painel da CaptchaAI |
| Node.js 14+ | Com fetch nativo ou node-fetch |
| sitekey | Parâmetro k= da URL de anchor do Enterprise |
| URL da página | Endereço completo onde o CAPTCHA aparece |
action (opcional) |
Parâmetro sa= da mesma URL de anchor |
Passo 1: confirme que a página usa mesmo Enterprise v2
Abra o DevTools, vá até a aba Network e procure a requisição de anchor carregada pelo widget — três sinais confirmam o diagnóstico:
https://www.google.com/recaptcha/enterprise/anchor?ar=1&k=6LdxxXXxAAAAAAcX...&sa=LOGIN&...
- O script vem de
/recaptcha/enterprise.jsou de/enterprise/anchor - O parâmetro
k=é a sitekey (chave pública do widget) - O parâmetro
sa=, quando existe, é a action
Se a URL for /recaptcha/api2/anchor, o site usa o v2 padrão e você não deve enviar enterprise=1 — marcar um widget comum como Enterprise é causa frequente de token recusado.
Passo 2: envie a tarefa para a CaptchaAI
A requisição vai para in.php e carrega quatro informações:
method=userrecaptcha— o mesmo método do v2 padrãogooglekey— a sitekey lida nok=pageurl— a URL completa do formulárioenterprise=1— o marcador que troca o back-end de validação
Acrescente json=1 para receber a resposta em JSON. O action entra apenas quando o anchor trouxe sa=:
const API_KEY = "YOUR_API_KEY";
async function submitTask(sitekey, pageurl, action) {
const params = new URLSearchParams({
key: API_KEY,
method: "userrecaptcha",
googlekey: sitekey,
pageurl: pageurl,
enterprise: "1",
json: "1",
});
if (action) {
params.set("action", action);
}
const response = await fetch(
`https://ocr.captchaai.com/in.php?${params}`
);
const data = await response.json();
if (data.status !== 1) {
throw new Error(`Submit failed: ${data.request}`);
}
console.log(`Task submitted. ID: ${data.request}`);
return data.request;
}
A resposta traz o ID da tarefa em data.request. Guarde esse valor: ele é a chave de tudo que vem depois.
Passo 3: consulte o resultado com polling
O tempo de resolução do reCAPTCHA v2 Enterprise fica abaixo de 60 s, e o ritmo do polling sai daí:
- espere 20 s antes da primeira consulta
- repita a cada 5 s, no máximo 30 vezes
- trate
CAPCHA_NOT_READYcomo "ainda processando", não como erro
O loop abaixo faz exatamente isso:
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function pollResult(taskId) {
await delay(20000);
for (let attempt = 0; attempt < 30; attempt++) {
const params = new URLSearchParams({
key: API_KEY,
action: "get",
id: taskId,
json: "1",
});
const response = await fetch(
`https://ocr.captchaai.com/res.php?${params}`
);
const data = await response.json();
if (data.status === 1) {
console.log(`Solved. Token: ${data.request.substring(0, 60)}...`);
return {
token: data.request,
userAgent: data.user_agent || "",
};
}
if (data.request !== "CAPCHA_NOT_READY") {
throw new Error(`Solve failed: ${data.request}`);
}
console.log(`Attempt ${attempt + 1}: not ready, waiting 5s...`);
await delay(5000);
}
throw new Error("Solve timed out");
}
Repare que a função devolve o token e o user_agent. Guardar esse segundo valor separa uma integração estável de uma que falha de forma intermitente.
Passo 4: envie o token no campo g-recaptcha-response
O token vai para o formulário no campo g-recaptcha-response. Quando a API devolve um user_agent, use exatamente esse valor no cabeçalho — o back-end Enterprise compara o contexto do token com quem o apresenta:
async function submitForm(token, userAgent) {
const headers = { "Content-Type": "application/x-www-form-urlencoded" };
if (userAgent) {
headers["User-Agent"] = userAgent;
}
const response = await fetch("https://example.com/api/login", {
method: "POST",
headers,
body: new URLSearchParams({
username: "user",
password: "pass",
"g-recaptcha-response": token,
}),
});
console.log(`Response status: ${response.status}`);
return response;
}
Antes de dar como pronto, confira três pontos:
- o token viaja no corpo do POST, não na query string
- a
pageurlenviada é a mesma página do formulário - o
User-Agentda requisição é o que veio deres.php
Erros mais comuns e como corrigir
| Erro | Causa | Correção |
|---|---|---|
ERROR_WRONG_USER_KEY |
Formato da chave de API inválido | A chave tem 32 caracteres; copie-a de novo no painel |
ERROR_KEY_DOES_NOT_EXIST |
Chave não reconhecida | Confirme a chave ativa em captchaai.com |
ERROR_ZERO_BALANCE |
Saldo insuficiente | Recarregue a conta antes de reenviar a fila |
ERROR_BAD_TOKEN_OR_PAGEURL |
sitekey ou pageurl incorretos |
Extraia o k= do anchor Enterprise e use a URL completa da página |
ERROR_CAPTCHA_UNSOLVABLE |
A tarefa não pôde ser resolvida | Verifique se a sitekey é mesmo Enterprise v2 e reenvie com backoff exponencial |
| Token recusado pelo site | User-Agent diferente do usado na resolução | Aplique o user_agent devolvido em res.php nos cabeçalhos |
Script completo em Node.js
Os quatro passos em um arquivo só, com as constantes no topo para trocar:
const API_KEY = "YOUR_API_KEY";
const SITE_KEY = "6LdxxXXxAAAAAAcXxxXxxX91xxxxxxxx8xxOx7A";
const PAGE_URL = "https://staging.example.com/qa-login";
const ACTION = "LOGIN"; // optional — omit if not in anchor URL
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function solveRecaptchaV2Enterprise() {
// Submit task
const submitParams = new URLSearchParams({
key: API_KEY,
method: "userrecaptcha",
googlekey: SITE_KEY,
pageurl: PAGE_URL,
enterprise: "1",
action: ACTION,
json: "1",
});
const submitRes = await fetch(
`https://ocr.captchaai.com/in.php?${submitParams}`
);
const submitData = await submitRes.json();
if (submitData.status !== 1) {
throw new Error(`Submit error: ${submitData.request}`);
}
const taskId = submitData.request;
console.log(`Task ID: ${taskId}`);
// Poll for result
await delay(20000);
for (let i = 0; i < 30; i++) {
const pollParams = new URLSearchParams({
key: API_KEY,
action: "get",
id: taskId,
json: "1",
});
const pollRes = await fetch(
`https://ocr.captchaai.com/res.php?${pollParams}`
);
const pollData = await pollRes.json();
if (pollData.status === 1) {
return {
token: pollData.request,
userAgent: pollData.user_agent || "",
};
}
if (pollData.request !== "CAPCHA_NOT_READY") {
throw new Error(`Solve error: ${pollData.request}`);
}
await delay(5000);
}
throw new Error("Solve timed out");
}
(async () => {
const { token, userAgent } = await solveRecaptchaV2Enterprise();
console.log(`Token: ${token.substring(0, 60)}...`);
if (userAgent) console.log(`User-Agent: ${userAgent}`);
})();
Saída esperada:
Task ID: 73849562810
Token: 03AGdBq24PBCqLmOx2V4pGHJjkR2xZ1r...
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)...
Threads, tempo de resolução e escolha do plano
A CaptchaAI cobra por thread simultânea, não por resolução: cada plano inclui resoluções ilimitadas dentro das threads contratadas. Uma thread é um CAPTCHA em andamento — quando ele termina, ela aceita a próxima tarefa.
Como o Enterprise v2 resolve em menos de 60 s, dá para dimensionar pelo pior caso. Uma suíte de regressão que valida 30 formulários com CAPTCHA por execução termina assim:
| Plano | Threads | Lote de 30 formulários |
|---|---|---|
| BASIC (US$ 15/mês) | 5 | ~6 minutos |
| STANDARD (US$ 30/mês) | 15 | ~2 minutos |
| ADVANCE (US$ 90/mês) | 50 | vários branches em paralelo |
Um detalhe que costuma passar batido: com workers em sa-east-1 (São Paulo), o RTT até a API entra no orçamento de cada iteração do polling. Isso não muda o tempo de resolução, mas justifica o intervalo de 5 s — consultar a cada 1 s não traz o token mais cedo.
O escopo do fluxo é ambiente próprio ou QA autorizado, como o https://staging.example.com/qa-login do script: formulários seus e dados fictícios. Se a automação tocar dados pessoais reais, considere as obrigações da LGPD antes de registrar payloads em log.
Perguntas frequentes
O Enterprise v2 exige uma chave de API separada?
Não. É a mesma chave e o mesmo method=userrecaptcha do v2 padrão; muda só o enterprise=1 (e o action, quando o anchor traz sa=).
Quanto tempo leva cada resolução na prática?
O reCAPTCHA v2 Enterprise resolve em menos de 60 s. Por isso o exemplo espera 20 s antes da primeira consulta e cobre até 30 tentativas de 5 s antes do timeout.
Posso resolver vários desafios ao mesmo tempo?
Sim. Cada tarefa em andamento ocupa uma thread, então o paralelismo máximo é o número de threads do plano. Dispare os envios com Promise.all e mantenha uma fila na sua aplicação para não passar desse limite.
Por que o site recusa um token que a API marcou como resolvido?
Quase sempre por divergência de User-Agent: o token Enterprise carrega o contexto do navegador que o gerou. Use o user_agent devolvido na consulta e confirme que a pageurl é idêntica à do formulário.
Quais tipos de CAPTCHA esse mesmo fluxo cobre?
O mesmo par in.php / res.php atende as variantes de reCAPTCHA, Cloudflare Turnstile e Challenge, GeeTest v3 e CAPTCHAs de imagem e de grade — muda o method, não a lógica do script. O hCaptcha e o FunCaptcha (Arkose Labs) não são suportados, e o GeeTest v4 aparece apenas como "em breve".
Coloque o fluxo para rodar
Pegue sua chave de API em captchaai.com, acrescente enterprise=1 às requisições v2 e valide o fluxo no seu staging antes de levá-lo ao pipeline.