Algunos sitios web implementan tanto reCAPTCHA v2 como v3 en la misma página. El patrón típico es: v3 se ejecuta de forma invisible en segundo plano y, si la puntuación es demasiado baja, v2 aparece como un desafío alternativo visible. Esta implementación dual crea confusión para la automatización porque es necesario manejar dos tipos de CAPTCHA diferentes con diferentes métodos de resolución. Esta guía cubre la detección, estrategias de resolución y casos extremos comunes.
Por qué los sitios usan v2 y v3 juntos
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
Este patrón ofrece lo mejor de ambos mundos:
- La mayoría de los usuarios (puntaje v3 alto) no ven CAPTCHA → baja fricción
- Usuarios sospechosos (puntaje v3 bajo) ven el desafío de seguridad v2 →
- Operador del sitio web controla el umbral entre invisible y visible
Patrones de implementación dual
Patrón 1: evaluación previa v3 + respaldo v2
El patrón más común. La v3 se ejecuta primero y la v2 aparece solo si es necesario.
<!-- 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: diferentes claves de sitio para diferentes acciones
Algunos sitios usan v3 para monitoreo pasivo y v2 para acciones específicas de alto riesgo:
Homepage → v3 only (passive score)
Login page → v3 assessment, v2 fallback
Checkout → v2 always (high security)
Contact form → v3 only
Patrón 3: guión único, modo dual
Google admite la carga de un único script reCAPTCHA que maneja tanto la versión 2 como la versión 3:
<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>
Detección de implementación dual
Detección de pitón
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 de 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 de resolución para reCAPTCHA dual
Estrategia 1: resolver primero la v3 y luego la v2 si es necesario
La estrategia óptima refleja el propio flujo del sitio:
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,
"min_score": "0.9",
"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: omitir v3, resolver v2 directamente
Si sabe que el sitio siempre muestra la versión 2 para el tráfico automatizado (la puntuación de la versión 3 será baja), omita la versión 3 y resuelva la versión 2 de inmediato:
# If you consistently fail v3 assessment, just solve v2 directly
token = solve_v2(v2_site_key, page_url)
submit_form(token)
Esto ahorra el tiempo y el costo de una solución v3 que podría no superar el umbral.
Estrategia 3: manejo basado en navegador
Para implementaciones complejas, utilice un navegador para manejar el flujo de reserva:
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()
Casos extremos
Dos claves de sitio diferentes en la misma página
Los sitios que utilizan reCAPTCHA dual tendrán DOS claves de sitio diferentes: una para v3 y otra para v2. La clave v3 aparece en la URL del script ?render=KEY y en grecaptcha.execute('KEY', ...). La clave v2 aparece en data-sitekey="KEY" en el div del widget. Usar la clave incorrecta para la versión incorrecta producirá tokens no válidos.
reCAPTCHA Enterprise con respaldo v2
Algunas implementaciones empresariales utilizan v3 Enterprise para puntuación y v2 para desafíos:
# 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"}
Múltiples formularios en una página
Si una página tiene varios formularios (inicio de sesión + registro), cada uno puede tener su propia instancia de reCAPTCHA. Extraiga la clave del sitio del formulario específico al que se dirige:
# 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
¿Necesito resolver tanto la v2 como la v3 en la misma página?
No. Normalmente, primero resuelve la versión 3 (se ejecuta automáticamente). Si la puntuación v3 supera el umbral del sitio, no aparece ningún desafío v2 y ya está. Solo necesita resolver v2 si la puntuación v3 activa el retroceso.
¿Puedo usar una única llamada API CaptchaAI para reCAPTCHA dual?
No. v2 y v3 son tipos CAPTCHA separados con diferentes claves de sitio y métodos de resolución. Cada uno requiere su propia llamada API a CaptchaAI. Sin embargo, solo necesita realizar una llamada si v3 pasa sin activar v2.
¿Cómo sé si se activó el respaldo v2?
Verifique la respuesta del servidor después de enviar el token v3. Si la respuesta contiene HTML del widget v2 o desencadena un desafío v2 (redireccionamiento o respuesta AJAX con CAPTCHA HTML), se activó el respaldo. En un navegador, verifique si el contenedor v2 se vuelve visible después del envío de v3.
¿Qué clave de sitio utilizo para cada versión?
La clave del sitio v3 está en la URL del script: api.js?render=V3_KEY. La clave del sitio v2 está en el widget HTML: data-sitekey="V2_KEY". Siempre son claves diferentes.
Resumen
Las implementaciones duales de reCAPTCHA utilizan v3 para una evaluación previa invisible y v2 como respaldo visible cuando la puntuación de v3 es demasiado baja. Detecte ambas versiones comprobando el parámetro de renderizado (v3) y la clave del sitio de datos del widget (v2). La estrategia de automatización óptima es: resolver v3 primero conCaptchaAI, envíe el token y resuelva v2 solo si se activa el respaldo. Cada versión requiere una llamada API independiente con su propia clave de sitio.
Artículos relacionados
- Aplicación dinámica de una sola página Recaptcha
- Cómo resolver la devolución de llamada de Recaptcha V2 usando Api
- Torniquete Recaptcha V2 Manejo en el mismo sitio