Если решатель вернул корректный токен, а бэкенд отвечает invalid-input-response, дело почти всегда не в решении, а в домене: токен привязан к одному имени хоста, а проверяется на другом. Хватает расхождения в www. или в один поддомен после редиректа, и сбой молчаливый — в логах решателя ошибки нет. Разбор ниже устроен так:
- где reCAPTCHA сверяет домен;
- три сценария отказа;
- правила сопоставления поддоменов;
- как найти реальный
pageurl.
Почему токен привязан к домену
reCAPTCHA вшивает имя хоста страницы в токен: владелец сайта регистрирует разрешённые домены для sitekey, а Google при валидации возвращает имя хоста, где токен был сгенерирован.
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 }
Три точки, где сверяется домен
| Точка проверки | Что именно сверяется |
|---|---|
| Клиентская часть | Виджет грузится только на разрешённых доменах (можно отключить) |
| Генерация токена | Вшитое в токен имя хоста совпадает с источником страницы |
| Валидация на сервере | siteverify возвращает hostname, сервер решает, принять ли его |
Вывод для автоматизации:
- первую и третью строку контролирует владелец сайта;
- вы контролируете только вторую — через
pageurl; - имя хоста в токене должно совпасть с адресом отправки.
Три ошибки, которые дают почти все обращения в поддержку
Ошибка 1: в ответе siteverify не то имя хоста
{
"success": true,
"hostname": "subdomain.example.com",
"challenge_ts": "2025-01-15T10:30:00Z"
}
Токен валиден, но в поле hostname не тот домен, которого ждёт сервер. Строгая валидация его отбраковывает:
# 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
Откуда берётся:
- токен решён для
www.example.com, а проверяется наexample.com; - токен решён для
staging.example.com, а проверяется наexample.com; - прокси или CDN подменяет имя хоста.
Что делать: взять домен, на который реально уходит токен, и подставить его в pageurl запроса к решателю.
Ошибка 2: виджет отказывается загружаться
Виджет reCAPTCHA не отрисовывается либо показывает ошибку:
ERROR: Invalid domain for site key
Откуда берётся: домена страницы нет в списке разрешённых; виджет открыт с localhost или по file://; вместо домена используется IP-адрес.
Что делать: на чужом сайте — никак. На своём staging-стенде добавьте домен в консоли reCAPTCHA до тестов.
Ошибка 3: токен отклонён, хотя решён корректно
{
"success": false,
"error-codes": ["invalid-input-response"]
}
Токен создан для одного домена, а проверяется на другом. Частая причина — pageurl задачи не совпадает с целевым:
# 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://staging.example.com/qa-login", # ← Must match actual domain
"json": 1,
})
# But submitting token to:
requests.post("https://app.https://staging.example.com/qa-login", ...) # Different subdomain!
В последних строках задача уходит с одним хостом, а токен — на другой поддомен: для reCAPTCHA это разные домены.
Таблица быстрой диагностики
| Симптом | Вероятная причина | Как проверить | Что сделать |
|---|---|---|---|
| Токен отклоняется всегда | pageurl не совпадает с доменом отправки |
Сравнить pageurl задачи с доменом отправки |
Привести pageurl в соответствие |
| На www работает, без www — нет | Разошлись варианты домена | Посмотреть редиректы | Взять вариант, который использует сайт |
| Иногда проходит, иногда нет | CDN отдаёт разные домены | Проверить домен от запроса к запросу | Зафиксировать один URL из цепочки |
| В браузере работает, в скрипте — нет | Другой источник запроса | Сверить адресную строку с pageurl |
Взять конечный URL из браузера |
| Токен Enterprise отклонён | Не та привязка проекта | Проверить привязку Enterprise sitekey | Уточнить домены в консоли Enterprise |
Правила сопоставления доменов
Все три ошибки упираются в один вопрос: насколько строго сайт сверяет поддомены. Решает это владелец сайта.
Точное совпадение и подстановочный знак
| Зарегистрированный домен | Допустимый источник |
|---|---|
example.com |
example.com, www.example.com, sub.example.com (с подстановочным знаком) |
www.example.com |
только www.example.com (строгий режим) |
*.example.com |
любой поддомен example.com |
localhost |
только localhost (разработка) |
Как ведёт себя проверка hostname на сервере
Поле hostname отражает страницу, где токен был сгенерирован. Дальше решает валидация на бэкенде:
# 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
Мягкий вариант прощает www и любой поддомен, строгий требует посимвольного совпадения. Без доступа к коду валидации исходите из строгого сценария.
Как чинить доменные ошибки в автоматизации
Приёмы ниже — по возрастанию сложности.
Приём 1: сделать pageurl точным
Передавайте ровно тот адрес, куда уйдёт токен:
# Correct: pageurl matches where you'll submit the token
target_url = "https://www.https://staging.example.com/qa-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,
})
Приём 2: развести www и не-www
Не гадайте — спросите у сайта:
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://staging.example.com/qa-login", allow_redirects=True)
actual_url = response.url # May be https://www.https://staging.example.com/qa-login after redirect
Приём 3: вытащить домен из цепочки редиректов
Частый случай — форма входа на auth-хосте:
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://staging.example.com/qa-login → https://auth.https://staging.example.com/qa-login
final_url = get_final_url("https://staging.example.com/qa-login")
# Use final_url as pageurl for solver
Приём 4: посмотреть, какой домен использует сам виджет
Нужен, когда виджет вынесен в iframe:
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
Скрипт диагностики домена
Класс ниже отвечает на два вопроса:
- меняется ли домен после редиректов;
- расходятся ли варианты с
www.и без него.
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://staging.example.com/qa-login")
diag.report()
Запускайте его на каждую новую цель.
Локальный сценарий: один стенд, четыре домена
Знакомая командам из Москвы, Минска и Алматы схема: QA-стенд в европейском регионе облака, перед ним CDN. У приложения одновременно живут staging.example.com, вариант с www., auth-хост формы входа и превью-домен на каждую ветку. Разработчик берёт адрес из закладок — и получает отказ.
Что помогает:
- прогонять URL через
get_final_url()до отправки задачи; - на нестабильных мобильных сетях добавлять повторную попытку;
- собирать со стенда только те данные, которые вы вправе обрабатывать (152-ФЗ для РФ, дисциплина в духе GDPR для кросс-граничных команд).
Часто задаваемые вопросы
Что означает ошибка «Invalid domain for site key»?
Домена страницы с виджетом нет в списке разрешённых для sitekey. Список редактирует владелец сайта.
Как понять, какой pageurl использовать?
Возьмите конечный адрес после редиректов: requests.get(url, allow_redirects=True).url. Передавайте именно его, а не набранный руками.
Что проверять первым делом, если токен отклонён?
Сравните hostname из ответа siteverify с адресом отправки токена — расхождение в www. или в поддомене видно сразу.
Почему на стенде токен проходит, а в продакшене — нет?
Четыре обычные причины:
- на тестах
localhostс особыми правилами домена; - в продакшене CDN отдаёт другой домен;
- включена более строгая проверка
hostname; - другая цепочка редиректов.
Где здесь CaptchaAI
Что сервис решает и чего не решает:
- reCAPTCHA v2 и v3, включая Enterprise-варианты;
- Cloudflare Turnstile и Challenge, GeeTest v3;
- image/OCR-, grid- и BLS-капчи;
- CaptchaFox (beta), Friendly Captcha (beta), Lemin (beta);
- hCaptcha и FunCaptcha не решает, GeeTest v4 — «скоро».
Сервис привязывает токен к переданному pageurl, но конфигурацию доменов не проверяет.
Оплата идёт за одновременные потоки, а не за каждое решение: BASIC ($15/мес, 5 потоков), STANDARD ($30/мес, 15 потоков), ADVANCE ($90/мес, 50 потоков) и далее до VIP-3 ($7,500/мес, 5000 потоков). Итерации отладки pageurl счёт не увеличивают.
Коротко
Доменные ошибки reCAPTCHA — почти всегда рассинхрон между pageurl задачи и адресом отправки токена. Порядок работы:
- Пройти редиректы до конечного URL.
- Зафиксировать вариант с
wwwили без него. - Сверить
hostnameиз ответаsiteverifyс ожидаемым. - И только после этого отправлять задачу в CaptchaAI.