Troubleshooting

Manejo de reCAPTCHA v2 y v3 en la misma página

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
Los comentarios están deshabilitados para este artículo.

Publicaciones relacionadas