Im Log steht "success": true, und Ihr Backend weist das Token trotzdem ab. Schuld ist dann selten der Solver, sondern die Domain: reCAPTCHA bindet jedes Token an den Hostnamen der Seite, auf der es entstanden ist. Weicht die pageurl auch nur um ein www. vom Ziel ab, sieht das Token gültig aus – die Serverprüfung fällt trotzdem negativ aus.
Drei Punkte erklären den Großteil dieser Fälle:
- Der Hostname im Token stammt aus der Seite, auf der das Widget geladen wurde – nicht aus Ihrem HTTP-Client.
- Die
pageurlmuss exakt der URL entsprechen, an die Sie das gelöste Token absenden. - Weiterleitungen, Login-Subdomains und regionale CDN-Hostnamen sind die häufigsten Auslöser.
Wie reCAPTCHA Token an eine Domain bindet
Über die Annahme entscheiden drei Stellen – nur eine liegt in Ihrer Hand.
| Kontrollpunkt | Was geprüft wird |
|---|---|
| Client-seitig | Das Widget lädt nur auf freigegebenen Domains (abschaltbar) |
| Token-Erzeugung | Der eingebettete Hostname entspricht dem Seitenursprung |
| Serverprüfung | siteverify liefert den Hostnamen; der Server entscheidet über die Annahme |
Der Ablauf im Überblick
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 }
Entscheidend ist die letzte Verzweigung: Google meldet den Hostnamen, aber ob er exakt passen muss, legt der Website-Betreiber fest.
Drei Fehlerbilder, die Sie im Log sehen
Fehler 1: Der Hostname in der siteverify-Antwort weicht ab
Das Token ist gültig, doch hostname zeigt eine andere Domain als erwartet:
{
"success": true,
"hostname": "subdomain.example.com",
"challenge_ts": "2025-01-15T10:30:00Z"
}
Manche Server-Implementierungen lehnen genau das ab:
# 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
Typisch sind drei Konstellationen: für www.example.com gelöst, gegen example.com validiert; auf staging.example.com gelöst, gegen Produktion geprüft; oder ein CDN liefert die Seite unter abweichendem Hostnamen aus. Der Fix bleibt derselbe: Die pageurl muss genau die Domain sein, an die das Token geht.
Fehler 2: Das Widget lädt gar nicht erst
ERROR: Invalid domain for site key
Diese Meldung erscheint, bevor eine Lösung angefordert wird, und ist immer konfigurationsseitig: Der Sitekey gibt die Seite nicht frei, das Widget läuft über localhost oder file://, oder es wird eine IP-Adresse statt eines Domainnamens aufgerufen. Am fremden Sitekey lässt sich nichts ändern; in eigenen Testumgebungen tragen Sie die Domain im reCAPTCHA-Admin nach.
Fehler 3: Token korrekt gelöst, Server lehnt trotzdem ab
{
"success": false,
"error-codes": ["invalid-input-response"]
}
Der Klassiker in Pipelines: Die Lösung kommt sauber zurück, das Token gehört aber zu einer anderen Domain als der geprüften.
# 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!
Ein Blick auf beide Stellen – Solver-Aufruf und Ziel-Request – deckt den Unterschied sofort auf. Definieren Sie die Ziel-URL an einer Stelle und reichen Sie sie an beide Aufrufe weiter.
Schnelldiagnose: Symptom, Ursache, Fix
| Symptom | Wahrscheinliche Ursache | Diagnose | Fix |
|---|---|---|---|
| Token wird immer abgelehnt | Page-URL passt nicht zur Zieldomain | Solver-URL mit Ziel-URL vergleichen | Page-URL angleichen |
| Läuft auf www, scheitert ohne www | Abweichende Domain-Variante | Weiterleitung prüfen | Die Variante der Zielseite nutzen |
| Mal erfolgreich, mal nicht | CDN liefert wechselnde Hostnamen | Domain pro Anfrage vergleichen | Feste URL aus der Kette |
| Im Browser erfolgreich, im Skript nicht | Anderer Ursprung im Skript | Adressleiste mit Skript-URL vergleichen | Browser-URL übernehmen |
| Enterprise-Token abgelehnt | Falsche Projekt- oder Domain-Bindung | Sitekey-Zuordnung prüfen | Domains in der Enterprise Console pflegen |
Hinweis: Prüfen Sie zuerst, ob der Fehler bei jedem Lauf auftritt. Deterministisch heißt Domain-Fehler; sporadisch deutet auf abgelaufene Token oder Rate-Limiting hin.
Praxisbeispiel: Nightly-Pipeline gegen die eigene Staging-Umgebung
Ein Team in Köln testet den Login seines eigenen Shopware-Backends automatisiert. Die Staging-Instanz läuft auf einem Hetzner-Server unter staging.shop.example; die GitLab-CI-Pipeline zieht die Ziel-URL aber aus einer Variable, die noch auf shop.example zeigt und dort per Weiterleitung landet. Lokal läuft der Test durch, nachts scheitert er mit invalid-input-response – ein Domain-Fehler, kein Solver-Problem. Mit der finalen URL in der Variable ist die Fehlerklasse erledigt.
Danach hängt der Durchsatz nur an der Parallelität: CaptchaAI rechnet pro gleichzeitigem Thread ab, nicht pro Lösung – für eine Nightly-Pipeline mit wenigen parallelen Jobs reicht BASIC (15 $/Monat, 5 Threads). Preise in US-Dollar.
Wie streng prüft reCAPTCHA Subdomains?
Standardmäßig ist die Prüfung kein strikter Subdomain-Abgleich. Was zählt, ist die Registrierung des Betreibers:
| Registrierte Domain | Akzeptierte Herkunft |
|---|---|
example.com |
example.com, www.example.com, sub.example.com (bei aktivierter Wildcard) |
www.example.com |
nur www.example.com (bei strikter Konfiguration) |
*.example.com |
jede Subdomain von example.com |
localhost |
nur localhost (für die Entwicklung) |
Permissive oder strikte Prüfung auf Serverseite
Ob eine Subdomain durchgeht, entscheidet die Gegenseite – beide Varianten sind üblich:
# 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
Vier Fixes gegen Domain-Fehler in der Automatisierung
Fix 1: Page-URL exakt angleichen
Eine Variable für die Ziel-URL, die sowohl in die Solver-Anfrage als auch in den späteren Request geht:
# 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,
})
Fix 2: www und Nicht-www sauber behandeln
Prüfen Sie, welche Variante die Zielseite tatsächlich ausliefert:
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
Fix 3: Der Weiterleitungskette bis zum Ziel folgen
Manche Portale reichen den Login an einen separaten Auth-Host weiter:
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
Das Ergebnis gehört in die Konfiguration, nicht in jeden einzelnen Lauf.
Fix 4: Domain aus dem reCAPTCHA-iframe ableiten
Bei unübersichtlicher Seitenstruktur hilft ein Blick auf das eingebettete Widget:
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
Diagnose-Skript vor dem ersten Solve
Klären Sie die Domain-Situation vorab, statt Fehler nach dem Absenden zu analysieren. Das Skript folgt Weiterleitungen, vergleicht die www-Varianten und nennt die passende Page-URL:
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()
| Ausgabe | Bedeutung |
|---|---|
Use as pageurl |
Dieser Wert gehört in die Solver-Anfrage |
redirect |
Ihre URL ist nicht die Seite, auf der das Widget rendert |
www_redirect |
Die Domain-Variante löst je nach Umgebung anders auf |
Häufige Fragen
Liegt es an der Domain oder an einem abgelaufenen Token?
An der Domain, wenn der Fehler bei jedem Lauf auftritt. reCAPTCHA-Tokens sind nur rund zwei Minuten gültig; Ablauffehler treten sporadisch auf und hängen an langen Wartezeiten zwischen Lösung und Absenden.
Welche URL gehört in die pageurl, wenn die Seite weiterleitet?
Die letzte URL der Kette – die Seite, auf der das Widget rendert. Ermitteln Sie sie einmalig mit aktivierten Weiterleitungen und hinterlegen Sie den Wert in der Konfiguration.
Brauche ich für www.example.com und example.com getrennte Sitekeys?
Nein, ein Sitekey kann mehrere Hostnamen abdecken. Entscheidend ist, welche davon freigegeben sind und wie streng der Server den zurückgegebenen hostname auswertet.
Verändern Proxys oder ein CDN den Hostnamen im Token?
Das Token selbst nicht. Ein Proxy kann aber dazu führen, dass Ihr Skript eine andere Variante der Seite erreicht als Ihr Browser – etwa einen regionalen CDN-Hostnamen. Prüfen Sie die finale URL aus Sicht des Skripts.
Gilt dieselbe Domain-Bindung auch für Cloudflare Turnstile?
Ja. Auch Turnstile bindet seine Antwort an die Seite, auf der die Abfrage lief; für GeeTest v3 gilt dasselbe. Nur die Feldnamen im Formular unterscheiden sich – bei reCAPTCHA ist es g-recaptcha-response.
Fazit
Der häufigste Fehler in Automatisierungen ist keine misslungene Lösung, sondern eine unpassende Page-URL: Die URL, die Sie an CaptchaAI übergeben, muss der Domain entsprechen, an die das Token geht. Folgen Sie den Weiterleitungen bis zur echten Zielseite, klären Sie die www-Variante einmal verbindlich und lassen Sie vorher das Diagnose-Skript laufen.