Se a API da CaptchaAI devolve um token válido e o navegador continua travado no mesmo CAPTCHA, o problema está quase sempre em um destes cinco pontos: campo errado, callback que não dispara, token expirado, pageurl incorreto ou detecção de bot que nada tem a ver com o CAPTCHA.
Veja abaixo o diagnóstico rápido e a correção de cada caso, com código para Selenium — a mesma lógica vale para Playwright e Puppeteer.
Por que o token passa na API mas falha no navegador
- Seletor errado para o campo de resposta → o token não é definido no elemento certo e o formulário é enviado sem ele.
- Tempo longo demais entre resolver e enviar → o token expira e o site volta a mostrar o CAPTCHA.
- Callback do
grecaptchanão foi chamado → o botão do formulário continua desativado. - URL usada na resolução não bate com a real → o
pageurlenviado à API está errado e o token fica inválido para o domínio. - O navegador entrega sinais de automação → mesmo com o token aceito, a detecção de bot bloqueia o acesso.
Diagnóstico rápido: sintoma → causa → correção
| Sintoma | Causa provável | Correção |
|---|---|---|
| Token definido, mas o formulário rejeita | Callback não disparou | Localize e chame a função de callback |
| Botão de enviar continua desativado | É o callback que libera o botão | Dispare o callback logo após definir o token |
| "CAPTCHA inválido" depois de enviar | O token expirou | Reduza o tempo entre resolver e enviar |
| Erro 403 depois de enviar o formulário | Detecção de bot, não é o CAPTCHA | Ajuste as opções do navegador de QA |
Funciona com requests, falha no navegador |
pageurl diferente da URL real |
Use driver.current_url como pageurl |
Correção 1: como aplicar o token do reCAPTCHA v2 no campo certo
O token precisa ir para o campo g-recaptcha-response e o callback precisa disparar — só definir o valor no textarea não basta. Na prática são dois passos, nessa ordem:
- Tornar visível e preencher o textarea
g-recaptcha-response(pode haver mais de um na página). - Chamar a função de callback do
grecaptchapara liberar o botão de envio.
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
def inject_recaptcha_token(driver, token):
"""Properly inject reCAPTCHA v2 token in browser."""
# Step 1: Make the response textarea visible and set the value
driver.execute_script("""
// Find all response textareas (may be multiple on page)
var textareas = document.querySelectorAll('[name="g-recaptcha-response"]');
textareas.forEach(function(ta) {
ta.style.display = 'block';
ta.value = arguments[0];
});
// Also set via ID if present
var byId = document.getElementById('g-recaptcha-response');
if (byId) {
byId.style.display = 'block';
byId.value = arguments[0];
}
""", token)
# Step 2: Trigger the callback
driver.execute_script("""
// Try standard callback
if (typeof ___grecaptcha_cfg !== 'undefined') {
var clients = ___grecaptcha_cfg.clients;
for (var key in clients) {
var client = clients[key];
// Navigate nested structure to find callback
for (var prop in client) {
var val = client[prop];
if (val && typeof val === 'object') {
for (var subprop in val) {
var subval = val[subprop];
if (subval && typeof subval === 'object' && subval.callback) {
subval.callback(arguments[0]);
return;
}
}
}
}
}
}
// Fallback: try common callback names
if (typeof onCaptchaSuccess === 'function') onCaptchaSuccess(arguments[0]);
else if (typeof captchaCallback === 'function') captchaCallback(arguments[0]);
""", token)
# Usage
driver = webdriver.Chrome()
driver.get("https://example.com/form")
# ... solve CAPTCHA via CaptchaAI ...
inject_recaptcha_token(driver, token)
time.sleep(1)
driver.find_element(By.CSS_SELECTOR, "button[type=submit]").click()
Correção 2: formulários que só liberam o envio após o callback
- Muitos formulários mantêm o botão de envio desativado até o callback do CAPTCHA disparar.
- Localize a função de callback e chame-a manualmente — o snippet abaixo tenta duas abordagens.
def find_and_trigger_callback(driver, token):
"""Find the reCAPTCHA callback and trigger it."""
# Method 1: Extract callback from data attribute
callback_name = driver.execute_script("""
var widget = document.querySelector('.g-recaptcha');
if (widget) {
return widget.getAttribute('data-callback');
}
return null;
""")
if callback_name:
driver.execute_script(f"window['{callback_name}'](arguments[0]);", token)
return True
# Method 2: Extract from grecaptcha config
triggered = driver.execute_script("""
try {
var widgetId = 0;
var callback = ___grecaptcha_cfg.clients[widgetId].aa.l.callback;
if (typeof callback === 'function') {
callback(arguments[0]);
return true;
}
} catch(e) {}
return false;
""", token)
return triggered
Correção 3: tempo de vida do token — resolva e use na hora
O token do reCAPTCHA v2 expira poucos minutos após a resolução, e a janela útil é menor do que parece. Dois fatores já consomem tempo real antes mesmo de você tentar enviar o formulário:
- Latência de rede até os workers — mesmo com workers em
sa-east-1(São Paulo), a viagem de ida e volta soma tempo. - Tempo de polling em
in.php/res.php— cada ciclo de consulta acrescenta alguns segundos até o status virar1.
import requests
import time
def solve_and_inject_fast(driver, api_key, sitekey, page_url):
"""Solve CAPTCHA and inject token with minimal delay."""
# Submit solve request
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": api_key,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
"json": 1,
}, timeout=30)
task_id = resp.json()["request"]
# Poll for result
time.sleep(15)
for _ in range(24):
resp = requests.get("https://ocr.captchaai.com/res.php", params={
"key": api_key, "action": "get",
"id": task_id, "json": 1,
}, timeout=15)
data = resp.json()
if data.get("status") == 1:
token = data["request"]
# IMMEDIATELY inject — don't wait
inject_recaptcha_token(driver, token)
# Submit form within 5 seconds
time.sleep(0.5)
return True
if data["request"] != "CAPCHA_NOT_READY":
raise RuntimeError(data["request"])
time.sleep(5)
raise TimeoutError("Solve timeout")
Correção 4: pageurl correto em SPAs e CAPTCHA dentro de iframe
- CAPTCHA dentro de iframe: use a URL da página pai (
driver.current_url), não a URL do iframe. - SPA com navegação client-side: a URL atual pode já ter mudado desde o carregamento inicial — sempre leia
driver.current_urlno momento da resolução.
Se a API recebe um pageurl que não bate com nenhum desses dois casos, o token sai atrelado ao domínio ou à rota errada e o site rejeita:
def get_correct_pageurl(driver):
"""Get the URL that reCAPTCHA actually sees."""
# Check if CAPTCHA is in an iframe
iframes = driver.find_elements(By.TAG_NAME, "iframe")
for iframe in iframes:
src = iframe.get_attribute("src") or ""
if "recaptcha" in src or "anchor" in src:
# CAPTCHA is in iframe — use parent page URL
return driver.current_url
# For SPAs, use current URL (may differ from initial load)
return driver.current_url
# WRONG — using the URL you navigated to
pageurl = "https://example.com/form" # May have redirected
# CORRECT — using the URL the browser is actually on
pageurl = get_correct_pageurl(driver)
Correção 5: quando o bloqueio não é do CAPTCHA, e sim do navegador
Um token aceito não garante acesso: muitos sites rodam uma checagem de automação separada e bloqueiam mesmo depois do CAPTCHA passar. Antes de reenviar, ajuste pelo menos estes dois pontos no navegador de QA:
- Remova os indicadores de automação (
--disable-blink-features=AutomationControlled,excludeSwitches). - Sobrescreva o sinal de navegador automatizado via CDP antes do primeiro carregamento da página.
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
def create_qa_browser_driver():
"""Create a browser that avoids basic bot detection."""
options = Options()
# Remove automation indicators
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("useAutomationExtension", False)
# Set realistic window size
options.add_argument("--window-size=1920,1080")
driver = webdriver.Chrome(options=options)
# Override sinal de navegador automatizado
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
})
return driver
driver = create_qa_browser_driver()
Correção 6: token do Cloudflare Turnstile no navegador
O Cloudflare Turnstile usa um campo e um fluxo de callback diferentes do reCAPTCHA:
- Campo de resposta:
cf-turnstile-response(mantenhag-recaptcha-responsecomo fallback — alguns sites reaproveitam o mesmo formulário). - Callback: leia
data-callbackno widget e dispare a função manualmente, como no reCAPTCHA.
def inject_turnstile_token(driver, token):
"""Inject Turnstile token into the page."""
driver.execute_script("""
// Find Turnstile response input
var inputs = document.querySelectorAll(
'input[name="cf-turnstile-response"], ' +
'input[name="g-recaptcha-response"]'
);
inputs.forEach(function(input) {
input.value = arguments[0];
});
// Trigger change event
inputs.forEach(function(input) {
input.dispatchEvent(new Event('change', { bubbles: true }));
input.dispatchEvent(new Event('input', { bubbles: true }));
});
// Try Turnstile callback
if (window.turnstile) {
var widgets = document.querySelectorAll('[data-callback]');
widgets.forEach(function(w) {
var cb = w.getAttribute('data-callback');
if (typeof window[cb] === 'function') {
window[cb](arguments[0]);
}
});
}
""", token)
Lista de verificação de depuração
Se nada acima resolveu, rode este script no console (ou via driver.execute_script) antes de abrir um chamado. Ele reúne, em um só lugar:
- Se o textarea de resposta existe e já tem valor.
- Se o
grecaptchafoi carregado e qual é o nome do callback configurado no widget. - A URL atual do navegador, para comparar com o
pageurlenviado à API.
def debug_captcha_injection(driver, token):
"""Print debug info to diagnose injection failures."""
info = driver.execute_script("""
var result = {};
// Check textarea exists
var ta = document.querySelector('[name="g-recaptcha-response"]');
result.textarea_found = !!ta;
result.textarea_value_set = ta ? ta.value.length > 0 : false;
// Check reCAPTCHA loaded
result.grecaptcha_loaded = typeof grecaptcha !== 'undefined';
// Check for callback
var widget = document.querySelector('.g-recaptcha');
result.has_data_callback = widget ? !!widget.getAttribute('data-callback') : false;
result.callback_name = widget ? widget.getAttribute('data-callback') : null;
// Current URL
result.current_url = window.location.href;
// Check for errors in console (basic)
result.has_submit_button = !!document.querySelector('button[type=submit], input[type=submit]');
return result;
""")
for key, value in info.items():
print(f" {key}: {value}")
return info
Perguntas frequentes
Por que o mesmo token funciona pela API, mas falha no navegador?
Quase sempre porque o callback do reCAPTCHA não disparou. O formulário depende dele para liberar o botão de envio ou preencher campos ocultos — só definir o valor no textarea raramente basta.
Preciso pagar de novo a cada nova tentativa de reenvio do token?
Não. A CaptchaAI cobra por thread simultânea, não por tentativa — o plano BASIC (US$ 15/mês, 5 threads) deixa testar o mesmo fluxo quantas vezes precisar, sem custo extra. O que consome tempo é o polling em in.php/res.php, não o número de tentativas de aplicar o token.
A lógica do callback é igual em Playwright, Puppeteer e Selenium?
Sim: definir o token no campo certo e disparar o callback via JavaScript — só muda a forma de rodar o script:
- Selenium:
driver.execute_script(...) - Playwright:
page.evaluate(...) - Puppeteer:
page.evaluate(...)
Devo automatizar em modo headless ou headful?
Comece em modo headful para confirmar que o fluxo funciona. Alguns sites detectam navegadores headless e rejeitam o token mesmo com o CAPTCHA resolvido — migre para headless só depois de validar esse comportamento.
Guias Relacionados
Continue o diagnóstico com estes guias:
Próximos passos
Depois de resolver a injeção, estes passos ajudam a consolidar o fluxo: