Troubleshooting

Erreurs et correctifs de vérification de domaine reCAPTCHA

Un token reCAPTCHA résolu sans erreur mais refusé par le serveur cible a presque toujours la même cause : le nom d'hôte inscrit dans le token ne correspond pas à celui de la page où vous l'envoyez. Le pageurl transmis au solveur est donc la première chose à vérifier, avant les proxys, les en-têtes ou la clé du site.

Cette panne coûte cher parce qu'elle est muette : ni code d'erreur, ni quota dépassé. La résolution réussit, siteverify renvoie un hostname inattendu, la soumission échoue. Ce guide part du symptôme, remonte à la règle reCAPTCHA en cause et donne le correctif.

Diagnostic express : du symptôme à la cause

Situez votre panne dans ce tableau, puis lisez la section correspondante.

Symptôme Cause probable Correctif
Token systématiquement refusé pageurl différent du domaine de soumission Alignez le pageurl sur le domaine cible
Marche en www, échoue sans www Variantes de domaine non équivalentes Utilisez la variante réellement servie
Marche une fois sur deux CDN ou répartiteur servant plusieurs domaines Figez l'URL de la chaîne de redirection
Marche dans le navigateur, échoue dans le script Origines différentes Reprenez l'URL finale du navigateur
Token reCAPTCHA Enterprise refusé Mauvaise liaison projet / domaine Corrigez les domaines dans la console Enterprise

Ce que reCAPTCHA vérifie réellement

Site owner registers reCAPTCHA → adds allowed domains (example.com, www.example.com)
    ↓
reCAPTCHA widget loads on example.com → matches allowed domain ✓
    ↓
Token generated with embedded hostname
    ↓
Server validates token via siteverify API
    ↓
Google checks: Does token hostname match allowed domains?
    ├─ YES → { "success": true, "hostname": "example.com" }
    └─ NO  → { "success": false, error or hostname mismatch }

Ce parcours tient en trois contrôles, et un seul décide du sort de votre token.

  • Côté client : le widget ne se charge que sur les domaines autorisés — contrôle facultatif, désactivable par le propriétaire du site.
  • Génération du token : le nom d'hôte de la page est intégré au token.
  • Validation serveur : siteverify renvoie ce nom d'hôte, et le code du site cible décide de l'accepter ou non.

Retenez le troisième : c'est le site, et non Google, qui tranche sur un nom d'hôte voisin. Deux sites configurés à l'identique réagissent différemment au même token.

Les trois erreurs de domaine que vous rencontrerez

Erreur 1 : nom d'hôte inattendu dans la réponse siteverify

{
    "success": true,
    "hostname": "subdomain.example.com",
    "challenge_ts": "2025-01-15T10:30:00Z"
}

Le token est valide, mais le champ hostname ne correspond pas à celui attendu. Beaucoup d'implémentations refusent alors la requête sans message :

# Server-side validation that checks hostname
def validate_token(token, secret_key, expected_hostname):
    result = requests.post(
        "https://www.google.com/recaptcha/api/siteverify",
        data={"secret": secret_key, "response": token},
    ).json()

    if not result.get("success"):
        return False

    # This check causes failures when hostnames don't match
    if result.get("hostname") != expected_hostname:
        return False  # Domain mismatch!

    return True

Trois situations le produisent.

Nom d'hôte du token Nom d'hôte attendu
www.example.com example.com
staging.example.com example.com
Hôte réécrit par un proxy ou un CDN Domaine public

Correctif : alignez le pageurl de votre requête de résolution sur le domaine où le token sera soumis.

Erreur 2 : le widget refuse de s'afficher

Le widget ne se charge pas et la console affiche :

ERROR: Invalid domain for site key
  1. Les domaines autorisés de la clé du site n'incluent pas la page courante.
  2. La page est chargée depuis localhost ou via file://.
  3. Une adresse IP remplace le nom de domaine.

Correctif côté automatisation : cette configuration appartient au propriétaire du site ; transmettez un pageurl pointant vers un domaine autorisé.

Erreur 3 : token refusé malgré une résolution correcte

{
    "success": false,
    "error-codes": ["invalid-input-response"]
}

Le token a été généré pour un autre domaine que celui qui le valide : pageurl sur le domaine apex, soumission sur un sous-domaine applicatif.

# WRONG: pageurl doesn't match actual target
submit = requests.post("https://ocr.captchaai.com/in.php", data={
    "key": API_KEY,
    "method": "userrecaptcha",
    "googlekey": sitekey,
    "pageurl": "https://example.com/login",  # ← Must match actual domain
    "json": 1,
})

# But submitting token to:
requests.post("https://app.example.com/login", ...)  # Different subdomain!

Règles de correspondance de domaine

Correspondance exacte ou caractère générique

reCAPTCHA n'impose pas par défaut une correspondance stricte de sous-domaine : tout dépend de la configuration du site.

Domaine enregistré Origines acceptées
example.com example.com, www.example.com, sub.example.com (si le générique est activé)
www.example.com www.example.com uniquement (en mode strict)
*.example.com Tout sous-domaine de example.com
localhost localhost uniquement (développement)

Comment le serveur interprète le nom d'hôte

Dans la réponse siteverify, le hostname reflète la page qui a généré le token. Le serveur choisit ensuite entre validation permissive et validation stricte :

# Permissive validation (accepts any subdomain)
def validate_permissive(token, secret, base_domain):
    result = requests.post(
        "https://www.google.com/recaptcha/api/siteverify",
        data={"secret": secret, "response": token},
    ).json()

    if not result.get("success"):
        return False

    hostname = result.get("hostname", "")
    return hostname == base_domain or hostname.endswith(f".{base_domain}")


# Strict validation (exact match only)
def validate_strict(token, secret, expected_hostname):
    result = requests.post(
        "https://www.google.com/recaptcha/api/siteverify",
        data={"secret": secret, "response": token},
    ).json()

    return result.get("success") and result.get("hostname") == expected_hostname

Une validation stricte ne pardonne aucun écart, pas même le préfixe www : partez de cette hypothèse, elle vous évite les pannes intermittentes.

Corriger les erreurs de domaine dans vos scripts

Dans l'ordre, avant de toucher au reste de la pile :

  1. Suivez les redirections de l'URL cible jusqu'au domaine final.
  2. Comparez ce domaine avec le pageurl envoyé au solveur.
  3. Testez séparément les variantes www et non-www.
  4. Journalisez le hostname renvoyé par siteverify à chaque résolution.

Correctif 1 : aligner le pageurl sur la cible réelle

Le correctif qui résout la majorité des cas : le pageurl désigne la page exacte où le token partira.

# Correct: pageurl matches where you'll submit the token
target_url = "https://www.example.com/login"

submit = requests.post("https://ocr.captchaai.com/in.php", data={
    "key": API_KEY,
    "method": "userrecaptcha",
    "googlekey": "6LcR_RsTAAAAAN_r0GEkGBfq3L7KmU5JbPHJtwNp",
    "pageurl": target_url,  # Must match the actual domain
    "json": 1,
})

Correctif 2 : traiter www et non-www comme deux domaines

Ne devinez pas la variante servie : la redirection vous la donne.

from urllib.parse import urlparse

def normalize_url(url):
    """Normalize URL for consistent domain matching."""
    parsed = urlparse(url)
    # Use exactly what the target site uses
    # Check if the site redirects www → non-www or vice versa
    return f"{parsed.scheme}://{parsed.netloc}{parsed.path}"

# Test which variant the site uses
response = requests.get("https://example.com/login", allow_redirects=True)
actual_url = response.url  # May be https://www.example.com/login after redirect

Correctif 3 : suivre la chaîne de redirection

Beaucoup de portails redirigent vers un domaine d'authentification distinct.

def get_final_url(url):
    """Follow redirects to find the actual CAPTCHA page domain."""
    response = requests.get(url, allow_redirects=True, timeout=15)
    return response.url

# Login URL might redirect:
# https://example.com/login → https://auth.example.com/login
final_url = get_final_url("https://example.com/login")
# Use final_url as pageurl for solver

Correctif 4 : lire le domaine dans l'iframe reCAPTCHA

Quand le widget est chargé dans une iframe, le domaine de liaison se lit dans le HTML.

from bs4 import BeautifulSoup
from urllib.parse import urlparse

def extract_recaptcha_domain(html, page_url):
    """Extract the domain reCAPTCHA uses for token binding."""
    soup = BeautifulSoup(html, "html.parser")

    # Check for reCAPTCHA iframe
    iframe = soup.find("iframe", src=lambda s: s and "recaptcha" in s)
    if iframe:
        src = iframe.get("src", "")
        # The iframe URL may contain the domain parameter
        if "domain=" in src:
            # Extract domain from iframe URL
            pass

    # Default: use the page URL's domain
    return urlparse(page_url).netloc

Un script de diagnostic à garder sous la main

Avant d'ouvrir un ticket, comparez l'URL visée et celle que le site sert réellement.

import requests
from urllib.parse import urlparse

class DomainDiagnostic:
    """Diagnose domain verification issues for reCAPTCHA solving."""

    def __init__(self, target_url):
        self.target_url = target_url
        self.issues = []

    def check_redirects(self):
        """Check if the URL redirects to a different domain."""
        try:
            response = requests.get(
                self.target_url, allow_redirects=True, timeout=15,
                headers={"User-Agent": "Mozilla/5.0 Chrome/120.0.0.0"},
            )
            final_url = response.url
            original_domain = urlparse(self.target_url).netloc
            final_domain = urlparse(final_url).netloc

            if original_domain != final_domain:
                self.issues.append({
                    "type": "redirect",
                    "message": f"Redirects from {original_domain} to {final_domain}",
                    "fix": f"Use pageurl: {final_url}",
                })

            return final_url
        except Exception as e:
            self.issues.append({"type": "error", "message": str(e)})
            return self.target_url

    def check_www_variant(self):
        """Check if www and non-www point to the same content."""
        parsed = urlparse(self.target_url)
        domain = parsed.netloc

        if domain.startswith("www."):
            alt_domain = domain[4:]
        else:
            alt_domain = f"www.{domain}"

        alt_url = self.target_url.replace(domain, alt_domain)

        try:
            alt_response = requests.get(alt_url, allow_redirects=True, timeout=10)
            alt_final = urlparse(alt_response.url).netloc

            if alt_final != domain and alt_final != alt_domain:
                self.issues.append({
                    "type": "www_redirect",
                    "message": f"{alt_domain} redirects to {alt_final}",
                })
        except Exception:
            pass

    def report(self):
        """Generate diagnostic report."""
        final_url = self.check_redirects()
        self.check_www_variant()

        print(f"Target URL: {self.target_url}")
        print(f"Final URL:  {final_url}")
        print(f"Use as pageurl: {final_url}")

        if self.issues:
            print("\nIssues found:")
            for issue in self.issues:
                print(f"  [{issue['type']}] {issue['message']}")
                if "fix" in issue:
                    print(f"  Fix: {issue['fix']}")
        else:
            print("\nNo domain issues detected.")


# Usage
diag = DomainDiagnostic("https://example.com/login")
diag.report()

Journalisez ce nom d'hôte à chaque résolution : vous saurez si une régression vient d'un changement côté site ou de votre code. Côté RGPD, limitez ces logs à l'URL et à l'horodatage.

Cas concret : un SaaS français derrière un CDN

Une équipe QA parisienne teste le parcours de connexion d'une application hébergée chez OVHcloud. Le domaine public est www.exemple.fr, la page de connexion redirige vers auth.exemple.fr, et le CDN sert la variante apex exemple.fr sur certaines régions. Les scripts envoient https://exemple.fr/connexion comme pageurl : en staging tout passe, en production un test sur trois échoue. Les logs le montrent — le hostname renvoyé par siteverify alterne entre exemple.fr et auth.exemple.fr, et le back-end valide en mode strict.

La correction tient en deux gestes : suivre la redirection avant chaque résolution, puis passer l'URL finale en pageurl. Le gain dépasse la fiabilité : les plans CaptchaAI se comptent en threads simultanés — BASIC ($15/mois, 5 threads) — et non en résolutions, donc chaque retry inutile mobilise un thread que vos tests n'ont plus. Cloudflare Turnstile et GeeTest v3 lient eux aussi le token à l'origine de la page.

Questions fréquentes

Comment savoir quel nom d'hôte est inscrit dans mon token ?

Validez-le une fois avec siteverify en environnement de test et lisez le champ hostname. C'est la seule source fiable : le token est opaque, et déduire le domaine de l'URL de départ est justement ce qui crée la panne.

Un token généré pour un domaine fonctionne-t-il sur un autre ?

Non. Le token est lié au nom d'hôte de la page qui l'a produit : example.com et other-site.com sont incompatibles, et deux sous-domaines échouent dès que la validation est stricte.

Un reverse proxy ou Cloudflare peut-il fausser la vérification ?

Oui, dès que la couche intermédiaire réécrit l'hôte ou sert plusieurs domaines. Deux vérifications suffisent à trancher :

  • l'en-tête Host réellement émis par votre script ;
  • le nom d'hôte final vu dans la barre d'adresse du navigateur.

Mes tentatives échouées consomment-elles mes threads CaptchaAI ?

Indirectement, oui : chaque requête occupe un thread le temps de la résolution, et un pageurl erroné consomme ce temps pour rien. La facturation reste au thread simultané, avec des résolutions illimitées par thread, mais votre débit utile se dégrade.

Faut-il une clé du site différente entre staging et production ?

Pas nécessairement, mais les domaines des deux environnements doivent figurer dans la clé utilisée, sinon localhost ou staging.example.com échouera. Vérifiez cette liste avant d'accuser le solveur.

À retenir

  • Le token est lié au nom d'hôte de la page qui l'a généré : l'écart vient presque toujours du pageurl transmis à CaptchaAI.
  • Suivez les redirections pour identifier le domaine réellement servi, et traitez www et non-www comme deux domaines.
  • Journalisez le hostname renvoyé par siteverify pour repérer une dérive avant qu'elle ne casse vos scripts.

Articles connexes

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