¿Resolviste el reCAPTCHA v2, escribiste el token en g-recaptcha-response y la página no reacciona? El widget usa un callback: espera que invoques una función de JavaScript con el token en lugar de leerlo desde el campo oculto. Ese es todo el problema, y se soluciona con una sola línea extra.
En esta guía detectas si un reCAPTCHA v2 usa callback, lo resuelves con la API de CaptchaAI e invocas la función correcta con el token. La llamada a la API es exactamente la misma que la del reCAPTCHA v2 estándar: lo único que cambia es cómo entregas el token a la página.
¿Empiezas con reCAPTCHA v2? Repasa primero Cómo resolver reCAPTCHA v2 con la API para el flujo estándar y vuelve aquí para la variante con callback.
Diagnóstico en 30 segundos
Antes de tocar una sola línea de código, confirma el síntoma con esta lista:
- Resolviste el CAPTCHA y CaptchaAI te devolvió un token válido: el problema no está en la API.
- Escribiste ese token en
g-recaptcha-responsey el formulario sigue sin avanzar. - El
divdel widget llevadata-callback, o encuentras uncallbackdentro degrecaptcha.render().
Si marcas las tres casillas, estás ante una implementación con callback. El resto de la guía te lleva de la detección a la invocación correcta.
En qué cambia respecto al reCAPTCHA v2 estándar
La buena noticia: la llamada a la API de CaptchaAI es idéntica a la del v2 estándar. La única diferencia aparece al final, cuando ya tienes el token en la mano.
| Paso | reCAPTCHA v2 estándar | reCAPTCHA v2 con callback |
|---|---|---|
| 1. Enviar a CaptchaAI | method=userrecaptcha + sitekey + pageurl |
Igual |
| 2. Consultar el resultado | action=get + ID del captcha |
Igual |
| 3. Recibir el token | Mismo formato de token | Igual |
| 4. Inyectar el token | Asignar el valor del campo g-recaptcha-response |
Llamar a la función callback con el token |
| 5. Enviar el formulario | Disparar el envío del formulario | Normalmente automático: el callback se encarga |
Importante: en las implementaciones con callback no asignes
g-recaptcha-response. La página ignora ese campo y espera a que se dispare la función callback. Si rellenas el campo sin llamar al callback, parecerá que el CAPTCHA nunca se resolvió.
Este matiz decide muchas pruebas en la práctica. Piensa en un equipo que automatiza el QA del formulario de acceso a un panel de cita previa en un entorno de staging:
- El envío está protegido con un reCAPTCHA v2 que dispara un callback tras validar.
- Si el script solo rellena el campo oculto, la prueba se queda colgada sin error aparente.
- En cuanto invoca el callback, el flujo avanza igual que con un usuario real.
Cómo identificar una implementación con callback
La diferencia de fondo entre las dos variantes es observable en el DOM:
- v2 estándar: escribe el token resuelto en un
textareaoculto llamadog-recaptcha-response. - Con callback: se salta ese campo y llama directamente a una función de JavaScript.
Tienes tres formas de confirmar cuál de las dos tienes delante. Ve de la más rápida a la más profunda.
Método 1: revisa el atributo data-callback
Inspecciona el div del widget de reCAPTCHA en el código fuente de la página:
<div class="g-recaptcha"
data-sitekey="6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-"
data-callback="SubmitToken">
</div>
Si existe data-callback, el sitio usa un callback. Su valor (SubmitToken) es el nombre de la función que necesitas.
Método 2: revisa las llamadas a grecaptcha.render()
Busca grecaptcha.render en el JavaScript de la página:
grecaptcha.render('recaptcha-container', {
sitekey: '6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-',
callback: userVerified
});
La propiedad callback indica la función. Aquí, userVerified.
Método 3: inspecciona la configuración interna de reCAPTCHA
Abre la consola del navegador en la página de destino y ejecuta:
___grecaptcha_cfg.clients[0]
Recorre el árbol de objetos hasta encontrar la propiedad callback. La ruta exacta varía según el sitio: puede ser clients[0].aa.l.callback u otra distinta según la versión y la minificación de reCAPTCHA. Si la página tiene varias instancias, revisa clients[1], clients[2], etc.
Script de detección rápida
Ejecuta esto en la consola del navegador para localizar los nombres de callback de forma automática:
// 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));
});
}
Lo que necesitas antes de escribir código
Reúne estos cinco datos antes de abrir el editor: sin ellos, ni la resolución ni la invocación del callback llegan a buen puerto.
| Requisito | Detalles |
|---|---|
| Clave API de CaptchaAI | Consíguela en captchaai.com/api.php. Cadena de 32 caracteres. |
| URL de la página de destino | La URL completa donde se carga el widget de reCAPTCHA v2. |
| sitekey de reCAPTCHA v2 | La clave pública asociada a la instancia del widget. |
| Herramienta de automatización del navegador | Selenium, Puppeteer o Playwright: necesitas ejecutar JavaScript para invocar el callback. |
| Nombre de la función callback | La función de JavaScript que el sitio espera para recibir el token. |
Flujo de resolución
El recorrido completo, del sitekey al token, se ve así:
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 en Python (Selenium)
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 este código:
- Envía el sitekey y la pageurl a
in.phpconmethod=userrecaptcha, igual que en el v2 estándar. - Sondea
res.phpcada 5 segundos hasta que el token está listo. - Detecta el nombre de la función callback desde el DOM de la página.
- Llama a la función callback con el token resuelto mediante
execute_script. - El propio JavaScript del sitio hace el resto: envío del formulario, validación o redirección.
Implementación en Node.js (Puppeteer)
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 en PHP
La llamada a la API es la misma en PHP. Invocar el callback requiere un contexto de navegador, así que este ejemplo cubre la resolución del lado del servidor. Usa una herramienta de navegador headless (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, usa una herramienta de automatización del navegador (por ejemplo, php-webdriver) para ejecutar:
SubmitToken("TOKEN_FROM_CAPTCHAAI");
Solución de problemas
Si algo falla después de invocar el callback, parte del síntoma exacto que ves en pantalla y baja por esta lista.
El token se resuelve pero la página no reacciona
La causa más habitual es la de siempre: asignaste g-recaptcha-response en lugar de llamar al callback. Comprueba dos señales antes de seguir:
- ¿El widget tiene un atributo
data-callback? - ¿Aparece una propiedad
callbackdentro degrecaptcha.render()?
Si respondes que sí a cualquiera, tienes que invocar esa función en lugar de escribir el campo oculto.
ReferenceError: SubmitToken is not defined
La función callback todavía no se ha cargado o el nombre es incorrecto. Repasa estos tres puntos en orden:
- Confirma el nombre inspeccionando
data-callbacko la configuración interna. - Espera a que la página termine de cargar antes de invocarla.
- En sitios minificados, la función puede estar asignada a una variable: revisa
window.SubmitTokenen la consola.
La página tiene varios widgets de reCAPTCHA
Cada widget puede tener su propio callback, así que apuntar al equivocado es fácil. Para acertar:
- Inspecciona cada
divg-recaptchapor separado, o revisa___grecaptcha_cfg.clientspara ver todas las instancias registradas. - Empareja el widget con el formulario concreto al que apuntas antes de invocar nada.
El token funciona en el v2 estándar pero falla en esta página
Lo más probable es que estés ante una implementación con callback. Revisa los pasos de detección de más arriba para confirmarlo y, después, cambia a la invocación del callback.
ERROR_BAD_TOKEN_OR_PAGEURL
El par sitekey/pageurl no es válido. Es un error de la API, no tiene relación con el callback ni con el flujo estándar. Vuelve a extraer ambos valores de la página.
ERROR_CAPTCHA_UNSOLVABLE
El desafío no se pudo resolver. Reinténtalo con una solicitud nueva. Esto no es específico del callback.
Para la referencia completa de errores, consulta Errores comunes al resolver reCAPTCHA v2.
Errores comunes
Estos son los tropiezos que más se repiten al pasar del v2 estándar a la variante con callback:
| # | Error | Qué ocurre | Solución |
|---|---|---|---|
| 1 | Asignar g-recaptcha-response en vez de llamar al callback |
La página ignora el token y el formulario nunca se envía | Localiza el nombre del callback e invócalo con el token |
| 2 | Nombre de la función callback incorrecto | Error de JavaScript: función no definida | Vuelve a revisar data-callback, grecaptcha.render() o la configuración interna |
| 3 | El callback está en otro índice de cliente | Se apunta a la instancia de reCAPTCHA equivocada en páginas con varios widgets | Revisa ___grecaptcha_cfg.clients[1], clients[2], etc. |
| 4 | Llamar al callback antes de que la página esté lista | La función aún no está definida en el contexto de la página | Espera a DOMContentLoaded o networkidle antes de invocarla |
| 5 | Usar un nombre ofuscado o minificado | El nombre del callback en el código fuente está alterado | Usa la consola del navegador en tiempo de ejecución para encontrar la referencia real |
| 6 | Mezclar callback v2 con Invisible v2 | Algunas implementaciones invisibles también usan callbacks | Comprueba si está presente data-size="invisible"; si es así, consulta Cómo resolver reCAPTCHA Invisible con la API |
Por qué CaptchaAI encaja en este caso
| Factor | Detalle |
|---|---|
| Misma llamada a la API | El flujo de envío y sondeo es idéntico al del reCAPTCHA v2 estándar: no hacen falta parámetros extra |
| Tasa de éxito | Más del 99,5% en reCAPTCHA v2 (el callback y el estándar comparten el mismo solver) |
| Tiempo de resolución | Menos de 60 segundos |
| Compatibilidad del token | El token devuelto sirve tanto para la inyección en g-recaptcha-response como para la invocación del callback |
| Precio | Planes basados en threads desde $15/mes (plan BASIC, 5 threads) con resoluciones ilimitadas |
La idea que conviene retener es sencilla:
- El token que devuelve CaptchaAI es el mismo sin importar cómo implemente el sitio el reCAPTCHA v2.
- Toda la diferencia vive en tu código del lado del cliente: en cómo le entregas ese token a la página.
Preguntas frecuentes
¿Por qué la página ignora el token que inyecté en g-recaptcha-response?
Porque el widget está configurado con un callback. En esas implementaciones, reCAPTCHA no lee el campo oculto: espera que una función de JavaScript reciba el token. La corrección tiene dos pasos:
- Localiza el callback con
data-callbacko dentro degrecaptcha.render(). - Invócalo con el token en lugar de escribir el campo oculto.
¿Necesito Selenium o Puppeteer para invocar el callback?
Sí, para el paso de invocación. El callback vive en el contexto de la página, así que hace falta un navegador que ejecute JavaScript. Concretando:
- Invocar el callback requiere Selenium, Puppeteer, Playwright o una herramienta equivalente.
- Resolver el CAPTCHA (envío y sondeo) puede correr en cualquier lenguaje del lado del servidor, sin navegador.
¿Cómo detecto el nombre del callback cuando el código está minificado?
Usa la consola del navegador en tiempo de ejecución: aunque el nombre esté ofuscado en el código fuente, ___grecaptcha_cfg.clients conserva la referencia real de la función. Dos caminos para dar con ella:
- Recorre el árbol de objetos hasta encontrar la propiedad
callback. - O comprueba
window.<nombre>con los candidatos que veas, hasta acertar con la función que reCAPTCHA invoca al validar.
¿Funciona el mismo token si el sitio combina callback con reCAPTCHA Invisible?
Sí, el token es el mismo. Lo que cambia es el disparador: las variantes invisibles suelen ejecutar el callback automáticamente tras la validación. Comprueba si el widget tiene data-size="invisible" y trátalo como una implementación con callback.
¿Cuánto tarda CaptchaAI en resolver un reCAPTCHA v2 con callback?
Normalmente menos de 60 segundos. El callback y el reCAPTCHA v2 estándar usan el mismo solver, así que el tiempo de resolución no cambia por usar un callback: solo cambia lo que haces con el token al recibirlo.
Ejemplo completo ejecutable
¿Quieres un proyecto funcional completo, con configuración del entorno, sondeo, reintentos y manejo de errores?
Mira el ejemplo completo en GitHub →
Empieza a resolver el callback de reCAPTCHA v2
- Consigue tu clave API — captchaai.com/api.php
- Detecta el nombre del callback — revisa
data-callback,grecaptcha.render()o la configuración interna - Copia el código de Python o Node.js de arriba — sustituye los marcadores por tu clave, sitekey, pageurl y nombre del callback
- Ejecútalo — el token llega en menos de 60 segundos, el callback se dispara y la página procesa el resultado
- ¿Atascado? Empieza por Errores comunes al resolver reCAPTCHA v2 o lee la documentación de la API de CaptchaAI
Artículos relacionados
- Resolver reCAPTCHA Invisible con Python
- Manejo de reCAPTCHA v2 y Turnstile en el mismo sitio
- Análisis a fondo de la API de reCAPTCHA Enterprise Assessment