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
grecaptcha.execute()lanza el challenge invisible- Tras resolver, Google llama a la función especificada en
data-callback - 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
siteverifydel lado del servidor devuelvetimeout-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
- Cómo funciona reCAPTCHA Invisible y cómo solucionarlo
- Cómo resolver reCAPTCHA v2 usando la API
- Errores comunes de reCAPTCHA v2
- Referencia de códigos de error CaptchaAI