Tutoriales de API

Cómo resolver el callback de reCAPTCHA v2 con la API

¿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:

  1. Resolviste el CAPTCHA y CaptchaAI te devolvió un token válido: el problema no está en la API.
  2. Escribiste ese token en g-recaptcha-response y el formulario sigue sin avanzar.
  3. El div del widget lleva data-callback, o encuentras un callback dentro de grecaptcha.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 textarea oculto llamado g-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:

  1. Envía el sitekey y la pageurl a in.php con method=userrecaptcha, igual que en el v2 estándar.
  2. Sondea res.php cada 5 segundos hasta que el token está listo.
  3. Detecta el nombre de la función callback desde el DOM de la página.
  4. Llama a la función callback con el token resuelto mediante execute_script.
  5. 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 callback dentro de grecaptcha.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:

  1. Confirma el nombre inspeccionando data-callback o la configuración interna.
  2. Espera a que la página termine de cargar antes de invocarla.
  3. En sitios minificados, la función puede estar asignada a una variable: revisa window.SubmitToken en 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 div g-recaptcha por separado, o revisa ___grecaptcha_cfg.clients para 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-callback o dentro de grecaptcha.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

  1. Consigue tu clave APIcaptchaai.com/api.php
  2. Detecta el nombre del callback — revisa data-callback, grecaptcha.render() o la configuración interna
  3. Copia el código de Python o Node.js de arriba — sustituye los marcadores por tu clave, sitekey, pageurl y nombre del callback
  4. Ejecútalo — el token llega en menos de 60 segundos, el callback se dispara y la página procesa el resultado
  5. ¿Atascado? Empieza por Errores comunes al resolver reCAPTCHA v2 o lee la documentación de la API de CaptchaAI

Artículos relacionados

Los comentarios están deshabilitados para este artículo.