¿Tienes que resolver la v2 y la v3 a la vez cuando una página monta las dos? No. La v3 se ejecuta invisible y calcula una puntuación; solo cuando esa puntuación cae por debajo del umbral aparece el desafío visible de la v2. El trabajo difícil no es resolver cada versión —eso es rutina con la API—, sino detectar qué hay montado y con qué sitekey. Esta guía cubre la detección, las tres estrategias de resolución y los casos límite del flujo.
Cómo conviven v2 y v3 en la misma página
El circuito es siempre el mismo: la v3 puntúa en silencio y el servidor decide si enseña la v2.
User visits page
↓
reCAPTCHA v3 runs invisibly in background
↓
Score returned to server (e.g., 0.4)
↓
Score below threshold (e.g., < 0.7)?
├─ YES → Show reCAPTCHA v2 checkbox/image challenge
└─ NO → Allow action without visible CAPTCHA
En qué se diferencian las dos versiones dentro de la misma página:
| Aspecto | reCAPTCHA v3 | reCAPTCHA v2 |
|---|---|---|
| Visibilidad | Invisible, en segundo plano | Visible: casilla o desafío de imágenes |
| Dónde está el sitekey | En ?render= y en grecaptcha.execute(...) |
En el data-sitekey del widget |
| Cuándo se resuelve | En cada carga de la página | Solo si la puntuación v3 no alcanza el umbral |
| Campo del token | g-recaptcha-response |
g-recaptcha-response |
La ventaja para el sitio es doble: quien puntúa alto no ve nada y el tráfico sospechoso recibe el segundo filtro.
Cómo se combinan v2 y v3 en la práctica
Vas a encontrarte con tres patrones de montaje.
Patrón 1: la v3 evalúa y la v2 aparece como respaldo
El montaje más habitual: la v3 corre primero y la v2 solo aparece si la puntuación no alcanza el umbral.
<!-- Both scripts loaded -->
<script src="https://www.google.com/recaptcha/api.js?render=V3_SITE_KEY"></script>
<script src="https://www.google.com/recaptcha/api.js?render=explicit" async defer></script>
<form id="loginForm">
<!-- v2 widget (hidden initially) -->
<div id="recaptcha-v2-container" style="display:none;">
<div class="g-recaptcha" data-sitekey="V2_SITE_KEY"></div>
</div>
<button type="submit">Login</button>
</form>
<script>
// First attempt: v3 invisible
grecaptcha.ready(function() {
grecaptcha.execute('V3_SITE_KEY', {action: 'login'}).then(function(v3Token) {
fetch('/api/verify-v3', {
method: 'POST',
body: JSON.stringify({token: v3Token})
})
.then(r => r.json())
.then(data => {
if (data.score < 0.7) {
// Score too low → show v2 fallback
document.getElementById('recaptcha-v2-container').style.display = 'block';
grecaptcha.render('recaptcha-v2-container', {sitekey: 'V2_SITE_KEY'});
} else {
// Score OK → submit form directly
document.getElementById('loginForm').submit();
}
});
});
});
</script>
Patrón 2: claves distintas para acciones distintas
Otros sitios reservan la v3 para el monitoreo pasivo y solo sacan la v2 en las acciones de alto riesgo, como el checkout:
Homepage → v3 only (passive score)
Login page → v3 assessment, v2 fallback
Checkout → v2 always (high security)
Contact form → v3 only
Patrón 3: un solo script en modo dual
Un único script de reCAPTCHA puede gestionar a la vez la v2 y la v3:
<script src="https://www.google.com/recaptcha/api.js?render=V3_SITE_KEY"></script>
<script>
// v3 execute
grecaptcha.execute('V3_SITE_KEY', {action: 'login'});
// v2 render (uses a different site key)
grecaptcha.render('v2-container', {sitekey: 'V2_SITE_KEY'});
</script>
Un caso frecuente en la región: el login de un portal de cita previa o de un marketplace tipo MercadoLibre puntúa cada intento con la v3 y solo lanza la v2 ante tráfico que parece automatizado. Al hacer QA de tu propio flujo de acceso, la verás asomar justo ahí.
Cómo detectar el reCAPTCHA dual en una página
Antes de resolver nada tienes que saber qué versiones hay y con qué sitekey. La v3 vive en ?render= o en grecaptcha.execute(...); la v2, en el data-sitekey del widget.
Detección con Python
import requests
import re
def detect_dual_recaptcha(url):
"""Detect if a page uses both reCAPTCHA v2 and v3."""
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/120.0.0.0 Safari/537.36",
}
html = requests.get(url, headers=headers, timeout=15).text
result = {
"has_v3": False,
"has_v2": False,
"v3_site_key": None,
"v2_site_key": None,
"dual": False,
"pattern": None,
}
# Detect v3 (render parameter or enterprise.execute)
v3_match = re.search(r"api\.js\?render=([A-Za-z0-9_-]+)", html)
if v3_match and v3_match.group(1) != "explicit":
result["has_v3"] = True
result["v3_site_key"] = v3_match.group(1)
# Detect v3 in execute calls
v3_execute = re.search(
r"grecaptcha\.(?:enterprise\.)?execute\s*\(\s*['\"]([^'\"]+)['\"]",
html,
)
if v3_execute:
result["has_v3"] = True
if not result["v3_site_key"]:
result["v3_site_key"] = v3_execute.group(1)
# Detect v2 (g-recaptcha class or explicit render)
v2_match = re.search(r'data-sitekey="([^"]+)"', html)
if v2_match:
key = v2_match.group(1)
if key != result.get("v3_site_key"):
result["has_v2"] = True
result["v2_site_key"] = key
# Check for explicit v2 render
v2_render = re.search(
r"grecaptcha\.render\s*\([^,]+,\s*\{[^}]*sitekey:\s*['\"]([^'\"]+)",
html,
)
if v2_render:
result["has_v2"] = True
if not result["v2_site_key"]:
result["v2_site_key"] = v2_render.group(1)
result["dual"] = result["has_v3"] and result["has_v2"]
if result["dual"]:
# Determine pattern
if "display:none" in html or "display: none" in html:
result["pattern"] = "v3_pre_assessment_v2_fallback"
else:
result["pattern"] = "v2_v3_simultaneous"
return result
detection = detect_dual_recaptcha("https://staging.example.com/qa-login")
print(detection)
Detección con Node.js
const axios = require("axios");
async function detectDualRecaptcha(url) {
const { data: html } = await axios.get(url, { timeout: 15000 });
const result = {
hasV3: false,
hasV2: false,
v3SiteKey: null,
v2SiteKey: null,
dual: false,
};
// v3 detection
const v3Match = html.match(/api\.js\?render=([A-Za-z0-9_-]+)/);
if (v3Match && v3Match[1] !== "explicit") {
result.hasV3 = true;
result.v3SiteKey = v3Match[1];
}
// v2 detection
const v2Match = html.match(/data-sitekey="([^"]+)"/);
if (v2Match && v2Match[1] !== result.v3SiteKey) {
result.hasV2 = true;
result.v2SiteKey = v2Match[1];
}
result.dual = result.hasV3 && result.hasV2;
return result;
}
detectDualRecaptcha("https://staging.example.com/qa-login").then(console.log);
Estrategias para resolver reCAPTCHA v2 y v3
Con la página clasificada, hay tres caminos según cuánto confíes en la puntuación de la v3.
Estrategia 1: resolver la v3 y recurrir a la v2 solo si hace falta
- Resuelve la v3 y envía el token al destino, imitando el flujo del propio sitio.
- Revisa la respuesta del servidor.
- Resuelve la v2 solo si el respaldo se activa.
import requests
import time
API_KEY = "YOUR_API_KEY"
def solve_v3(site_key, page_url, action="login"):
"""Solve reCAPTCHA v3 and return token."""
submit = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": site_key,
"pageurl": page_url,
"version": "v3",
"action": action,
"json": 1,
}).json()
task_id = submit["request"]
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, "json": 1,
}).json()
if result.get("status") == 1:
return result["request"]
raise TimeoutError("v3 solve timeout")
def solve_v2(site_key, page_url):
"""Solve reCAPTCHA v2 and return token."""
submit = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": site_key,
"pageurl": page_url,
"json": 1,
}).json()
task_id = submit["request"]
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, "json": 1,
}).json()
if result.get("status") == 1:
return result["request"]
raise TimeoutError("v2 solve timeout")
def solve_dual_recaptcha(v3_key, v2_key, page_url, action="login"):
"""Handle dual reCAPTCHA: try v3, fall back to v2."""
# Step 1: Try v3
v3_token = solve_v3(v3_key, page_url, action)
# Step 2: Submit v3 token to target
response = requests.post(f"{page_url}/verify", data={
"g-recaptcha-response": v3_token,
})
# Step 3: Check if v2 fallback is needed
if "recaptcha" in response.text.lower() and v2_key:
print("v3 score too low — v2 fallback triggered")
v2_token = solve_v2(v2_key, page_url)
return {"version": "v2", "token": v2_token}
return {"version": "v3", "token": v3_token}
result = solve_dual_recaptcha(
v3_key="6LcExample_v3_key",
v2_key="6LcExample_v2_key",
page_url="https://staging.example.com/qa-login",
)
print(f"Solved with {result['version']}")
Estrategia 2: saltarte la v3 y resolver la v2 directamente
- Si ya sabes que la v3 siempre puntúa bajo y el sitio termina mostrando la v2, ahórrate el rodeo.
- Resuelve la v2 de entrada y evita el costo de una v3 que no iba a pasar el umbral.
# If you consistently fail v3 assessment, just solve v2 directly
token = solve_v2(v2_site_key, page_url)
submit_form(token)
Estrategia 3: dejar que el navegador decida
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
driver = webdriver.Chrome()
driver.get("https://staging.example.com/qa-login")
time.sleep(3)
# Check if v2 widget is visible
v2_visible = driver.execute_script("""
const container = document.querySelector('.g-recaptcha');
if (!container) return false;
const style = window.getComputedStyle(container.parentElement);
return style.display !== 'none' && style.visibility !== 'hidden';
""")
if v2_visible:
# v2 is showing — solve v2
sitekey = driver.find_element(
By.CSS_SELECTOR, "[data-sitekey]"
).get_attribute("data-sitekey")
token = solve_v2(sitekey, driver.current_url)
driver.execute_script(
f'document.getElementById("g-recaptcha-response").value = "{token}";'
)
else:
# v3 only — solve v3
# Extract v3 key from page source
v3_key = driver.execute_script(
"return document.querySelector('script[src*=\"render=\"]')"
".src.match(/render=([^&]+)/)[1];"
)
token = solve_v3(v3_key, driver.current_url)
# Inject v3 token into the form
driver.execute_script(f"""
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'g-recaptcha-response';
input.value = '{token}';
document.querySelector('form').appendChild(input);
""")
driver.find_element(By.CSS_SELECTOR, "form").submit()
Detalles que suelen romper la automatización
La lógica es sencilla; lo que rompe los flujos son estos detalles.
Dos sitekeys diferentes en la misma página
Un sitio con reCAPTCHA dual expone DOS sitekeys distintos, y cruzarlos produce tokens inválidos:
- Clave v3 — en la URL del script
?render=KEYy engrecaptcha.execute('KEY', ...). - Clave v2 — en el
data-sitekey="KEY"del div del widget.
reCAPTCHA Enterprise con respaldo v2
Algunas Enterprise puntúan con la v3 Enterprise y reservan la v2 para el desafío. Detéctalo por enterprise.js:
# Detect and handle Enterprise + v2 combo
if "recaptcha/enterprise.js" in html:
# Use enterprise parameter for v3
v3_params = {"enterprise": 1, "version": "v3"}
else:
v3_params = {"version": "v3"}
Varios formularios en la misma página
- Con varios formularios (login y registro, por ejemplo), cada uno puede llevar su propia instancia de reCAPTCHA.
- Extrae el sitekey del formulario concreto al que apuntas:
# Target the login form specifically
login_form = soup.find("form", id="login-form")
widget = login_form.find(attrs={"data-sitekey": True})
sitekey = widget["data-sitekey"]
Preguntas frecuentes
¿Por qué aparece la v2 aunque ya resolví la v3?
- La puntuación de la v3 en ese intento quedó por debajo del umbral del sitio.
- No es un error tuyo: es el respaldo que salta ante tráfico automatizado. Resuélvela y continúa.
¿Cuánto me cuesta resolver reCAPTCHA dual?
- Depende de tu volumen, no de cuántas versiones resuelvas: CaptchaAI cobra por thread concurrente, no por resolución.
- Cada plan incluye resoluciones ilimitadas por thread durante el mes.
- El plan BASIC ($15/mes, 5 threads) sobra para probar un flujo dual; subes de plan cuando crece tu concurrencia.
¿Puedo detectar la doble implementación sin abrir un navegador?
- Sí, casi siempre: descarga el HTML y busca
?render=(v3) y eldata-sitekey(v2), como hace el script de arriba. - Solo necesitas un navegador cuando la v2 se inyecta por JavaScript tras cargar la página.
¿Sirve una sola llamada a la API para las dos versiones?
- No: la v2 y la v3 son tipos distintos, con sitekeys y métodos diferentes; cada uno consume su propia llamada a CaptchaAI.
- Si la v3 supera el umbral, te quedas en una sola llamada y la v2 nunca aparece.
Resumen
El reCAPTCHA dual usa la v3 para una evaluación invisible y la v2 como respaldo visible cuando la puntuación de la v3 es demasiado baja. La estrategia óptima es resolver primero la v3 con CaptchaAI, enviar el token y recurrir a la v2 solo si el respaldo se dispara.
Artículos relacionados
- reCAPTCHA en aplicaciones de una sola página (SPA)
- Resolver el callback de reCAPTCHA v2 con la API
- reCAPTCHA v2 y Turnstile en el mismo sitio