O token chegou, você gravou o valor na textarea g-recaptcha-response e o botão continua cinza. Não é falha da resolução: o formulário não reconhece o desafio porque o callback — a função JavaScript que o site registra no widget — nunca rodou. Quem marca a caixinha no navegador dispara esse callback sem perceber; quem injeta um token resolvido pela API precisa chamá-lo explicitamente.
Comece pela triagem: qual dos quatro casos é o seu
Antes de mexer no script, identifique o sintoma. Quase todo relato de "token injetado e nada acontece" cai em uma destas quatro linhas.
| Sintoma | Causa provável | O que fazer |
|---|---|---|
| Formulário ainda desativado após a injeção | O callback não foi acionado | Localize a função e chame-a com o token |
ReferenceError: function not defined |
Callback definido dentro de um closure | Use o plano B com ___grecaptcha_cfg |
| Token injetado, mas nenhuma requisição AJAX sai | O callback dispara AJAX, não o submit | Leia o corpo da função antes de replicar |
| Token aceito, mas a página mostra erro | O token expirou antes do callback | Resolva mais perto do momento do envio |
Um teste rápido separa os dois mundos: se o token funciona quando você chama a API isoladamente, o problema está na camada de automação, não na resolução. Na prática, a primeira linha responde pela maioria dos casos — e é dela que o resto do artigo trata.
Por que o formulário depende do callback
O widget do reCAPTCHA v2 produz duas saídas independentes. A primeira é o token, gravado numa textarea escondida e validado depois no backend. A segunda é um evento: concluído o desafio, o script do Google executa a função declarada em data-callback, passando o token como único argumento.
É nessa função que mora quase toda a lógica útil do formulário — habilitar o botão, preencher um campo hidden, disparar uma requisição AJAX. Sem executá-la, o envio simplesmente não acontece.
<div class="g-recaptcha"
data-sitekey="6Le-SITEKEY"
data-callback="onCaptchaSuccess"
data-expired-callback="onCaptchaExpired">
</div>
<script>
function onCaptchaSuccess(token) {
document.getElementById('submit-btn').disabled = false;
document.getElementById('captcha-token').value = token;
}
</script>
Guarde dois detalhes: onCaptchaSuccess é global e recebe a string do token. São eles que definem como chamá-la depois.
Três formas de descobrir o nome do callback
Nunca deixe o nome da função fixo no código do teste: ele muda entre ambientes e, às vezes, entre deploys. Extraia do DOM nesta ordem — cada método cobre um caso que o anterior não alcança:
- O atributo
data-callback, quando o widget está declarado no HTML. - A configuração passada para
grecaptcha.render, quando o widget nasce em JavaScript. - A interceptação do próprio
render, quando a função é anônima.
Método 1: leia o atributo data-callback
// In browser console
const widget = document.querySelector('.g-recaptcha');
const callbackName = widget?.getAttribute('data-callback');
console.log('Callback:', callbackName);
Funciona na maioria dos formulários estáticos e custa uma linha. Se o retorno for null, siga adiante.
Método 2: procure a chamada grecaptcha.render
Muitos sites renderizam o widget por JavaScript e passam o callback na configuração, sem atributo no HTML:
// Search page source for grecaptcha.render
document.querySelectorAll('script:not([src])').forEach(s => {
if (s.textContent.includes('grecaptcha.render')) {
console.log(s.textContent.match(/callback\s*:\s*(\w+)/)?.[1]);
}
});
Método 3: intercepte o registro do widget
Quando o callback é anônimo dentro de um closure, os métodos anteriores não acham nome algum. Rode isto no DevTools antes de a página carregar (Sources → Snippets):
const origRender = grecaptcha.render;
grecaptcha.render = function(container, params) {
console.log('Render callback:', params.callback);
console.log('Expired callback:', params['expired-callback']);
return origRender.apply(this, arguments);
};
Só a extração dinâmica sobrevive a um deploy do time de front-end.
Acionando o callback em um endpoint de QA autorizado
O exemplo abaixo é o padrão que uma equipe de QA monta para validar o próprio formulário de login em staging: resolve o desafio pela API, injeta o token e chama o callback pelo nome extraído do DOM. Sem nome disponível, cai no plano B, que percorre ___grecaptcha_cfg. Vale registrar o enquadramento: esse padrão serve para ambientes que você controla ou tem autorização explícita para testar.
Python (Selenium)
import requests
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
API_KEY = "YOUR_API_KEY"
driver = webdriver.Chrome()
driver.get("https://staging.example.com/qa-login")
# Extract sitekey and callback
sitekey = driver.find_element(
By.CSS_SELECTOR, ".g-recaptcha"
).get_attribute("data-sitekey")
callback = driver.find_element(
By.CSS_SELECTOR, ".g-recaptcha"
).get_attribute("data-callback")
# Solve with CaptchaAI
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": driver.current_url,
"json": "1",
}).json()
task_id = resp["request"]
token = None
for _ in range(24):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": task_id, "json": "1"
}).json()
if result["status"] == 1:
token = result["request"]
break
# Inject token into textarea
driver.execute_script("""
document.querySelector('textarea[name="g-recaptcha-response"]').value = arguments[0];
""", token)
# Trigger the callback
if callback:
driver.execute_script(f"window['{callback}'](arguments[0]);", token)
print(f"Triggered callback: {callback}")
else:
# Fallback: try ___grecaptcha_cfg
driver.execute_script("""
try {
var widgetId = Object.keys(___grecaptcha_cfg.clients)[0];
var callback = ___grecaptcha_cfg.clients[widgetId].aa.l.callback;
if (typeof callback === 'function') callback(arguments[0]);
} catch(e) {}
""", token)
print("Triggered callback via ___grecaptcha_cfg")
Repare no laço de consulta: 24 tentativas com 5 s de intervalo. Como a CaptchaAI cobra por thread simultânea e não por resolução, esse polling não gera custo por tentativa — o que limita uma suíte noturna é a concorrência do plano, de BASIC (US$ 15/mês, 5 threads) a VIP-3 (US$ 7.500/mês, 5.000 threads).
Um detalhe de infraestrutura para quem roda os workers em São Paulo (sa-east-1) e testa um formulário hospedado fora: o RTT entra no orçamento de tempo do cenário, e o timeout do polling precisa contemplá-lo.
JavaScript (Puppeteer)
const puppeteer = require('puppeteer');
// After solving and getting the token...
await page.evaluate((token, callbackName) => {
// Set textarea value
const textarea = document.querySelector(
'textarea[name="g-recaptcha-response"]'
);
textarea.value = token;
textarea.style.display = 'block'; // sometimes hidden
// Trigger callback
if (callbackName && typeof window[callbackName] === 'function') {
window[callbackName](token);
console.log(`Called ${callbackName}()`);
} else {
// Fallback: search grecaptcha config
try {
const clients = ___grecaptcha_cfg.clients;
const widgetId = Object.keys(clients)[0];
const cb = clients[widgetId]?.aa?.l?.callback;
if (typeof cb === 'function') cb(token);
} catch (e) {}
}
}, token, callbackName);
A versão em Node.js muda um detalhe importante: o page.evaluate recebe o token e o nome do callback como argumentos serializados — nada do escopo do seu script existe dentro da página.
Quando o site não declara callback algum
Há uma família inteira de formulários que ignora data-callback e consulta grecaptcha.getResponse() no envio. Não existe função a chamar: o que resolve é substituir o próprio getResponse para que ele devolva o token injetado.
driver.execute_script("""
const token = arguments[0];
document.querySelector('textarea[name="g-recaptcha-response"]').value = token;
// Override getResponse to return the token
if (typeof grecaptcha !== 'undefined') {
grecaptcha.getResponse = function() { return token; };
}
""", token)
# Then submit the form normally
driver.find_element(By.CSS_SELECTOR, "form").submit()
Depois disso o envio segue o caminho normal do formulário, e a verificação acontece por inteiro no servidor — o cenário mais estável dos quatro, porque não depende de nenhuma função declarada pelo site.
O callback de expiração pode travar tudo de novo
O atributo irmão data-expired-callback dispara quando o token perde a validade, e muitos sites o usam para desabilitar o botão outra vez. Se o script resolve o desafio na abertura do cenário e só envia o formulário minutos depois, há uma janela real em que ele desfaz o que você acabou de destravar.
Duas regras práticas evitam o problema:
- Resolva perto do envio, não no início do teste.
- Confira se o site declara esse callback.
// Check for expired callback
const expiredCallback = document.querySelector('.g-recaptcha')
?.getAttribute('data-expired-callback');
console.log('Expired callback:', expiredCallback);
Se o valor voltar preenchido, o site relança o bloqueio assim que o token vencer.
Perguntas frequentes
Posso chamar o callback direto pelo console para testar?
Sim, e é a forma mais rápida de confirmar a hipótese. Com um token válido, execute window['nomeDoCallback']('TOKEN') no DevTools: se o botão destravar, seu script só precisa reproduzir essa chamada.
Preciso injetar o token na textarea se já vou acionar o callback?
Precisa. O callback cuida da interface; a textarea g-recaptcha-response é o campo enviado ao servidor. Sem a injeção, o backend recebe um valor vazio e recusa o envio.
A CaptchaAI resolve o reCAPTCHA v2 invisível e o Enterprise também?
Sim. Checkbox, invisible e Enterprise usam o mesmo método userrecaptcha da API. O que muda é o gatilho na página, não o envio da tarefa.
O mesmo mecanismo vale para hCaptcha?
Não, e vale registrar o limite: a CaptchaAI não resolve hCaptcha nem FunCaptcha. Os tipos suportados incluem reCAPTCHA v2 e v3, Cloudflare Turnstile e Cloudflare Challenge, GeeTest v3, imagem/OCR e grade de imagens, além de CaptchaFox (beta), Friendly Captcha (beta) e Lemin (beta).
O reCAPTCHA v3 tem callback igual?
Não. O v3 roda por grecaptcha.execute(), que devolve uma Promise, sem widget visível e sem data-callback. Você obtém o token no fluxo assíncrono e o envia junto com a requisição.
Resolva o reCAPTCHA v2 com o callback tratado corretamente
Crie sua conta na CaptchaAI e destrave o primeiro formulário do seu ambiente de staging hoje: obtenha sua chave de API em captchaai.com.