Troubleshooting

Срок действия токена Cloudflare Turnstile и условия гонки

Токен Cloudflare Turnstile живёт около 300 секунд — и именно в это окно чаще всего укладывается разница между «работает на тестовом стенде» и «падает в production». Форма из пяти шагов, нестабильное мобильное соединение или токены, решённые заранее «про запас», — и к моменту отправки токен, который вы честно получили вовремя, уже недействителен. Разбираемся, откуда берётся этот тайминг и как выстроить рабочий процесс, чтобы условие гонки не всплывало на проде.

Сколько живёт токен

Токен Cloudflare Turnstile действителен примерно 300 секунд (5 минут) с момента создания. Это заметно больше, чем ~120 секунд у reCAPTCHA, но в многошаговых сценариях даже такого запаса не всегда хватает.

Тип CAPTCHA Срок действия токена
reCAPTCHA v2/v3 ~120 секунд
Cloudflare Turnstile ~300 секунд
hCaptcha ~120 секунд

Важная деталь. Таймер запускается в момент, когда Cloudflare сгенерировал токен, — не когда CaptchaAI вернул его вам, и не когда ваш код его фактически получил. Эти несколько секунд между генерацией и получением тоже списываются со счётчика.

Условие гонки по секундам

Time 0:00  — You submit a Turnstile task to CaptchaAI
Time 0:15  — CaptchaAI begins solving
Time 0:20  — Token is generated (timer starts here)
Time 0:25  — CaptchaAI returns token to you
Time 0:25+ — Your code processes the token
Time ???   — Your code submits the token to the site

Что это значит на практике:

  • отсчёт пошёл с отметки 0:20;
  • формально дедлайн — примерно 5:20, то есть почти пять минут в запасе;
  • звучит как большой запас — но посмотрите, что происходит в реальном рабочем процессе, а не в синтетическом тесте:
Time 0:20  — Token generated
Time 0:25  — Received by your code
Time 0:30  — Fill form fields
Time 0:35  — Navigate to next page
Time 1:00  — Handle additional dialogs
Time 2:00  — Wait for page load
Time 4:00  — Network latency spike
Time 5:30  — Submit token → EXPIRED

Ни один из шагов сам по себе не выглядит медленным. Но они складываются, и всплеск задержки сети на пятой минуте — обычное дело, а не аномалия.

Где обычно теряют токен

1. Многошаговые формы

Формы, где решение CAPTCHA стоит не на последнем шаге, а где-то в середине цепочки:

Step 1: Fill personal info → Step 2: Fill address → 
Step 3: Solve CAPTCHA → Step 4: Review → Step 5: Submit

Если CAPTCHA решается на шаге 3, а отправка — только на шаге 5, задержка между решением и submit легко превышает 5-минутный лимит, особенно если пользователь (или ваш скрипт) задерживается на промежуточных экранах.

2. Пакетное решение «про запас»

Частая ошибка при массовой обработке: решить все токены заранее, а использовать их позже.

# DON'T: Solve all tokens first, then use them
tokens = []
for url in urls:
    tokens.append(solve_turnstile(url))  # Tokens age while waiting

for url, token in zip(urls, tokens):
    submit_form(url, token)  # Early tokens may be expired

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

3. Повтор попытки со старым токеном

Ещё один типичный баг — переиспользование одного и того же токена после неудачной отправки:

token = solve_turnstile(site_key, page_url)

for attempt in range(3):
    result = submit_form(page_url, token)
    if result.ok:
        break
    # BUG: Retrying with the same token — it may be expired OR already consumed

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

Пример из практики. Команда, которая тестирует checkout-форму для клиента с пользователями из Казахстана и Беларуси, столкнулась именно со сценарием №1: на мобильных сетях с нестабильным соединением загрузка промежуточных шагов иногда занимала больше минуты, и токен, решённый на третьем экране, «протухал» ещё до подтверждения заказа. Перенос решения CAPTCHA на последний шаг снял проблему полностью — без единой строчки правок в самой форме. Если в рамках такого тестирования вы логируете запросы пользователей, собирайте только те данные, которые вправе обрабатывать по договору с клиентом (для аудитории РФ это прямо требует 152-ФЗ, для трансграничных проектов — эквивалентная дисциплина в духе GDPR).

Как решать токен «точно в срок»

Стратегия 1: решение непосредственно перед отправкой

Запрашивайте токен только тогда, когда форма реально готова к submit — не раньше:

import requests
import time

def solve_turnstile(site_key, page_url):
    resp = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": "YOUR_API_KEY",
        "method": "turnstile",
        "sitekey": site_key,
        "pageurl": page_url,
        "json": 1
    })
    task_id = resp.json()["request"]

    for _ in range(60):
        time.sleep(3)
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": "YOUR_API_KEY",
            "action": "get",
            "id": task_id,
            "json": 1
        })
        data = result.json()
        if data["status"] == 1:
            return data["request"]
    raise TimeoutError("Solve timed out")

# Complete all form steps FIRST
fill_personal_info()
fill_address()
navigate_to_review()

# THEN solve and submit immediately
token = solve_turnstile(site_key, page_url)
submit_form(token)  # Submit within seconds of receiving the token

Стратегия 2: отслеживание возраста токена

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

import time

class TimedToken:
    def __init__(self, token, created_at=None):
        self.token = token
        self.created_at = created_at or time.time()
        self.max_age = 270  # 4.5 min — safety margin from 5 min limit

    @property
    def is_valid(self):
        return (time.time() - self.created_at) < self.max_age

    @property
    def remaining_seconds(self):
        return max(0, self.max_age - (time.time() - self.created_at))

# Usage
timed_token = TimedToken(solve_turnstile(site_key, page_url))

# Check before using
if timed_token.is_valid:
    submit_form(timed_token.token)
else:
    # Solve a fresh token
    timed_token = TimedToken(solve_turnstile(site_key, page_url))
    submit_form(timed_token.token)

Обратите внимание на запас: max_age = 270 секунд, а не полные 300 — это сознательный буфер на сетевые задержки и погрешность самого таймера Cloudflare.

Стратегия 3: свежий токен на каждой попытке (JavaScript)

Для retry-логики правило простое: ни одна повторная попытка не должна отправлять токен, полученный для предыдущей:

async function submitWithFreshToken(siteKey, pageUrl, formData) {
  const maxRetries = 3;

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    // Always solve a fresh token for each attempt
    const token = await solveTurnstile(siteKey, pageUrl);

    const response = await fetch(pageUrl, {
      method: 'POST',
      body: JSON.stringify({ ...formData, 'cf-turnstile-response': token }),
      headers: { 'Content-Type': 'application/json' }
    });

    if (response.ok) return await response.json();

    console.log(`Attempt ${attempt + 1} failed, solving fresh token...`);
  }

  throw new Error('All attempts failed');
}

Как понять, что дело именно в истёкшем токене

Сайт почти никогда прямо не пишет «токен истёк». Об этом обычно сигнализируют косвенные признаки:

Сигнал О чём говорит
HTTP 403 после отправки токена Токен недействителен или его срок истёк
Редирект обратно на страницу формы Проверка токена на сервере не прошла
Сообщение вида «Проверка не удалась» Общая ошибка — истечение срока один из вероятных виновников
Испытание Cloudflare появляется повторно Токен отклонён, Cloudflare переигрывает проверку заново

Логирование для диагностики

Прежде чем гадать, добавьте в код возраст токена на момент отправки — это снимает большинство вопросов за один прогон:

import time
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("turnstile")

token_received_at = time.time()
token = solve_turnstile(site_key, page_url)
logger.info(f"Token received, length: {len(token)}")

# ... workflow steps ...

submit_time = time.time()
age = submit_time - token_received_at
logger.info(f"Submitting token, age: {age:.1f}s")

if age > 270:
    logger.warning(f"Token may be expired (age: {age:.1f}s > 270s safety limit)")

Автообновление в браузерных сценариях

В браузерных потоках виджет Turnstile сам обновляет токен до истечения срока действия. Колбэк data-expired-callback срабатывает именно в момент истечения:

turnstile.render('#captcha', {
  sitekey: '0x4AAAA...',
  callback: (token) => {
    console.log('New token:', token);
  },
  'expired-callback': () => {
    console.log('Token expired — widget will auto-refresh');
  }
});

При автоматизации только через API, без реального браузера, этот механизм не работает — виджета попросту нет. Отслеживать свежесть токена в таком случае приходится вручную, стратегиями выше.

Частые ошибки и быстрые исправления

Проблема Причина Исправление
Токен работает в тестах, но не в production Реальный рабочий процесс медленнее тестового сценария Решайте токен непосредственно перед отправкой, а не заранее
Первая отправка проходит, повторные — нет Повторное использование уже проверенного (consumed) токена Запрашивайте новый токен на каждую попытку
Периодические сбои на длинных формах Токен истекает в процессе многошагового флоу Перенесите решение CAPTCHA на последний шаг перед submit
Высокий процент ошибок в пакетных заданиях Токены решены пачкой заранее и успевают устареть Решайте токены по требованию, а не батчем

FAQ

Как отличить истёкший токен от обычной ошибки проверки?

По прямому признаку это сделать сложно — Cloudflare редко возвращает явный код «expired». Логируйте возраст токена (см. раздел про логирование): если ошибка стабильно случается при возрасте токена ближе к 270–300 секундам, а не сразу после решения, причина почти наверняка в истечении срока.

Можно ли продлить срок действия токена Cloudflare Turnstile?

Нет. Время жизни токена задаёт Cloudflare, и оно не настраивается на стороне клиента или CaptchaAI. Единственный рабочий вариант — решить новый токен ближе к моменту отправки.

Что делать, если CAPTCHA решается на раннем шаге многошаговой формы?

Два рабочих варианта:

  • по возможности перенесите виджет или запрос токена на последний шаг, непосредственно перед submit;
  • если это невозможно из-за архитектуры формы, оборачивайте токен в объект с проверкой возраста (Стратегия 2) и перерешивайте его, если он «просрочен» к моменту отправки.

Почему пакетное решение токенов даёт больше ошибок, чем решение по одному?

Потому что первые токены в пакете ждут своей очереди, пока решаются остальные, и часть из них истекает ещё до использования. Решение «по требованию» — сразу перед конкретной отправкой — убирает эту проблему полностью.

Какой запас времени стоит закладывать на практике?

Используйте не полные ~300 секунд, а safety-лимит около 270 секунд (4,5 минуты) — этот запас закладывают в примерах кода выше. Он покрывает погрешность самого таймера Cloudflare и типичные сетевые задержки.


Следующие шаги

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