Troubleshooting

Gestion de reCAPTCHA v2 et v3 sur la même page

Quand une page charge à la fois reCAPTCHA v2 et v3, la marche à suivre tient en trois points :

  • Résolvez la v3 en premier, comme le ferait le navigateur.
  • Ne résolvez la v2 que si le site déclenche le repli visible.
  • N'intervertissez jamais les deux sitekeys : chacune va avec sa version.

Toute la difficulté vient du « seulement si » : ce sont deux types CAPTCHA distincts, avec des sitekeys et des méthodes de résolution différentes. Le cas typique : un formulaire de connexion qui note la session avec la v3 et n'affiche la case v2 que si le score passe sous le seuil de l'éditeur.


Pourquoi ce montage combine v2 et v3

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

Le but est de concilier friction minimale et sécurité :

  • La majorité des visiteurs (score v3 élevé) ne voient aucun défi.
  • Les sessions suspectes (score v3 faible) reçoivent le défi v2.
  • L'éditeur règle lui-même le seuil qui sépare l'invisible du visible.

Conséquence : la même URL peut n'exiger aucune interaction un jour et imposer un défi visible le lendemain. Votre code doit gérer les deux cas.


Schéma 1 : v3 en pré-évaluation, v2 en repli

Le montage le plus répandu. La v3 s'exécute d'abord ; la v2 n'apparaît qu'en cas de score insuffisant.

<!-- 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>

Schéma 2 : des clés différentes selon l'action

Certains sites réservent la v3 à la surveillance passive et sortent la v2 pour les actions les plus sensibles :

Homepage → v3 only (passive score)
Login page → v3 assessment, v2 fallback
Checkout → v2 always (high security)
Contact form → v3 only

Schéma 3 : un seul script, deux versions

Google permet de charger un unique script reCAPTCHA qui pilote à la fois la v2 et 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>

Détecter les deux versions avant de résoudre

Confirmez d'abord que la page combine les deux versions et récupérez chaque sitekey. Deux signaux suffisent souvent :

  • La v3 se lit au paramètre render du script (api.js?render=...) ou à un appel grecaptcha.execute(...).
  • La v2 se lit à l'attribut data-sitekey du widget, souvent dans une div masquée au départ.

Vérifier la page en 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://example.com/login")
print(detection)

Vérifier la page en 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://example.com/login").then(console.log);

Résoudre le double reCAPTCHA avec CaptchaAI

Avec les deux sitekeys en main, trois approches selon ce que vous savez du site :

  • Vous ignorez si la v2 s'affichera → approche 1 (v3 puis v2).
  • La v2 s'affiche toujours pour votre trafic → approche 2 (v2 directe).
  • Montage complexe ou dynamique → approche 3 (navigateur).

Côté coût, CaptchaAI facture au thread (une résolution en cours), pas à la résolution : chaque plan inclut des résolutions illimitées par thread. Le plan BASIC ($15/mois, 5 threads) couvre donc autant de résolutions v2 et v3 que nécessaire. Ce qui compte, c'est le nombre mené en parallèle.

Approche 1 : la v3 d'abord, la v2 au besoin

Cette stratégie reproduit le flux du site lui-même :

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://example.com/login",
)
print(f"Solved with {result['version']}")

Approche 2 : viser directement la v2

Si le site sert toujours la v2 au trafic automatisé (le score v3 restera bas), inutile de payer une résolution v3 vouée à échouer : attaquez directement la v2.

# If you consistently fail v3 assessment, just solve v2 directly
token = solve_v2(v2_site_key, page_url)
submit_form(token)

Vous économisez le temps et le thread mobilisés par une v3 qui ne franchira pas le seuil.

Approche 3 : piloter le repli au navigateur

Pour les montages complexes, laissez un navigateur gérer le basculement et lire l'état réel du widget v2 :

from selenium import webdriver
from selenium.webdriver.common.by import By
import time

driver = webdriver.Chrome()
driver.get("https://example.com/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()

Gérer deux sitekeys sur la même page

Un site en double reCAPTCHA expose deux sitekeys différentes ; les confondre produit un token invalide, rejeté à la soumission. Retenez leur emplacement :

  • Clé v3 : dans l'URL du script ?render=KEY et dans grecaptcha.execute('KEY', ...).
  • Clé v2 : dans le data-sitekey="KEY" de la div du widget.

reCAPTCHA Enterprise avec repli v2

Certaines intégrations Enterprise notent la session avec la v3 Enterprise et gardent la v2 classique pour le défi visible :

# 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"}

Plusieurs formulaires sur la même page

Si une page comporte plusieurs formulaires (connexion + inscription), chacun peut embarquer sa propre instance reCAPTCHA. Extrayez la sitekey du formulaire précis que vous ciblez :

# 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"]

Dépannage rapide

Les ratés viennent presque toujours d'une clé mal attribuée ou d'un repli mal lu :

Symptôme Cause probable Correctif
Le site rejette le token (invalid-input-response) Mauvaise sitekey pour la version résolue Résolvez la v3 avec la clé ?render=, la v2 avec data-sitekey
La v2 apparaît alors que la v3 a renvoyé un token Score v3 sous le seuil : le repli s'est déclenché Enchaînez une résolution v2, c'est le comportement attendu
Le conteneur v2 reste masqué après la soumission Le repli est décidé côté serveur, pas dans le DOM Analysez la réponse HTTP renvoyée après l'envoi du token v3
Résolution v3 en timeout systématique version ou action absent de la requête Transmettez version: v3 et l'action attendue (ex. login)

Questions fréquentes

Un double reCAPTCHA consomme-t-il deux threads sur mon plan ?

Non, pas en parallèle dans le cas courant. Vous résolvez la v3, puis la v2 seulement si le repli se déclenche : les résolutions s'enchaînent au lieu d'occuper deux threads en même temps. Comme chaque plan inclut un nombre de résolutions illimité par thread, ce parcours n'entraîne aucun surcoût unitaire.

Comment détecter la double implémentation sans navigateur ?

Une requête HTTP suffit le plus souvent : récupérez le HTML, cherchez le paramètre ?render= du script (v3) et l'attribut data-sitekey du widget (v2). Réservez Selenium aux pages qui injectent le widget v2 par JavaScript après le chargement.

Que faire si le site utilise reCAPTCHA Enterprise pour la v3 ?

Traitez la v3 comme une résolution Enterprise (paramètre enterprise) et gardez la v2 classique. Le repli est identique : seule la notation du score change.


En résumé

Une page en double reCAPTCHA note la session avec la v3 et n'affiche la v2 que si le score tombe sous le seuil. Repérez chaque version via le paramètre render (v3) et le data-sitekey (v2), puis appliquez la règle : résolvez la v3 avec CaptchaAI, soumettez le token, et ne passez à la v2 qu'en cas de repli. Chaque version est un appel API distinct, avec sa propre sitekey.

Articles connexes

Les commentaires sont désactivés pour cet article.