Troubleshooting

Errores y soluciones comunes de reCAPTCHA Invisible

Invisible reCAPTCHA elimina la casilla de verificación, pero agrega nuevos modos de falla para la automatización. Los mayores problemas son no detectar el widget invisible, omitir la función callback, enviar tokens caducados y usar parámetros estándar v2 cuando se requieren parámetros invisibles.

Esta guía cubre todos los patrones de error comunes con la solución exacta. Si necesitas información sobre cómo funciona reCAPTCHA invisible, lee Cómo funciona reCAPTCHA Invisible y cómo solucionarlo primero.


Referencia rápida de errores

Error Causa Solución
ERROR_WRONG_GOOGLEKEY Sitekey incorrecto o de un dominio diferente Extrae el sitekey del div del widget invisible o de la llamada grecaptcha.render()
ERROR_PAGEURL La URL no coincide: se envió la URL de la página principal en lugar de la del iframe Usa la URL exacta donde se carga el widget invisible
ERROR_CAPTCHA_UNSOLVABLE Google marcó la tarea como imposible Reintenta con proxy y cookies nuevos; verifica si el sitio cambió a v3
ERROR_BAD_TOKEN_OR_PAGEURL Token rechazado por el sitio de destino Verifica que la URL de la página coincida exactamente; inyecta mediante callback, no campo oculto
CAPCHA_NOT_READY La tarea aún se está procesando Sigue sondeando cada 5 segundos; las soluciones invisibles tardan entre 10 y 30 segundos
ERROR_KEY_DOES_NOT_EXIST Clave API CaptchaAI no válida Comprueba la clave en captchaai.com/account
Token aceptado pero el formulario falla Callback no ejecutado tras inyectar el token Busca y llama a la función data-callback con el token

Error 1: no se detecta reCAPTCHA invisible en la página

El reCAPTCHA invisible no tiene ninguna casilla de verificación visible. Si su scraper no lo detecta, las solicitudes protegidas con captcha fallan silenciosamente con errores de envío de formularios o redirecciones.

Cómo detectar reCAPTCHA invisible

Busque estos patrones en la página HTML:

<!-- Pattern 1: div with data-size="invisible" -->
<div class="g-recaptcha" data-sitekey="6LdKlZEU..."
     data-size="invisible"
     data-callback="onSubmit"></div>

<!-- Pattern 2: button with data-sitekey and invisible size -->
<button class="g-recaptcha"
        data-sitekey="6LdKlZEU..."
        data-callback="onSubmit"
        data-action="submit">Submit</button>

<!-- Pattern 3: programmatic render with size: invisible -->
<script>
  grecaptcha.render('submit-btn', {
    sitekey: '6LdKlZEU...',
    callback: onSubmit,
    size: 'invisible'
  });
</script>

Script de detección (Python):

import requests
from bs4 import BeautifulSoup
import re

def detect_invisible_recaptcha(url):
    resp = requests.get(url)
    soup = BeautifulSoup(resp.text, "html.parser")

    # Check for data-size="invisible"
    widget = soup.find("div", {"data-size": "invisible", "class": "g-recaptcha"})
    if widget:
        return {
            "type": "invisible",
            "sitekey": widget.get("data-sitekey"),
            "callback": widget.get("data-callback")
        }

    # Check for programmatic render with invisible
    scripts = soup.find_all("script")
    for script in scripts:
        if script.string and "size" in str(script.string) and "invisible" in str(script.string):
            key_match = re.search(r"sitekey['\"]?\s*[:=]\s*['\"]([^'\"]+)", script.string)
            if key_match:
                return {
                    "type": "invisible-programmatic",
                    "sitekey": key_match.group(1),
                    "callback": "check grecaptcha.render() call"
                }

    return None

Script de detección (Node.js):

const axios = require("axios");
const cheerio = require("cheerio");

async function detectInvisibleRecaptcha(url) {
  const { data } = await axios.get(url);
  const $ = cheerio.load(data);

  // Check for data-size="invisible"
  const widget = $(".g-recaptcha[data-size='invisible']");
  if (widget.length) {
    return {
      type: "invisible",
      sitekey: widget.attr("data-sitekey"),
      callback: widget.attr("data-callback"),
    };
  }

  // Check script tags for programmatic invisible render
  const scriptContent = $("script")
    .map((_, el) => $(el).html())
    .get()
    .join("\n");
  if (scriptContent.includes("invisible")) {
    const keyMatch = scriptContent.match(/sitekey['"]?\s*[:=]\s*['"]([^'"]+)/);
    if (keyMatch) {
      return {
        type: "invisible-programmatic",
        sitekey: keyMatch[1],
        callback: "check grecaptcha.render() call",
      };
    }
  }

  return null;
}

Error 2: clave de sitio incorrecta: ERROR_WRONG_GOOGLEKEY

Esto sucede cuando envía una clave de sitio que no coincide con el widget reCAPTCHA invisible en la página de destino. Causas comunes:

  • Copié la clave del sitio de una casilla de verificación v2 en una página diferente
  • Usó una clave de sitio de la URL de anclaje de una versión de reCAPTCHA diferente.
  • La página tiene múltiples widgets reCAPTCHA y tomaste el equivocado

Solución: extraiga la clave de sitio invisible correcta

import requests
from bs4 import BeautifulSoup

def get_invisible_sitekey(url):
    resp = requests.get(url)
    soup = BeautifulSoup(resp.text, "html.parser")

    # Priority 1: invisible widget
    widget = soup.find(attrs={"data-size": "invisible", "class": "g-recaptcha"})
    if widget:
        return widget["data-sitekey"]

    # Priority 2: any g-recaptcha div (may be invisible without data-size)
    widget = soup.find(class_="g-recaptcha")
    if widget and widget.get("data-sitekey"):
        return widget["data-sitekey"]

    return None

sitekey = get_invisible_sitekey("https://staging.example.com/qa-login")
print(f"Sitekey: {sitekey}")

Error 3: callback no ejecutado — el formulario se envía pero no pasa nada

Este es el fallo invisible número uno de reCAPTCHA que los desarrolladores pasan por alto. A diferencia de la casilla de verificación v2 donde inyectar el token en g-recaptcha-response es suficiente, el reCAPTCHA invisible casi siempre usa una función callback de JavaScript. Si inyectas el token pero no llamas al callback, el formulario nunca se procesa.

Cómo funciona el flujo del callback

  1. grecaptcha.execute() lanza el challenge invisible
  2. Tras resolver, Google llama a la función especificada en data-callback
  3. Esa función callback envía el formulario o realiza la llamada a la API.

Solución: busca y ejecuta el callback

Paso 1: Identifica el nombre del callback:

# From HTML: data-callback="onSubmit"
# From JS: callback: onSubmit
# From grecaptcha.render: second argument with callback property

**Paso 2: Inyecta el token Y llama al callback (Selenium):

from selenium import webdriver
import requests
import time

driver = webdriver.Chrome()
driver.get("https://example.com/form")

# Get sitekey
sitekey = driver.find_element("css selector", ".g-recaptcha").get_attribute("data-sitekey")
callback_name = driver.find_element("css selector", ".g-recaptcha").get_attribute("data-callback")

# Solve con CaptchaAI
task_id = requests.get("https://ocr.captchaai.com/in.php", params={
    "key": "YOUR_API_KEY",
    "method": "userrecaptcha",
    "googlekey": sitekey,
    "pageurl": driver.current_url,
    "invisible": 1
}).text.split("|")[1]

# Poll for result
token = None
for _ in range(60):
    time.sleep(5)
    resp = requests.get("https://ocr.captchaai.com/res.php", params={
        "key": "YOUR_API_KEY",
        "action": "get",
        "id": task_id
    }).text
    if resp.startswith("OK|"):
        token = resp.split("|")[1]
        break

# Inject token into the response field
driver.execute_script(
    f'document.getElementById("g-recaptcha-response").value = "{token}";'
)

# CRITICAL: Call the callback function
driver.execute_script(f'{callback_name}("{token}");')

Paso 2: inyecta el token Y llama al callback (Puppeteer):

const puppeteer = require("puppeteer");
const axios = require("axios");

(async () => {
  const browser = await puppeteer.launch({ headless: "new" });
  const page = await browser.newPage();
  await page.goto("https://example.com/form");

  // Get sitekey and callback
  const { sitekey, callback } = await page.evaluate(() => {
    const el = document.querySelector(".g-recaptcha[data-size='invisible']");
    return {
      sitekey: el?.getAttribute("data-sitekey"),
      callback: el?.getAttribute("data-callback"),
    };
  });

  // Submit to CaptchaAI
  const submitResp = await axios.get("https://ocr.captchaai.com/in.php", {
    params: {
      key: "YOUR_API_KEY",
      method: "userrecaptcha",
      googlekey: sitekey,
      pageurl: page.url(),
      invisible: 1,
    },
  });
  const taskId = submitResp.data.split("|")[1];

  // Poll for result
  let token;
  for (let i = 0; i < 60; i++) {
    await new Promise((r) => setTimeout(r, 5000));
    const result = await axios.get("https://ocr.captchaai.com/res.php", {
      params: { key: "YOUR_API_KEY", action: "get", id: taskId },
    });
    if (result.data.startsWith("OK|")) {
      token = result.data.split("|")[1];
      break;
    }
  }

  // Inject token and fire callback
  await page.evaluate(
    (tok, cb) => {
      document.getElementById("g-recaptcha-response").value = tok;
      if (cb && typeof window[cb] === "function") {
        window[cb](tok);
      }
    },
    token,
    callback,
  );

  await browser.close();
})();

Error 4: falta el parámetro invisible=1

Al resolver reCAPTCHA invisible a través de CaptchaAI, debes incluir invisible=1 en tu solicitud. Sin él, el solucionador trata la tarea como un desafío de casilla de verificación estándar v2, lo que puede generar ERROR_CAPTCHA_UNSOLVABLE o tokens que el sitio de destino rechaza.

Solicitud incorrecta versus correcta

# WRONG — missing invisible=1
params = {
    "key": "YOUR_API_KEY",
    "method": "userrecaptcha",
    "googlekey": sitekey,
    "pageurl": page_url
}

# CORRECT — includes invisible=1
params = {
    "key": "YOUR_API_KEY",
    "method": "userrecaptcha",
    "googlekey": sitekey,
    "pageurl": page_url,
    "invisible": 1  # Required for invisible reCAPTCHA
}

response = requests.get("https://ocr.captchaai.com/in.php", params=params)

Error 5: el token expiró antes del envío

Los tokens reCAPTCHA invisibles caducan en 120 segundos, lo mismo que el estándar v2. Pero los flujos de trabajo invisibles suelen tener pasos de procesamiento adicionales entre la resolución y el envío, lo que hace que la caducidad sea más probable.

Síntomas

  • El formulario devuelve un error genérico después de la inyección del token
  • siteverify del lado del servidor devuelve timeout-or-duplicate
  • El token era válido pero tardó demasiado en llegar al paso de envío.

Solución: solución justo a tiempo

Solicite la solución solo cuando esté listo para enviarla de inmediato:

import requests
import time

def solve_invisible_recaptcha(api_key, sitekey, page_url):
    # Submit task
    resp = requests.get("https://ocr.captchaai.com/in.php", params={
        "key": api_key,
        "method": "userrecaptcha",
        "googlekey": sitekey,
        "pageurl": page_url,
        "invisible": 1
    })
    if not resp.text.startswith("OK|"):
        raise Exception(f"Submit failed: {resp.text}")
    task_id = resp.text.split("|")[1]

    # Poll for result
    for _ in range(60):
        time.sleep(5)
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": api_key,
            "action": "get",
            "id": task_id
        })
        if result.text.startswith("OK|"):
            return result.text.split("|")[1]
        if result.text != "CAPCHA_NOT_READY":
            raise Exception(f"Solve failed: {result.text}")

    raise Exception("Solve timed out after 5 minutes")

# Usage: solve JUST before you need to submit
# 1. Navigate to page and prepare form data first
# 2. THEN solve the captcha
# 3. Inject token and submit immediately
token = solve_invisible_recaptcha("YOUR_API_KEY", sitekey, page_url)
# Submit within 120 seconds of receiving the token

Error 6: Token rechazado – ERROR_BAD_TOKEN_OR_PAGEURL

El sitio de destino verificó el token con Google y obtuvo un error. Causas comunes:

Causa Cómo identificar Solución
pageurl incorrecto La URL no coincide con el dominio del sitekey Usa la URL exacta donde se carga el widget
Token usado en un dominio diferente Reutilización de tokens entre dominios Resuelve con el pageurl del dominio correcto
Token ya utilizado Enviar el mismo token dos veces Solicita una nueva solución para cada envío
IP no coincide Tu IP difiere de la IP del solucionador Agrega tu parámetro proxy para hacer coincidir la IP de la sesión
Falta el parámetro invisible Resuelto como v2 estándar, usado en página invisible Agrega invisible=1 a la solicitud de resolución

Lista de verificación de depuración

def debug_invisible_solve(api_key, sitekey, page_url, proxy=None):
    """Run a diagnostic solve with detailed logging."""
    print(f"Sitekey: {sitekey}")
    print(f"Page URL: {page_url}")
    print(f"Proxy: {proxy or 'none'}")

    params = {
        "key": api_key,
        "method": "userrecaptcha",
        "googlekey": sitekey,
        "pageurl": page_url,
        "invisible": 1
    }
    if proxy:
        params["proxy"] = proxy
        params["proxytype"] = "HTTP"

    # Submit
    resp = requests.get("https://ocr.captchaai.com/in.php", params=params)
    print(f"Submit response: {resp.text}")
    if not resp.text.startswith("OK|"):
        return None

    task_id = resp.text.split("|")[1]
    print(f"Task ID: {task_id}")

    # Poll with timing
    start = time.time()
    for _ in range(60):
        time.sleep(5)
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": api_key, "action": "get", "id": task_id
        })
        elapsed = time.time() - start
        print(f"  [{elapsed:.0f}s] {result.text[:50]}")
        if result.text.startswith("OK|"):
            token = result.text.split("|")[1]
            print(f"Token received after {elapsed:.0f}s")
            print(f"Token length: {len(token)} characters")
            print(f"Token starts with: {token[:30]}...")
            return token
        if result.text != "CAPCHA_NOT_READY":
            print(f"FAILED: {result.text}")
            return None

    print("TIMEOUT after 5 minutes")
    return None

Error 7: varios widgets reCAPTCHA en una página

Algunas páginas tienen una casilla de verificación v2 visible Y un reCAPTCHA invisible. Si resuelve el error incorrecto, el token es válido pero no coincide con el widget que protege la acción que necesita.

Solución: apunte al widget correcto

from bs4 import BeautifulSoup

def find_all_recaptcha_widgets(html):
    soup = BeautifulSoup(html, "html.parser")
    widgets = []

    for el in soup.find_all(class_="g-recaptcha"):
        widgets.append({
            "sitekey": el.get("data-sitekey"),
            "size": el.get("data-size", "normal"),
            "callback": el.get("data-callback"),
            "tag": el.name,
            "id": el.get("id")
        })

    return widgets

# Example output:
# [
#   {"sitekey": "6LdA...", "size": "normal", "callback": None, "tag": "div", "id": "recaptcha-login"},
#   {"sitekey": "6LdB...", "size": "invisible", "callback": "onRegister", "tag": "div", "id": "recaptcha-register"}
# ]
# Use the widget with size="invisible" for the invisible solve

Completo solucionador reCAPTCHA invisible con manejo de errores

Este contenedor listo para producción maneja todos los errores anteriores:

import requests
import time
import logging

logger = logging.getLogger(__name__)

class InvisibleRecaptchaSolver:
    def __init__(self, api_key, max_retries=3):
        self.api_key = api_key
        self.max_retries = max_retries
        self.base_url = "https://ocr.captchaai.com"

    def solve(self, sitekey, page_url, proxy=None):
        """Solve invisible reCAPTCHA with automatic retry on transient errors."""
        for attempt in range(1, self.max_retries + 1):
            try:
                token = self._attempt_solve(sitekey, page_url, proxy)
                if token:
                    return token
            except Exception as e:
                logger.warning(f"Attempt {attempt} failed: {e}")
                if attempt < self.max_retries:
                    time.sleep(2 ** attempt)
        raise Exception(f"Failed to solve after {self.max_retries} attempts")

    def _attempt_solve(self, sitekey, page_url, proxy):
        params = {
            "key": self.api_key,
            "method": "userrecaptcha",
            "googlekey": sitekey,
            "pageurl": page_url,
            "invisible": 1
        }
        if proxy:
            params["proxy"] = proxy
            params["proxytype"] = "HTTP"

        # Submit task
        resp = requests.get(f"{self.base_url}/in.php", params=params)

        if "ERROR" in resp.text:
            error = resp.text.strip()
            if error in ("ERROR_WRONG_GOOGLEKEY", "ERROR_KEY_DOES_NOT_EXIST"):
                raise Exception(f"Configuration error (do not retry): {error}")
            if error == "ERROR_ZERO_BALANCE":
                raise Exception("Account balance is zero — add funds")
            raise Exception(f"Submit error: {error}")

        if not resp.text.startswith("OK|"):
            raise Exception(f"Unexpected submit response: {resp.text}")

        task_id = resp.text.split("|")[1]

        # Poll for result
        for _ in range(60):
            time.sleep(5)
            result = requests.get(f"{self.base_url}/res.php", params={
                "key": self.api_key,
                "action": "get",
                "id": task_id
            })

            if result.text.startswith("OK|"):
                return result.text.split("|")[1]

            if result.text == "CAPCHA_NOT_READY":
                continue

            if result.text == "ERROR_CAPTCHA_UNSOLVABLE":
                logger.warning("Captcha unsolvable — will retry with new task")
                return None

            raise Exception(f"Poll error: {result.text}")

        raise Exception("Solve timed out after 5 minutes")


# Usage
solver = InvisibleRecaptchaSolver("YOUR_API_KEY")
token = solver.solve(
    sitekey="6LdKlZEU...",
    page_url="https://staging.example.com/qa-login"
)
print(f"Token: {token[:50]}...")
const axios = require("axios");

class InvisibleRecaptchaSolver {
  constructor(apiKey, maxRetries = 3) {
    this.apiKey = apiKey;
    this.maxRetries = maxRetries;
    this.baseUrl = "https://ocr.captchaai.com";
  }

  async solve(sitekey, pageUrl, proxy) {
    for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
      try {
        const token = await this._attemptSolve(sitekey, pageUrl, proxy);
        if (token) return token;
      } catch (err) {
        console.warn(`Attempt ${attempt} failed: ${err.message}`);
        if (attempt < this.maxRetries) {
          await new Promise((r) => setTimeout(r, 2 ** attempt * 1000));
        }
      }
    }
    throw new Error(`Failed to solve after ${this.maxRetries} attempts`);
  }

  async _attemptSolve(sitekey, pageUrl, proxy) {
    const params = {
      key: this.apiKey,
      method: "userrecaptcha",
      googlekey: sitekey,
      pageurl: pageUrl,
      invisible: 1,
    };
    if (proxy) {
      params.proxy = proxy;
      params.proxytype = "HTTP";
    }

    // Submit task
    const submitResp = await axios.get(`${this.baseUrl}/in.php`, { params });
    if (submitResp.data.includes("ERROR")) {
      const error = submitResp.data.trim();
      if (["ERROR_WRONG_GOOGLEKEY", "ERROR_KEY_DOES_NOT_EXIST"].includes(error)) {
        throw new Error(`Configuration error (do not retry): ${error}`);
      }
      throw new Error(`Submit error: ${error}`);
    }

    const taskId = submitResp.data.split("|")[1];

    // Poll for result
    for (let i = 0; i < 60; i++) {
      await new Promise((r) => setTimeout(r, 5000));
      const result = await axios.get(`${this.baseUrl}/res.php`, {
        params: { key: this.apiKey, action: "get", id: taskId },
      });

      if (result.data.startsWith("OK|")) {
        return result.data.split("|")[1];
      }
      if (result.data === "CAPCHA_NOT_READY") continue;
      if (result.data === "ERROR_CAPTCHA_UNSOLVABLE") return null;
      throw new Error(`Poll error: ${result.data}`);
    }
    throw new Error("Solve timed out after 5 minutes");
  }
}

// Usage
const solver = new InvisibleRecaptchaSolver("YOUR_API_KEY");
solver.solve("6LdKlZEU...", "https://staging.example.com/qa-login").then((token) => {
  console.log(`Token: ${token.substring(0, 50)}...`);
});

Lista de verificación de solución de problemas

Ejecute esta lista de verificación cuando falle la resolución invisible de reCAPTCHA:

Paso Verificar Comando/Acción
1 Confirma que es invisible, no v2 estándar Busca data-size="invisible" o size: 'invisible' en la llamada de renderizado
2 Verifica que el sitekey sea correcto Compara con data-sitekey en el widget invisible específicamente
3 Confirma invisible=1 en la solicitud de API Verifica tus parámetros en in.php
4 Comprueba que pageurl coincida exactamente Usa la URL de DevTools del navegador, no una URL de redirección
5 Encuentra el nombre de la función callback Busca el atributo data-callback o callback en grecaptcha.render()
6 Verifica inyección de token + llamada al callback Ambos pasos son obligatorios: el token solo no es suficiente
7 Comprueba la frescura del token El token debe usarse dentro de los 120 segundos
8 Prueba con proxy si la IP importa Agrega los parámetros proxy y proxytype

Preguntas frecuentes

¿En qué se diferencia el reCAPTCHA invisible del estándar v2 para la resolución?

El método API es el mismo (method=userrecaptcha), pero debes agregar invisible=1 a tu solicitud. La diferencia crítica está en el lado de la inyección: reCAPTCHA invisible casi siempre requiere llamar a una función callback de JavaScript después de inyectar el token, mientras que el estándar v2 generalmente funciona solo con el campo oculto.

¿Por qué mi token funciona en pruebas pero falla en producción?

Lo más probable es que no coincida la IP. Durante las pruebas, el solucionador y su navegador pueden compartir direcciones IP similares. En producción, la IP del solucionador y la IP de su servidor difieren. Agregue un parámetro de proxy que coincida con la IP de su sesión para solucionar este problema.

¿Cuánto tiempo tarda en resolverse el reCAPTCHA invisible?

Los tiempos de resolución típicos son de 10 a 30 segundos hasta CaptchaAI. Los desafíos invisibles son generalmente más rápidos que los desafíos de casilla de verificación v2 porque no requieren reconocimiento de imágenes: dependen del análisis de riesgos.

¿Puedo resolver reCAPTCHA invisible sin un navegador?

Sí. Dado que la solución se realiza en el lado del servidor a través de la API, solo necesita la clave del sitio y la URL de la página. El navegador solo es necesario si debe ejecutar la función callback en la página real. Para flujos de trabajo API puros, extrae la sitekey, resuelve mediante CaptchaAI y envía el token con tu solicitud HTTP.


Artículos relacionados

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

Publicaciones relacionadas

Explainers reCAPTCHA v2 Invisible: Detección y resolución de activadores
Explicación clara sobre re CAPTCHA v 2 Invisible: Detección y resolución de desencadenantes y lo que implica para automatización, integración y tasas de éxito c...

Explicación clara sobre re CAPTCHA v 2 Invisible: Detección y resolución de desencadenantes y lo que implica p...

Apr 29, 2026
Comparisons Cloudflare Managed Challenge vs Interactive Challenge
Cloudflare Managed Challenge vs Interactive Challenge: diferencias de comportamiento, códigos HTTP, métodos de resolución con Captcha AI y cuándo usar cada uno.

Cloudflare Managed Challenge vs Interactive Challenge: diferencias de comportamiento, códigos HTTP, métodos de...

Apr 18, 2026
Explainers Reglas WAF de Cloudflare que desencadenan desafíos CAPTCHA
Guía sobre las reglas WAF de Cloudflare que activan desafíos CAPTCHA: qué acciones WAF son solucionables, cómo identificar la regla desencadenante y cómo resolv...

Guía sobre las reglas WAF de Cloudflare que activan desafíos CAPTCHA: qué acciones WAF son solucionables, cómo...

Apr 24, 2026