Algunos sitios web implementan reCAPTCHA v2 con una función de devolución de llamada en lugar del campo oculto estándar g-recaptcha-response. Cuando resuelves el CAPTCHA e inyectas el token en el campo oculto, no sucede nada: la página lo ignora. Esto se debe a que el sitio espera que usted llame a una función de JavaScript con el token.
Esta guía le muestra cómo detectar reCAPTCHA v2 basado en devolución de llamada, resolverlo a través de la API CaptchaAI e invocar la devolución de llamada correctamente. La llamada API es idéntica al estándar reCAPTCHA v2: solo cambia el paso de inyección del token.
¿Nuevo en reCAPTCHA v2? Comience conCómo resolver reCAPTCHA v2 usando APIpara el flujo estándar, luego regrese aquí para ver la variante de devolución de llamada.
Lo que necesitas antes de empezar
| Requisito | Detalles |
|---|---|
| Clave API CaptchaAI | Consigue uno decaptchaai.com/api.php. Cadena de 32 caracteres. |
| URL de la página de destino | La URL completa donde se carga el widget reCAPTCHA v2. |
| Clave del sitio reCAPTCHA v2 | La clave pública vinculada a la instancia del widget. |
| Herramienta de automatización del navegador | Selenium, Puppeteer o Playwright: necesita la ejecución de JavaScript para invocar la devolución de llamada. |
| Nombre de la función de devolución de llamada | La función de JavaScript que el sitio espera recibir el token. |
Cómo identificar una implementación de devolución de llamada
El estándar reCAPTCHA v2 escribe el token resuelto en un área de texto oculta g-recaptcha-response. Las implementaciones de devolución de llamada omiten eso y llaman directamente a una función de JavaScript. Aquí se explica cómo notar la diferencia.
Método 1: comprobar el atributo data-callback
Inspeccione el div del widget reCAPTCHA en la fuente de la página:
<div class="g-recaptcha"
data-sitekey="6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-"
data-callback="SubmitToken">
</div>
Si data-callback existe, el sitio utiliza una devolución de llamada. El valor (SubmitToken) es el nombre de la función que necesita.
Método 2: comprobar las llamadas grecaptcha.render()
Busque en el JavaScript de la página grecaptcha.render:
grecaptcha.render('recaptcha-container', {
sitekey: '6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-',
callback: userVerified
});
La propiedad callback nombra la función. En este caso, userVerified.
Método 3: inspeccionar la configuración interna de reCAPTCHA
Abra la consola del navegador en la página de destino y ejecute:
___grecaptcha_cfg.clients[0]
Navegue por el árbol de objetos para encontrar la propiedad callback. La ruta exacta varía según el sitio; puede ser clients[0].aa.l.callback u otra cosa dependiendo de la versión y minificación de reCAPTCHA. Si la página tiene varias instancias de reCAPTCHA, marque clients[1], clients[2], etc.
Script de detección rápida
Ejecute esto en la consola del navegador para buscar nombres de devolución de llamada automáticamente:
// 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));
});
}
En qué se diferencia la resolución de devolución de llamada del estándar v2
La llamada API a CaptchaAI es idéntica. La única diferencia es lo que haces con el token después de recibirlo.
| paso | Estándar v2 | Devolución de llamada v2 |
|---|---|---|
| 1. Enviar a CaptchaAI | method=userrecaptcha + clave del sitio + URL de página |
Lo mismo |
| 2. Consulta para obtener resultados | action=get + ID de captcha |
Lo mismo |
| 3. Recibir token | Mismo formato de token | Lo mismo |
| 4. Inyectar token | Establecer el valor del campo g-recaptcha-response |
Llame a la función de devolución de llamada con el token |
| 5. Enviar formulario | Activar envío de formulario | Generalmente automático: la devolución de llamada se encarga de ello |
Crítico: No configure
g-recaptcha-responseen implementaciones basadas en devolución de llamada. La página ignora ese campo y espera a que se active la función de devolución de llamada. Configurar el campo sin llamar a la devolución de llamada hará que parezca que el CAPTCHA nunca se resolvió.
Flujo de resolución
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
Implementación de Python (Selenio)
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()
Qué hace esto:
- Envía la clave del sitio y la URL de la página a
in.phpconmethod=userrecaptcha, idéntico al estándar v2. - Sondea
res.phpcada 5 segundos hasta que el token esté listo. - Detecta el nombre de la función de devolución de llamada de la página DOM.
- Llama a la función de devolución de llamada con el token resuelto usando
execute_script. - El propio JavaScript del sitio se encarga del resto: envío de formularios, validación o redireccionamiento de páginas.
Implementación de Node.js (titiritero)
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();
})();
implementación PHP
La llamada API es la misma en PHP. La invocación de devolución de llamada requiere un contexto de navegador, por lo que este ejemplo cubre la solución del lado del servidor. Utilice una herramienta de navegador sin cabeza (por ejemplo, PHP WebDriver) para el paso de inyección.
<?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);
}
Después de obtener el token en PHP, use una herramienta de automatización del navegador (por ejemplo, php-webdriver) para ejecutar:
SubmitToken("TOKEN_FROM_CAPTCHAAI");
Errores comunes
| # | error | que pasa | Solución |
|---|---|---|---|
| 1 | Configurar g-recaptcha-response en lugar de llamar a la devolución de llamada |
La página ignora el token: el formulario nunca se envía | Busque el nombre de la devolución de llamada e invoquelo con el token |
| 2 | Nombre de función de devolución de llamada incorrecto | Error de JavaScript: función no definida | Vuelva a verificar data-callback, grecaptcha.render() o la configuración interna |
| 3 | La devolución de llamada está en un índice de clientes diferente | Instancia reCAPTCHA incorrecta dirigida a páginas con múltiples widgets | Marque ___grecaptcha_cfg.clients[1], clients[2], etc. |
| 4 | Llamar a la devolución de llamada antes de que la página esté lista | Función aún no definida en el contexto de la página | Espere DOMContentLoaded o networkidle antes de invocar |
| 5 | Usando un nombre /minified ofuscado | El nombre de la devolución de llamada en la fuente está alterado | Utilice la consola del navegador en tiempo de ejecución para encontrar la referencia de función real |
| 6 | Mezclando callback v2 con invisible v2 | Algunas implementaciones invisibles también utilizan devoluciones de llamada. | Compruebe si data-size="invisible" está presente; si es así, consulteCómo resolver reCAPTCHA Invisible usando API |
Solución de problemas
Token resuelto pero la página no reacciona
La causa más común: configuraste g-recaptcha-response en lugar de llamar a la devolución de llamada. Compruebe si el widget tiene data-callback o callback en grecaptcha.render(). Si es así, debe invocar esa función.
ReferenceError: SubmitToken is not defined
La función de devolución de llamada aún no está cargada o el nombre es incorrecto. Prueba:
- Confirme el nombre inspeccionando
data-callbacko la configuración interna. - Espere a que la página se cargue por completo antes de invocarla.
- En sitios minimizados, la función se puede asignar a una variable; marque
window.SubmitTokenen la consola.
El token funciona en el estándar v2 pero falla en esta página
Probablemente esté enfrentando una implementación de devolución de llamada. Siga los pasos de detección descritos arriba para confirmar, luego cambie a la invocación de devolución de llamada.
ERROR_BAD_TOKEN_OR_PAGEURL
El par sitekey/pageurl no es válido. Este es un error de API, no relacionado con la devolución de llamada ni con el estándar. Vuelva a extraer ambos valores de la página.
La página tiene múltiples widgets reCAPTCHA
Cada widget puede tener su propia devolución de llamada. Inspeccione cada div g-recaptcha o verifique ___grecaptcha_cfg.clients para todas las instancias registradas. Haga coincidir el widget con el formulario al que se dirige.
ERROR_CAPTCHA_UNSOLVABLE
El desafío no se pudo resolver. Vuelva a intentarlo con una solicitud nueva. Esto no es específico de la devolución de llamada.
Para obtener la referencia completa del error, consulteErrores comunes de resolución de reCAPTCHA v2.
Por qué CaptchaAI funciona para esto
| factores | Detalle |
|---|---|
| Misma llamada API | El flujo de submit/poll es idéntico al estándar reCAPTCHA v2: no se necesitan parámetros adicionales |
| Tasa de éxito | 99,5%+ para reCAPTCHA v2 (la devolución de llamada y el estándar utilizan el mismo solucionador) |
| Velocidad de resolución | Menos de 60 segundos |
| Compatibilidad de tokens | El token devuelto funciona tanto con la inyección g-recaptcha-response como con la invocación de devolución de llamada. |
| Precio | Planes basados en subprocesos desde $15/month para soluciones ilimitadas |
El token que devuelve CaptchaAI es el mismo independientemente de cómo el sitio implemente reCAPTCHA v2. La diferencia está completamente en el código del lado del cliente: cómo entrega el token a la página.
Ejemplo ejecutable completo
¿Necesita un proyecto funcional completo con configuración del entorno, sondeo, reintentos y manejo de errores?
Vea el ejemplo ejecutable completo en GitHub →
Preguntas frecuentes
¿Qué es una devolución de llamada reCAPTCHA v2?
Una devolución de llamada es una función de JavaScript que un sitio web registra para recibir el token reCAPTCHA resuelto. En lugar de escribir el token en el campo oculto g-recaptcha-response, reCAPTCHA llama a la función directamente. La función normalmente activa el envío de formularios, la validación o la redirección de una página.
¿En qué se diferencia la devolución de llamada del estándar reCAPTCHA v2 para la llamada API?
No es nada diferente. Envía la misma solicitud method=userrecaptcha con la misma clave de sitio y URL de página. La única diferencia es lo que haces con el token después de recibirlo: llamas a la función de devolución de llamada en lugar de establecer un valor de campo.
¿Cómo encuentro el nombre de la función de devolución de llamada?
Tres lugares para verificar: (1) atributo data-callback en el div reCAPTCHA, (2) propiedad callback en una llamada grecaptcha.render() en la página JavaScript, (3) objeto ___grecaptcha_cfg.clients[0] en la consola del navegador: navegue por el árbol para encontrar la propiedad callback.
¿Puedo usar el token con ambos métodos?
El token en sí funciona de cualquier manera. Pero si el sitio espera una devolución de llamada, se ignorará la configuración g-recaptcha-response. Siempre haga coincidir el método de inyección con lo que espera el sitio.
¿Necesito un navegador para invocar la devolución de llamada?
Sí. La devolución de llamada es una función de JavaScript en el contexto de la página. Necesita Selenium, Puppeteer, Playwright o una herramienta similar que pueda ejecutar JavaScript en la página de destino.
Comience a resolver la devolución de llamada reCAPTCHA v2
- Obtenga su clave API —captchaai.com/api.php
- Detectar el nombre de la devolución de llamada: verifique
data-callback,grecaptcha.render()o la configuración interna - Copie el código Python o Node.js anterior: reemplace los marcadores de posición con su clave, clave de sitio, URL de página y nombre de devolución de llamada.
- Ejecútelo: el token llega en menos de 60 segundos, se activa la devolución de llamada y la página procesa el resultado.
- ¿Atascado? Empezar conErrores comunes de resolución de reCAPTCHA v2o leer el completoDocumentos API CaptchaAI
Artículos relacionados
- Resolver Recaptcha Python invisible
- Torniquete Recaptcha V2 Manejo en el mismo sitio
- Análisis profundo de la API de evaluación empresarial de Recaptcha