Explainers

Режимы виджета Cloudflare Turnstile: управляемый, неинтерактивный, невидимый

Пользователь на одном сайте с Cloudflare Turnstile видит чекбокс «Verify you are human», на другом — вообще ничего. Разница в режиме виджета: управляемом (Cloudflare сама решает, что показать), неинтерактивном (только фоновая проверка, интерфейс не появляется никогда) и невидимом (контейнер вообще не рендерится). Для автоматизации это вопрос детектирования: сначала нужно понять режим страницы, и только потом извлекать sitekey.

Все три режима отдают один и тот же токен cf-turnstile-response и решаются одним методом CaptchaAI — turnstile. Разница только в том, что видно пользователю и как найти sitekey в разметке.


Быстрый ориентир: как понять, какой режим перед вами

Не открывая документацию, режим можно определить за три шага:

  1. Есть видимый чекбокс, который иногда пропадает без клика — это управляемый режим.
  2. Виден только спиннер, а чекбокса не появляется никогда — это неинтерактивный режим.
  3. На странице нет вообще ни одного элемента, привязанного к виджету — это невидимый режим.

Управляемый режим: Cloudflare решает за пользователя

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

Стандартная разметка выглядит так:

<!-- Managed mode (default) -->
<div class="cf-turnstile"
     data-sitekey="0x4AAAAAAAC3DHQhMMQ_Rxrg"
     data-theme="light">
</div>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>

Что видит автоматизация в зависимости от репутации источника:

  • Высокое доверие — проходит невидимо, интерфейса нет.
  • Среднее доверие — чекбокс: «нажмите для проверки».
  • Низкое доверие — интерактивная проверка или блок.

Для скриптов управляемый режим самый непредсказуемый: один sitekey может то показывать чекбокс, то проходить незаметно — в зависимости от сигналов браузера. Отличить его в HTML можно так:

def is_managed_mode(html):
    """Check if Turnstile is using managed mode (default)."""
    # Managed mode is the default — no explicit mode attribute
    has_turnstile = "cf-turnstile" in html
    has_explicit_mode = 'data-appearance="interaction-only"' in html or \
                        'data-appearance="always"' in html or \
                        'appearance: "interaction-only"' in html
    return has_turnstile and not has_explicit_mode

Неинтерактивный режим: проверка без интерфейса

Неинтерактивный режим никогда не показывает чекбокс или другой кликабельный элемент. Виден только спиннер, пока в фоне идёт proof-of-work. Если проверка не проходит, виджет сразу сообщает об ошибке — эскалации до чекбокса не будет.

<!-- Non-interactive mode -->
<div class="cf-turnstile"
     data-sitekey="0x4AAAAAAAC3DHQhMMQ_Rxrg"
     data-appearance="interaction-only">
</div>

Тот же режим можно включить через JavaScript API:

turnstile.render('#turnstile-container', {
    sitekey: '0x4AAAAAAAC3DHQhMMQ_Rxrg',
    appearance: 'interaction-only',
    callback: function(token) {
        document.getElementById('cf-turnstile-response').value = token;
    },
});

По шагам это выглядит так:

Page loads → Widget initializes
    ↓
Background proof-of-work runs
    ↓
Success → Token generated (no visible UI)
    OR
Failure → Widget reports error (no fallback to checkbox)

Где чаще всего встречается неинтерактивный режим:

  1. Формы комментариев и виджеты обратной связи.
  2. Подписка на рассылку.
  3. Действия с низкой ценой ошибки, где трение нужно свести к минимуму.
  4. API-эндпоинты с защитой на стороне браузера.

Невидимый режим: без единого визуального признака

Невидимый режим — буквально невидим: в вьюпорте не появляется ни один элемент, привязанный к виджету. Проверка запускается при загрузке страницы (или по программному триггеру) и отдаёт токен без какой-либо визуальной подсказки.

<!-- Invisible mode — container is hidden -->
<div id="turnstile-invisible"
     class="cf-turnstile"
     data-sitekey="0x4AAAAAAAC3DHQhMMQ_Rxrg"
     data-size="invisible">
</div>

Или полностью программно:

// Programmatic invisible Turnstile
turnstile.render('#hidden-container', {
    sitekey: '0x4AAAAAAAC3DHQhMMQ_Rxrg',
    size: 'invisible',
    callback: function(token) {
        // Token ready — submit form automatically
        submitForm(token);
    },
    'error-callback': function() {
        // Challenge failed
        console.error('Invisible Turnstile failed');
    },
});

У невидимого Cloudflare Turnstile нет видимых размеров контейнера, поэтому детектор должен опираться на признаки в разметке и сетевых запросах, а не на визуальный поиск элемента:

import re

def detect_invisible_turnstile(html):
    """Detect invisible Turnstile on a page."""
    indicators = {
        "script_loaded": "challenges.cloudflare.com/turnstile" in html,
        "size_invisible": 'data-size="invisible"' in html or
                          "size: 'invisible'" in html or
                          'size: "invisible"' in html,
        "api_render_call": "turnstile.render" in html,
        "response_field": "cf-turnstile-response" in html,
    }

    if indicators["script_loaded"] and indicators["size_invisible"]:
        return {"mode": "invisible", "confidence": "high"}
    elif indicators["script_loaded"] and indicators["api_render_call"]:
        return {"mode": "invisible_or_programmatic", "confidence": "medium"}
    elif indicators["response_field"]:
        return {"mode": "turnstile_present", "confidence": "low"}

    return {"mode": "none", "confidence": "high"}

Sitekey вне зависимости от режима

Какой бы режим ни стоял на странице, для решения нужен один и тот же параметр — sitekey. Достать его можно тремя способами: из атрибута data-sitekey, из вызова turnstile.render() в JS или из конфигурационного объекта:

import re

def extract_turnstile_sitekey(html):
    """Extract Turnstile sitekey from page HTML (works for all modes)."""

    # Pattern 1: data-sitekey attribute in HTML
    match = re.search(r'data-sitekey=["\']([0-9x][A-Za-z0-9_-]+)["\']', html)
    if match:
        return match.group(1)

    # Pattern 2: JavaScript render call
    match = re.search(r"sitekey:\s*['\"]([0-9x][A-Za-z0-9_-]+)['\"]", html)
    if match:
        return match.group(1)

    # Pattern 3: Turnstile config object
    match = re.search(r"siteKey['\"]?\s*[:=]\s*['\"]([0-9x][A-Za-z0-9_-]+)['\"]", html)
    if match:
        return match.group(1)

    return None

Как решить любой режим через API CaptchaAI

Режим виджета не влияет на вызов API — все три решаются одной функцией с методом turnstile. Отправьте sitekey и pageurl, получите ID задачи и опросите res.php:

Python

import requests
import time

API_KEY = "YOUR_API_KEY"

def solve_turnstile(sitekey, page_url):
    """Solve any Turnstile mode — managed, non-interactive, or invisible."""
    submit = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "turnstile",
        "sitekey": sitekey,
        "pageurl": page_url,
        "json": 1,
    })

    task_id = submit.json()["request"]

    for _ in range(60):
        time.sleep(5)
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY,
            "action": "get",
            "id": task_id,
            "json": 1,
        }).json()

        if result.get("status") == 1:
            return result["request"]

    raise TimeoutError("Turnstile solve timed out")


# Use with any mode
token = solve_turnstile("0x4AAAAAAAC3DHQhMMQ_Rxrg", "https://staging.example.com/qa-login")
print(f"Token: {token[:50]}...")

Node.js

const axios = require("axios");

const API_KEY = "YOUR_API_KEY";

async function solveTurnstile(sitekey, pageUrl) {
  const submit = await axios.post("https://ocr.captchaai.com/in.php", null, {
    params: {
      key: API_KEY,
      method: "turnstile",
      sitekey,
      pageurl: pageUrl,
      json: 1,
    },
  });

  const taskId = submit.data.request;

  for (let i = 0; i < 60; i++) {
    await new Promise((r) => setTimeout(r, 5000));

    const result = await axios.get("https://ocr.captchaai.com/res.php", {
      params: { key: API_KEY, action: "get", id: taskId, json: 1 },
    });

    if (result.data.status === 1) {
      return result.data.request;
    }
  }

  throw new Error("Turnstile solve timed out");
}

// Same function works for all Turnstile modes
solveTurnstile("0x4AAAAAAAC3DHQhMMQ_Rxrg", "https://staging.example.com/qa-login")
  .then((token) => console.log("Token:", token.substring(0, 50)));

Сравнение режимов в одной таблице

Сводка для быстрой сверки, когда все три режима уже разобраны по отдельности:

Особенность Управляемый Неинтерактивный Невидимый
Виджет виден? Иногда Никогда (только спиннер) Никогда
Нужен контейнер в DOM? Да Да Да (скрытый)
Требуется клик пользователя? Иногда (чекбокс) Нет Нет
Проверка proof-of-work? Да (может усложниться) Да (всегда) Да (всегда)
Резервный чекбокс при сбое? Да Нет — сразу ошибка Нет — сразу ошибка
Токен cf-turnstile-response cf-turnstile-response cf-turnstile-response
Метод CaptchaAI turnstile turnstile turnstile
Где чаще встречается Вход, регистрация Формы с низким трением Фоновая проверка

Типичные проблемы при работе с Cloudflare Turnstile

  • Токен верный, но форма его не принимаетsitekey не совпадает с тем, что реально отрендерился (на странице другой виджет). Проверьте sitekey из JS-рендера, а не только из статического HTML.
  • Виджет не находится в HTML — невидимый режим монтируется уже после первого рендера страницы. Дождитесь полной загрузки и проверьте XHR-ответы.
  • На странице несколько виджетов Turnstile — у разных форм разные sitekey. Сопоставляйте sitekey с конкретной формой, а не берите первый попавшийся.
  • data-size="compact" путает детектор — compact это размер, а не режим; по умолчанию используется управляемый режим.
  • Присутствует data-action — это тег для аналитики, а не признак режима; передавайте action в решение только если он нужен для валидации.
  • Токен просрочен к моменту отправки — токены Cloudflare Turnstile действуют 300 секунд. Решайте задачу непосредственно перед отправкой формы.

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

Меняется ли вызов API CaptchaAI в зависимости от режима Turnstile?

Нет. Управляемый, неинтерактивный и невидимый режимы решаются одним и тем же методом turnstile. Единственные обязательные параметры — sitekey и pageurl, формат токена и порядок опроса res.php от режима не зависят.

Сколько потоков тарифа нужно, чтобы решать managed-режим при пиковой нагрузке?

Отталкивайтесь от пика одновременных задач, а не от среднего трафика: managed-режим резко наращивает долю чекбоксов при всплеске подозрительных сигналов. Для staging с пиком в 30–40 параллельных решений подходит ADVANCE ($90/мес, 50 потоков); для небольших QA-прогонов хватит STANDARD ($30/мес, 15 потоков).

Почему невидимый виджет иногда не находится в HTML?

Чаще всего invisible-контейнер добавляется в DOM программно, уже после первичного рендера, или скрыт стилями без явного data-size="invisible" в разметке. Проверяйте не только HTML, но и вызовы turnstile.render() и запросы к challenges.cloudflare.com.

Может ли один и тот же sitekey переключаться между режимами?

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

Что делать, если Turnstile решён, а форма всё равно отклоняет токен?

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


Коротко о главном

Три режима виджета Cloudflare Turnstile — управляемый, неинтерактивный и невидимый — по-разному ведут себя в интерфейсе, но всегда отдают один и тот же токен cf-turnstile-response. Для автоматизации все три решаются одинаково через метод turnstile в решателе Cloudflare Turnstile от CaptchaAI, с высокой долей успешных решений на поддерживаемых типах. Главное отличие для разработчика — не решение, а детектирование: managed-режим обычно виден в статическом HTML, а invisible требует более глубокого анализа страницы, чтобы найти sitekey.

Похожие статьи

Подробнее по смежным темам: как отличить страницу с Cloudflare Challenge от Cloudflare Turnstile, GeeTest против Cloudflare Turnstile и что делать с ошибкой 403 в Cloudflare Turnstile после починки токена.

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