Explainers

Callback do reCAPTCHA v2: como funciona e como acioná-lo

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:

  1. O atributo data-callback, quando o widget está declarado no HTML.
  2. A configuração passada para grecaptcha.render, quando o widget nasce em JavaScript.
  3. 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.


Guias relacionados

Os comentários estão desativados para este artigo.