Se o token do reCAPTCHA v2 chega certinho mas o formulário não reage, o problema quase sempre é o mesmo: o site usa um callback em JavaScript em vez do campo oculto g-recaptcha-response. A correção não está na chamada de API — está em descobrir o nome dessa função e chamá-la você mesmo, com o token em mãos.
Esse padrão é comum em formulários de login e cadastro que validam o reCAPTCHA no próprio JavaScript da página, sem depender do envio tradicional do formulário. Preencher o campo oculto nesses casos não faz nada: a página ignora o valor e fica esperando o disparo da função.
Este guia mostra como identificar um reCAPTCHA v2 com callback, resolvê-lo pela API da CaptchaAI e invocar a função certa em Python, Node.js e PHP. A chamada à API é idêntica ao fluxo padrão — só muda a etapa de entrega do token.
Primeira vez com a API da CaptchaAI? Comece pelo fluxo padrão do reCAPTCHA v2 e volte aqui quando encontrar um site que usa callback.
Pré-requisitos para resolver o callback
Antes de sair copiando código, separe estes cinco itens — sem eles nenhum dos exemplos abaixo funciona:
- Chave de API da CaptchaAI — obtenha a sua em captchaai.com/api.php. É uma sequência de 32 caracteres.
- URL da página de destino — o endereço completo onde o widget reCAPTCHA v2 é carregado.
- Sitekey do reCAPTCHA v2 — a chave pública (a sitekey) vinculada à instância do widget.
- Ferramenta de automação do navegador — Selenium, Puppeteer ou Playwright. Você precisa executar JavaScript na página para invocar o callback.
- Nome da função de callback — a função JavaScript que o site espera receber o token resolvido.
Como saber se o reCAPTCHA v2 do site usa callback
No fluxo padrão, o token resolvido vai para uma textarea oculta chamada g-recaptcha-response. Numa implementação com callback, essa área nem é usada — o site chama uma função JavaScript direto. Normalmente é decisão de arquitetura, não capricho: frameworks de single-page application preferem controlar toda a validação em JavaScript, sem depender do envio nativo do formulário, ou o time quer disparar analytics e verificações extras no momento exato em que o token chega. Nenhum dos dois motivos muda como você resolve o CAPTCHA pela API — só como você entrega o token no fim. Três sinais no código-fonte confirmam qual dos dois você está vendo.
Três sinais no código-fonte
O primeiro é o atributo data-callback na div do widget. Inspecione o código-fonte da página:
<div class="g-recaptcha"
data-sitekey="6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-"
data-callback="SubmitToken">
</div>
Se data-callback existir, o site usa callback. O valor (SubmitToken, no exemplo acima) é o nome da função que você vai chamar depois.
O segundo é a chamada grecaptcha.render() no JavaScript da página:
grecaptcha.render('recaptcha-container', {
sitekey: '6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-',
callback: userVerified
});
A propriedade callback nomeia a função — neste exemplo, userVerified.
O terceiro é abrir o console do navegador na página de destino e inspecionar a configuração interna do reCAPTCHA:
___grecaptcha_cfg.clients[0]
Navegue na árvore de objetos até achar a propriedade callback. O caminho exato varia por site — pode ser clients[0].aa.l.callback ou outro, dependendo da versão do reCAPTCHA e do nível de minificação do JavaScript. Se a página tiver mais de um widget, verifique clients[1], clients[2] e assim por diante.
Script para detectar o callback automaticamente
Cole isto no console do navegador para encontrar o nome da função sem precisar navegar manualmente pela árvore de objetos:
// Check data-callback attributes
document.querySelectorAll('[data-callback]').forEach(el => {
console.log('data-callback:', el.getAttribute('data-callback'));
});
// Check internal config
if (typeof ___grecaptcha_cfg !== 'undefined') {
Object.keys(___grecaptcha_cfg.clients).forEach(key => {
const client = ___grecaptcha_cfg.clients[key];
console.log(`Client ${key}:`, JSON.stringify(client, null, 2));
});
}
Callback x fluxo padrão do reCAPTCHA v2: o que muda
A chamada para a API da CaptchaAI é idêntica nos dois casos. Passo a passo, é só a etapa 4 que diverge:
- 1. Enviar para a CaptchaAI —
method=userrecaptcha+ sitekey + URL da página. Igual nos dois fluxos. - 2. Consultar o resultado —
action=get+ ID do captcha. Igual nos dois fluxos. - 3. Receber o token — mesmo formato de token nos dois casos.
As duas etapas finais são onde os fluxos realmente se separam:
- 4. Entregar o token — no padrão, você define o valor do campo
g-recaptcha-response; no callback, você chama a função de callback passando o token. - 5. Enviar o formulário — no padrão, você dispara o submit; com callback, isso geralmente já é automático.
Atenção: não defina g-recaptcha-response em implementações com callback. A página ignora esse campo e só reage quando a função de callback dispara. Se você preencher o campo sem chamar o callback, o CaptchaAI mostra o token resolvido, mas para o site o CAPTCHA nunca foi respondido.
Por que usar a CaptchaAI para o callback do reCAPTCHA v2
- Mesma chamada de API — o fluxo de envio e consulta é idêntico ao padrão reCAPTCHA v2, sem parâmetro extra nenhum.
- Taxa de sucesso — alta nos dois casos, porque callback e fluxo padrão usam o mesmo solucionador de reCAPTCHA v2.
- Velocidade de resolução — menos de 60 segundos.
- Compatibilidade do token — o token retornado funciona tanto com a injeção em
g-recaptcha-responsequanto com a chamada de callback. - Preços — planos por thread a partir do BASIC (US$ 15/mês, 5 threads), com resoluções ilimitadas por thread.
O token que a CaptchaAI devolve é o mesmo, não importa como o site implementa o reCAPTCHA v2. A diferença fica inteiramente no seu código do lado do cliente — em como você entrega esse token à página.
Se os formulários que você testa rodam em infraestrutura no Brasil, vale rodar o worker de automação numa região como sa-east-1 (São Paulo) da AWS: reduz o RTT entre carregar a página, consultar res.php e disparar o callback — o que faz diferença em páginas com vários widgets reCAPTCHA.
Fluxo de resolução passo a passo
Do envio à página processando o resultado, é este o caminho que o token percorre:
Page → extract sitekey + pageurl + callback name
↓
POST to in.php (method=userrecaptcha)
↓
receive captcha ID
↓
wait 15–20 seconds
↓
GET res.php (action=get, id=…)
↓ ↓
CAPCHA_NOT_READY status=1 → token
(wait 5s, retry) ↓
invoke callback(token)
↓
site processes token automatically
Implementação em Python com Selenium
O código abaixo detecta o nome do callback direto no DOM, resolve o token pela API da CaptchaAI e invoca a função com execute_script:
import time
import requests
from selenium import webdriver
from selenium.webdriver.common.by import By
API_KEY = "YOUR_CAPTCHAAI_API_KEY"
SITEKEY = "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-"
PAGE_URL = "https://staging.example.com/qa-login"
CALLBACK_NAME = "SubmitToken" # The callback function name from the page
SUBMIT_URL = "https://ocr.captchaai.com/in.php"
RESULT_URL = "https://ocr.captchaai.com/res.php"
def solve_recaptcha_v2(api_key, sitekey, pageurl):
"""Submit a reCAPTCHA v2 task and return the solved token."""
# Step 1: Submit the captcha
submit_resp = requests.post(
SUBMIT_URL,
data={
"key": api_key,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": pageurl,
"json": 1,
},
timeout=30,
)
submit_resp.raise_for_status()
submit_data = submit_resp.json()
if submit_data.get("status") != 1:
raise RuntimeError(f"Submit failed: {submit_data}")
captcha_id = submit_data["request"]
print(f"Task created — captcha ID: {captcha_id}")
# Step 2: Wait before first poll
time.sleep(15)
# Step 3: Poll for result
for _ in range(60):
result_resp = requests.get(
RESULT_URL,
params={
"key": api_key,
"action": "get",
"id": captcha_id,
"json": 1,
},
timeout=30,
)
result_resp.raise_for_status()
result_data = result_resp.json()
if result_data.get("request") == "CAPCHA_NOT_READY":
time.sleep(5)
continue
if result_data.get("status") == 1:
return result_data["request"]
raise RuntimeError(f"Polling error: {result_data}")
raise TimeoutError("reCAPTCHA v2 solve timed out")
def detect_callback_name(driver):
"""Detect the reCAPTCHA callback function name from the page."""
# Try data-callback attribute first
callback = driver.execute_script("""
const el = document.querySelector('[data-callback]');
if (el) return el.getAttribute('data-callback');
return null;
""")
if callback:
return callback
# Try internal reCAPTCHA config
callback = driver.execute_script("""
if (typeof ___grecaptcha_cfg === 'undefined') return null;
const clients = ___grecaptcha_cfg.clients;
for (const key of Object.keys(clients)) {
const client = clients[key];
// Walk the object tree to find a callback function
const json = JSON.stringify(client);
const match = json.match(/"callback":"(\\w+)"/);
if (match) return match[1];
}
return null;
""")
return callback
# Main workflow
driver = webdriver.Chrome()
driver.get(PAGE_URL)
# Detect the callback name (or use the known name)
detected = detect_callback_name(driver)
callback_name = detected or CALLBACK_NAME
print(f"Using callback: {callback_name}")
# Solve the CAPTCHA
token = solve_recaptcha_v2(API_KEY, SITEKEY, PAGE_URL)
print(f"Solved token: {token[:80]}...")
# Invoke the callback with the token
driver.execute_script(f"{callback_name}(arguments[0]);", token)
print("Callback invoked — site should process the token automatically")
# Wait for the page to process
time.sleep(3)
driver.quit()
O que o código faz:
- Envia a sitekey e o pageurl para
in.phpcommethod=userrecaptcha— igual ao fluxo padrão. - Consulta
res.phpa cada 5 segundos até o token ficar pronto. - Detecta o nome da função de callback direto no DOM da página.
- Chama o callback com o token resolvido usando
execute_script. - Dali em diante, o JavaScript do próprio site cuida do resto — envio de formulário, validação ou redirecionamento.
Implementação em Node.js com Puppeteer
A lógica é a mesma do Python — só troca a biblioteca de automação:
const puppeteer = require("puppeteer");
const API_KEY = "YOUR_CAPTCHAAI_API_KEY";
const SITEKEY = "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-";
const PAGE_URL = "https://staging.example.com/qa-login";
const CALLBACK_NAME = "SubmitToken";
const SUBMIT_URL = "https://ocr.captchaai.com/in.php";
const RESULT_URL = "https://ocr.captchaai.com/res.php";
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function solveRecaptchaV2(apiKey, sitekey, pageurl) {
// Step 1: Submit the captcha
const submitResp = await fetch(SUBMIT_URL, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
key: apiKey,
method: "userrecaptcha",
googlekey: sitekey,
pageurl: pageurl,
json: "1",
}),
});
const submitData = await submitResp.json();
if (submitData.status !== 1) {
throw new Error(`Submit failed: ${JSON.stringify(submitData)}`);
}
const captchaId = submitData.request;
console.log(`Task created — captcha ID: ${captchaId}`);
// Step 2: Wait before first poll
await sleep(15_000);
// Step 3: Poll for result
for (let i = 0; i < 60; i++) {
const resultResp = await fetch(
`${RESULT_URL}?${new URLSearchParams({
key: apiKey,
action: "get",
id: captchaId,
json: "1",
})}`
);
const resultData = await resultResp.json();
if (resultData.request === "CAPCHA_NOT_READY") {
await sleep(5_000);
continue;
}
if (resultData.status === 1) {
return resultData.request;
}
throw new Error(`Polling error: ${JSON.stringify(resultData)}`);
}
throw new Error("reCAPTCHA v2 solve timed out");
}
async function detectCallbackName(page) {
return page.evaluate(() => {
// Try data-callback attribute
const el = document.querySelector("[data-callback]");
if (el) return el.getAttribute("data-callback");
// Try internal config
if (typeof ___grecaptcha_cfg !== "undefined") {
const clients = ___grecaptcha_cfg.clients;
for (const key of Object.keys(clients)) {
const json = JSON.stringify(clients[key]);
const match = json.match(/"callback":"(\w+)"/);
if (match) return match[1];
}
}
return null;
});
}
(async () => {
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
await page.goto(PAGE_URL, { waitUntil: "networkidle2" });
// Detect callback
const detected = await detectCallbackName(page);
const callbackName = detected || CALLBACK_NAME;
console.log(`Using callback: ${callbackName}`);
// Solve the CAPTCHA
const token = await solveRecaptchaV2(API_KEY, SITEKEY, PAGE_URL);
console.log(`Solved token: ${token.slice(0, 80)}...`);
// Invoke the callback
await page.evaluate(
(name, tkn) => {
window[name](tkn);
},
callbackName,
token
);
console.log("Callback invoked — site should process the token automatically");
await sleep(3_000);
await browser.close();
})();
Implementação em PHP: resolução no servidor
A chamada de API é idêntica em PHP. Só que invocar o callback exige um navegador de verdade, então este exemplo cobre apenas a resolução — a parte de servidor. Para a etapa de injeção, use uma ferramenta de navegador headless (por exemplo, PHP WebDriver).
<?php
$apiKey = "YOUR_CAPTCHAAI_API_KEY";
$sitekey = "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-";
$pageurl = "https://staging.example.com/qa-login";
// Step 1: Submit
$submit = file_get_contents("https://ocr.captchaai.com/in.php?" . http_build_query([
"key" => $apiKey,
"method" => "userrecaptcha",
"googlekey" => $sitekey,
"pageurl" => $pageurl,
"json" => 1,
]));
$submitData = json_decode($submit, true);
if ($submitData["status"] !== 1) {
die("Submit failed: " . $submit);
}
$captchaId = $submitData["request"];
echo "Task created — captcha ID: $captchaId\n";
// Step 2: Wait and poll
sleep(15);
for ($i = 0; $i < 60; $i++) {
$result = file_get_contents("https://ocr.captchaai.com/res.php?" . http_build_query([
"key" => $apiKey,
"action" => "get",
"id" => $captchaId,
"json" => 1,
]));
$resultData = json_decode($result, true);
if ($resultData["request"] === "CAPCHA_NOT_READY") {
sleep(5);
continue;
}
if ($resultData["status"] === 1) {
$token = $resultData["request"];
echo "Solved token: " . substr($token, 0, 80) . "...\n";
// Pass $token to your browser automation to invoke the callback
break;
}
die("Polling error: " . $result);
}
Com o token em mãos, use uma ferramenta de automação do navegador (por exemplo, php-webdriver) para executar:
SubmitToken("TOKEN_FROM_CAPTCHAAI");
Erros comuns e solução de problemas
A maioria dos problemas com callback cai em uma destas causas — confira a tabela antes de sair depurando linha por linha:
| Sintoma | Causa provável | O que fazer |
|---|---|---|
| Token resolvido, mas a página não reage | Você definiu g-recaptcha-response em vez de chamar o callback |
Confira se o widget tem data-callback ou callback em grecaptcha.render() e invoque essa função com o token |
ReferenceError: SubmitToken is not defined, ou função não definida |
Nome de callback errado, não carregado ainda, ou minificado/ofuscado no código-fonte | Confira de novo data-callback, grecaptcha.render() ou a configuração interna; aguarde a página carregar por completo; em sites minificados, use o console em runtime para achar a referência real da função |
| Callback aciona a instância errada de reCAPTCHA | Em páginas com vários widgets, o callback usado está em outro índice de cliente | Verifique ___grecaptcha_cfg.clients[1], clients[2] etc. e combine cada widget com o formulário correspondente |
| O callback nunca dispara, mesmo com o nome certo | Você chamou o callback antes da página estar pronta — a função ainda não existe no contexto da página | Aguarde DOMContentLoaded ou networkidle antes de invocar |
| Comportamento lembra reCAPTCHA Invisible | Algumas implementações invisíveis também usam callback | Veja se data-size="invisible" está presente — se estiver, consulte Como resolver reCAPTCHA Invisible com a API |
| O token funciona no fluxo padrão, mas falha nesta página | Você está diante de uma implementação com callback | Siga os passos de detecção descritos acima e mude para a invocação do callback |
ERROR_BAD_TOKEN_OR_PAGEURL |
O par sitekey/pageurl é inválido | Extraia os dois valores de novo, direto da página — sem relação com callback ou fluxo padrão |
| O callback dispara mais de uma vez | O listener é registrado a cada re-render (comum em SPAs) ou o script chama execute_script/page.evaluate mais de uma vez no mesmo token |
Chame o callback uma única vez por token resolvido e, se o front-end for reativo, registre o listener fora do ciclo de render |
ERROR_CAPTCHA_UNSOLVABLE |
O desafio não pôde ser resolvido | Tente de novo com uma requisição nova — não é específico de callback |
Referência completa: consulte Erros comuns na resolução do reCAPTCHA v2 para os demais códigos de erro da API.
Projeto completo no GitHub
Quer um projeto pronto, com setup de ambiente, polling, retentativas e tratamento de erro já resolvidos? Veja o exemplo executável completo no GitHub →
Perguntas frequentes
Meu formulário não avança mesmo com o token resolvido — o que verificar primeiro?
Confira se o widget tem data-callback ou a propriedade callback dentro de grecaptcha.render(). Se tiver, preencher g-recaptcha-response não faz nada — você precisa chamar essa função com o token. É a causa mais comum desse sintoma.
Como encontro o nome da função de callback?
Três lugares confirmam: o atributo data-callback na div do reCAPTCHA, a propriedade callback dentro de grecaptcha.render() no JavaScript da página, ou o objeto ___grecaptcha_cfg.clients[0] no console — navegue na árvore até achar callback.
Selenium, Puppeteer e Playwright resolvem o callback do mesmo jeito?
A chamada para a API da CaptchaAI é idêntica nos três. Só muda a sintaxe para rodar JavaScript na página — execute_script no Selenium, page.evaluate() no Puppeteer e no Playwright. A lógica de detectar e disparar o callback é a mesma.
Preciso de um navegador para chamar o callback?
Sim. O callback é uma função JavaScript que só existe no contexto da página carregada. Você precisa de Selenium, Puppeteer, Playwright ou outra ferramenta que execute JavaScript ali dentro — uma chamada de API sozinha não alcança essa função.
Esse fluxo serve para automação em produção, ou só para testes manuais?
Serve para automação recorrente, desde que em ambiente autorizado — QA, staging ou monitoramento próprio. Se o pipeline grava o token ou o payload de depuração em log, trate isso como dado sensível sob a LGPD e mantenha a retenção curta.
Comece a resolver o callback do reCAPTCHA v2
- Obtenha sua chave de API — captchaai.com/api.php
- Detecte o nome do callback — confira
data-callback,grecaptcha.render()ou a configuração interna - Copie o código Python, Node.js ou PHP acima — troque os placeholders pela sua chave, sitekey, pageurl e nome do callback
- Rode — o token chega em menos de 60 segundos, o callback dispara e a página processa o resultado
- Travou? Comece por Erros comuns na resolução do reCAPTCHA v2 ou leia a documentação completa da API CaptchaAI
Guias relacionados
- Como resolver reCAPTCHA Invisible em Python
- Cloudflare Turnstile e reCAPTCHA v2 no mesmo site
- Guia avançado da API de avaliação do reCAPTCHA Enterprise