Troubleshooting

ERROR_PAGEURL: Руководство по устранению неполадок с несоответствием URL-адресов

Если API CaptchaAI возвращает ERROR_PAGEURL, токен почти наверняка не пройдёт проверку на стороне сайта. Решатель сверяет домен из параметра pageurl с доменом, на котором CAPTCHA реально показывается пользователю, и при малейшем расхождении отклоняет запрос.

Разбираемся, откуда берётся это расхождение и как проверить pageurl ещё до отправки — а не после того, как токен уже отклонён и поток потрачен впустую.


Причины ERROR_PAGEURL и быстрая диагностика

Типичные причины

На практике причина почти всегда одна из этих:

  • Нет протоколаexample.com вместо https://example.com.
  • Домен указан неточноwww.example.com вместо example.com (или наоборот).
  • Редирект изменил адрес — форма на /login, а сервер фактически отдаёт /auth/login.
  • Маршрут SPA не совпадает с адресом на сервере — JS-роут /app/login ≠ URL, который видит бэкенд.
  • Спецсимволы или пробелы не закодированы — незакодированные пробелы, кириллица в пути и т.п.
  • CAPTCHA рендерится в iframe с другого (под)домена — виджет реально грузится не с того домена, что указан в pageurl.
  • Лишний слеш или query-параметр трекинга/login/ вместо /login, ?utm_source=... в адресе.

Прежде чем разбирать код ниже, быстро сверьтесь со списком — в девяти случаях из десяти проблема находится там за минуту.

Таблица диагностики: симптом → причина → решение

Если беглый просмотр причин выше не помог, сверьтесь с симптомами, которые видит разработчик:

Симптом Причина Решение
Ошибка, хотя URL выглядит верным Расхождение www / без www Сверьте адресную строку посимвольно
URL то работает, то нет На странице несколько вариантов адреса (A/B-тест) Захватывайте адрес именно в момент решения CAPTCHA
Токен решён, но сайт его отклоняет Домен в pageurl не совпадает с доменом сайта Домен токена должен совпадать с доменом сайта
В браузере всё работает, в коде — ошибка Скрипт не отследил редирект Используйте get_final_url()
В адресе есть query-параметры Часть параметров может быть обязательной Сохраняйте параметры, кроме трекинговых

Как определить и провалидировать pageurl

Правило: адрес из адресной строки, не из конфига

pageurl — это в точности то, что показывает адресная строка браузера в момент, когда CAPTCHA видна пользователю.

# WRONG — incomplete URL
pageurl = "https://staging.example.com/qa-login"

# WRONG — wrong protocol
pageurl = "http://https://staging.example.com/qa-login"

# CORRECT — full URL with protocol
pageurl = "https://staging.example.com/qa-login"

# CORRECT — with www if that's what the page uses
pageurl = "https://www.https://staging.example.com/qa-login"

Проверяйте это вручную при первой интеграции — копируйте адрес из адресной строки, а не из конфига фронтенда: там нередко остаётся старый или тестовый URL.

Валидация pageurl перед отправкой запроса

Чтобы не гонять заведомо некорректный pageurl через API и не тратить поток впустую, проверяйте его локально до вызова in.php:

from urllib.parse import urlparse


def validate_pageurl(url):
    """Validate pageurl before API submission."""
    parsed = urlparse(url)

    if not parsed.scheme:
        raise ValueError(f"Missing protocol: {url}. Use https://")

    if parsed.scheme not in ("http", "https"):
        raise ValueError(f"Invalid protocol: {parsed.scheme}")

    if not parsed.netloc:
        raise ValueError(f"Missing domain: {url}")

    # Remove fragment (hash) — not sent to server
    clean = f"{parsed.scheme}://{parsed.netloc}{parsed.path}"
    if parsed.query:
        clean += f"?{parsed.query}"

    return clean


# Usage
url = validate_pageurl("https://staging.example.com/qa-login#section")
# Returns: "https://staging.example.com/qa-login"

Функция отсекает отсутствующий протокол, посторонние схемы вроде ftp:// и URL без домена, а заодно убирает хэш-фрагмент — он не долетает до сервера и не должен попадать в pageurl.


Редиректы и SPA: какой URL передавать

Что делать с редиректами

Если форма логина сама уводит пользователя на другой адрес — например, /login перенаправляет на /auth/login — в pageurl идёт тот адрес, где CAPTCHA реально отрисовалась, а не тот, с которого начался переход:

import requests


def get_final_url(url):
    """Follow redirects to get the actual page URL."""
    resp = requests.get(url, allow_redirects=True, timeout=15)
    return resp.url


# If the login page redirects
original = "https://staging.example.com/qa-login"
final = get_final_url(original)
print(f"Final URL: {final}")
# Use final URL as pageurl

Особенности SPA

В одностраничных приложениях адрес в браузере меняется через JavaScript без перезагрузки страницы, а бэкенд об этом может ничего не знать. Решателю нужен именно домен, на котором показана CAPTCHA:

# For SPAs, use the domain root + the route shown in the address bar
# NOT the API endpoint that the form submits to

# WRONG — API endpoint
pageurl = "https://api.example.com/v1/auth/login"

# CORRECT — the page URL shown in browser
pageurl = "https://staging.example.com/qa-login"

iframe и финальный запрос к API

CAPTCHA внутри iframe: чей URL использовать

Если виджет рендерится внутри iframe с другого домена — частый случай для встроенных форм логина или чекаута, — в pageurl всё равно идёт адрес родительской страницы, а не src самого iframe:

# If the CAPTCHA is on the MAIN page
pageurl = "https://example.com/register"  # Main page URL

# If the CAPTCHA is in an IFRAME with a different domain
# Still use the main page URL, not the iframe src
pageurl = "https://example.com/register"
# NOT: "https://captcha-frame.example.com/challenge"

Итоговый запрос к in.php

Собираем всё вместе: сначала валидируем URL, и только потом отправляем задачу решателю reCAPTCHA/Turnstile:

import requests

# Validate URL first
pageurl = validate_pageurl("https://staging.example.com/qa-login")

resp = requests.post("https://ocr.captchaai.com/in.php", data={
    "key": "YOUR_API_KEY",
    "method": "userrecaptcha",
    "googlekey": "SITE_KEY",
    "pageurl": pageurl,
    "json": 1,
})
result = resp.json()

if result.get("status") == 1:
    print(f"Task ID: {result['request']}")
else:
    print(f"Error: {result.get('request')}")

Тестовые стенды за VPN и туннелями

Отдельный случай — QA-стенды, закрытые логином и доступные только через VPN или туннель вроде ngrok или Cloudflare Tunnel.

  • Адрес в браузере тестировщика (https://random-id.ngrok-free.app/qa-login) может отличаться от внутреннего адреса, который знает бэкенд.
  • Правило остаётся тем же: в pageurl идёт адрес из адресной строки в момент решения CAPTCHA, даже если для внешнего мира это временный туннельный домен.
  • Если такой адрес меняется при каждом перезапуске стенда, получайте его динамически в начале прогона автотестов, а не храните захардкоженным в конфиге.

Если это упустить, задачи начнут падать с ERROR_PAGEURL уже на второй день — как только туннель перезапустится с новым поддоменом.


Частые вопросы

Почему ERROR_PAGEURL появляется, даже если адрес в браузере выглядит правильно?

Чаще всего дело не в самом URL, а в моменте, когда он был захвачен: скрипт мог взять адрес до редиректа или до того, как SPA дорисовала маршрут. Сверяйте pageurl строго с тем, что показано на экране в момент решения CAPTCHA, включая протокол и www/не-www.

Как передавать pageurl для стенда за VPN или туннелем (ngrok, Cloudflare Tunnel)?

Так же, как для обычного адреса — берите то, что показывает адресная строка в момент решения CAPTCHA. Если туннельный домен генерируется заново при каждом запуске, получайте его динамически в начале прогона, а не храните в конфиге.

Нужно ли передавать в pageurl параметры запроса (query string)?

Только если они входят в видимый адрес страницы. Служебные параметры вроде utm_source, которые не влияют на контент страницы, можно и нужно убирать.

Как быть с pageurl, если CAPTCHA встроена в iframe с чужого домена?

Указывайте адрес родительской страницы, а не src iframe — решатель проверяет домен, на котором находится сама форма, а не домен виджета внутри неё.


Что читать дальше


Проверьте pageurl перед отправкой — решите CAPTCHA через CaptchaAI.

Комментарии для этой статьи отключены.