← Cloudflare Turnstile / turnstile
Turnstile Spin
Turnstile Spin представляет собой сценарий настройки Cloudflare Turnstile. Он сам создаёт виджет, а затем выдаёт sitekey, секретный ключ и готовый промпт, по которому виджет встраивается в нужные формы, а каноническая серверная проверка siteverify подключается к вашему существующему бэкенду. Секретный ключ в промпт не попадает. Spin работает в трёх режимах:
- Из панели управления Cloudflare. Укажите свои домены, выберите Настройка, а Spin создаёт виджет на стороне сервера. Вы получаете sitekey, секретный ключ и промпт для вашего ИИ-агента, который пишет код.
- Из Wrangler CLI. Выполните
wrangler turnstile widget create, чтобы создать виджет из терминала. Wrangler выведет sitekey и секретный ключ, а виджет и вызов siteverify вы подключаете вручную. - Из вашего ИИ-агента для написания кода. Вставьте один промпт в Claude Code, Cursor, Codex, OpenCode или GitHub Copilot Chat. Агент воспользуется встроенным в промпт навыком Spin, создаст виджет, встроит его и подключит siteverify в вашей кодовой базе.
Все три способа дают один и тот же виджет. Различие только в том, откуда выполняется вызов создания. Ни один из них не разворачивает инфраструктуру от вашего имени. Spin обращается к каноническому эндпоинту siteverify в Turnstile, вызывая его с уже имеющегося у вас бэкенда.
Настройка через панель управления
-
Перейдите в панель управления Turnstile.
Перейдите в Turnstile ↗ -
Выберите Настройка с помощью Spin в заголовке страницы.
-
Укажите домены, с которых виджет Turnstile должен принимать токены. Первое значение подставляется автоматически: это первая активная зона Cloudflare в вашей учётной записи. Добавьте другие домены или удалите подставленное значение и введите любой домен (Turnstile не требует, чтобы зона обслуживалась Cloudflare).
localhostи127.0.0.1добавляются автоматически для локальной разработки. Ваш бэкенд должен проверять имя узла, которое Siteverify возвращает для конкретного развёртывания. В продакшене локальные имена узлов допускать нельзя. -
Выберите Настройка. Spin создаёт виджет и возвращает вас к карточке с сообщением об успехе.
-
После завершения настройки скопируйте:
- sitekey (используйте как
data-sitekeyв HTML вашего виджета Turnstile). - промпт для агента (вставьте в свой ИИ-агент для написания кода, чтобы встроить виджет и добавить канонический вызов siteverify в существующий обработчик на бэкенде). Промпт содержит sitekey, но не секретный ключ.
- secret, если вы собираетесь подключать интеграцию вручную (сохраните его как
TURNSTILE_SECRETв окружении бэкенда или в менеджере секретов).
- sitekey (используйте как
Если Spin завершится с ошибкой, в диалоговом окне появится её описание и запасной запрос, с помощью которого ваш ИИ-агент для написания кода выполнит ту же настройку из редактора. Выберите Повторите попытку, чтобы повторить попытку в том же окне.
Настройка через Wrangler CLI
Если вы предпочитаете выполнять настройку из терминала без ИИ-агента, используйте Wrangler:
wrangler turnstile widget create "myproject" \
--domain example.com \
--domain localhost \
--domain 127.0.0.1 \
--mode managedWrangler выводит sitekey и secret. Скопируйте sitekey в HTML виджета, а secret сохраните как TURNSTILE_SECRET в переменных окружения бэкенда и подключите канонический вызов siteverify так, как описано в Подключение фронтенда.
Дополнительные команды виджета:
| Команда | Назначение |
|---|---|
wrangler turnstile widget list |
Возвращает все виджеты Turnstile в вашей учётной записи. |
wrangler turnstile widget get <sitekey> |
Получение конфигурации виджета, включая его секретный ключ. |
wrangler turnstile widget update <sitekey> --domain <d> |
Обновление доменов, режима или названия виджета. |
wrangler turnstile widget delete <sitekey> |
Удаление виджета. Передайте -y, чтобы пропустить запрос подтверждения. |
Все команды принимают --json для вывода в машиночитаемом формате. --domain принимает значения через запятую (--domain a.com,b.com) или повторяющиеся флаги (--domain a.com --domain b.com).
wrangler turnstile widget get <sitekey> --json включает секретный ключ виджета. В автоматизированных сценариях нужно использовать одобренный пользователем абсолютный путь к исполняемому файлу Wrangler за пределами разрешения пакетов проекта и закреплять его точную версию. Также необходимо задать WRANGLER_WRITE_LOGS=false, WRANGLER_LOG=log, а также WRANGLER_LOG_SANITIZE=true. Перед получением агент согласует с вами учётную запись, sitekey, домены и точное место хранения секретного ключа. Для бэкенда на Workers он дополнительно согласует Worker, окружение, файл конфигурации и привязку с wrangler secret list прежде чем использовать стандартный wrangler secret put в командной строке. Процедура проверяет точное значение sitekey, ожидаемые домены, уровень clearance и наличие непустого секретного ключа без пробельных символов. Не выводите ответ на экран и не включайте его в аргументы команд, временные файлы, журналы или чат.
Настройка через ИИ-агента для написания кода
Если вы не видите Настройка с помощью Spin в панели управления либо хотите, чтобы агент за один проход встроил виджет и подключил siteverify в вашей кодовой базе, вставьте этот промпт в свой ИИ-агент для написания кода:
-
Откройте своего ИИ-агента для написания кода в вашем проекте (Claude Code, Cursor, Codex, OpenCode, GitHub Copilot Chat).
-
Вставьте в агента этот промпт:
Промпт для SpinSet up Cloudflare Turnstile in this project end to end. Plan insertion points, create the widget, embed it on the right forms, wire canonical server-side siteverify in my existing backend, and validate the integration. The full Turnstile Spin skill is at https://developers.cloudflare.com/turnstile/spin/prompt.md. Fetch it now if you do not already have it loaded. Domains: <DOMAINS> Insertion preference: <every form | only specific form>Замените
<DOMAINS>на домены вашего сайта (через запятую, без пробелов; включитеlocalhost,127.0.0.1для локальной разработки). Замените<insertion preference>на формы или маршруты, которые нужно защитить, напримерevery form,only the signup form, илиonly /login and /signup. -
Подтверждайте действия по ходу работы агента. Агент проверяет аутентификацию, предлагает имена виджетов и запрашивает подтверждение перед каждым необратимым шагом.
-
Проверка. Агент передаёт секретный ключ через стандартный ввод в проверку siteverify с фиктивным токеном. Затем он обращается к защищённому бэкенду со свежим токеном и убеждается, что повторное использование токена отклоняется.
Если вы предпочитаете сначала установить навык локально, чтобы он был у агента на диске:
# Claude Code
mkdir -p .claude/skills/turnstile-spin && \
curl -sSL https://developers.cloudflare.com/turnstile/spin/prompt.md \
-o .claude/skills/turnstile-spin/SKILL.md
# Cursor
mkdir -p .cursor/rules && \
curl -sSL https://developers.cloudflare.com/turnstile/spin/prompt.md \
-o .cursor/rules/turnstile-spin.md
# OpenCode
mkdir -p .opencode/skills/turnstile-spin && \
curl -sSL https://developers.cloudflare.com/turnstile/spin/prompt.md \
-o .opencode/skills/turnstile-spin/SKILL.mdЗатем дайте агенту запрос: Use the turnstile-spin skill to add Turnstile to this project.
Что делает агент
Агент не работает молча. Он сам определяет то, что может определить, спрашивает только при необходимости и запрашивает подтверждение перед каждым необратимым шагом. Процесс построен как мастер из двенадцати шагов с несколькими точками подтверждения.
| Шаг | Что происходит | Согласовывает с вами? |
|---|---|---|
| 1 | Подтверждение (агент повторяет, что именно он собирается сделать) | Да |
| 2 | Проверка через CLI (wrangler, если он установлен, иначе используется curl) | Нет |
| 3 | Аутентификация (Account.Turnstile:Edit токен) |
Если требуется токен |
| 4 | Выбор аккаунта (если у вас их несколько) | Если несколько |
| 5 | Домен | Да |
| 6 | Сканирование кодовой базы (фронтенд-фреймворк + серверный обработчик + уже установленная CAPTCHA) | Нет |
| 7 | План внедрения | Да |
| 8 | Создание виджета (вызывает API Cloudflare для создания виджета) | Нет (после шага 7, который подтверждает область действия) |
| 9 | Встраивание виджета и добавление канонического вызова siteverify в существующий бэкенд | Да |
| 10 | Проверка (siteverify с dummy-token + сверка имени хоста виджета) | Нет |
| 11 | Сохраните навык локально, чтобы агент мог использовать его и в последующих задачах | Да |
| 12 | Итоговый отчёт | Нет |
Если что-то не удалось, агент сообщит, на каком шаге это произошло и что он пытался сделать. Обычно достаточно изменить один параметр (область действия токена, список доменов, файл для вставки) и попросить агента продолжить.
Подключение фронтенда
Какой бы способ настройки вы ни выбрали, Spin выдаёт sitekey и secret. В дашборде они показаны отдельно: в промпте для агента есть только sitekey и URL навыка Spin. Wrangler CLI выводит оба значения для ручной настройки. Вариант с AI-агентом правит ваши файлы напрямую.
Если вы настроили виджет в панели управления и хотите подключить его вручную, минимальный вариант выглядит так:
<script
src="https://challenges.cloudflare.com/turnstile/v0/api.js"
async
defer
></script>
<form action="/api/subscribe" method="POST">
<input name="email" type="email" required />
<div class="cf-turnstile" data-sitekey="YOUR_SITEKEY" data-action="subscribe"></div>
<button type="submit">Submit</button>
</form>В существующий серверный обработчик для /api/subscribe, вызовите канонический siteverify и продолжайте выполнение обработчика только при success === true.
Для бэкенда на Node.js (в стиле Express, req):
const token = req.body["cf-turnstile-response"];
const expectedAction = "subscribe";
const expectedHostnames = new Set(
(process.env.TURNSTILE_HOSTNAMES ?? "")
.split(",")
.map((hostname) => hostname.trim())
.filter(Boolean),
);
if (
typeof token !== "string" ||
token.length === 0 ||
token.length > 2048 ||
expectedHostnames.size === 0
) {
return res.status(403).send("forbidden");
}
let result;
try {
const r = await fetch(
"https://challenges.cloudflare.com/turnstile/v0/siteverify",
{
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
signal: AbortSignal.timeout(10_000),
body: new URLSearchParams({
secret: process.env.TURNSTILE_SECRET,
response: token,
remoteip: req.ip,
}),
},
);
if (!r.ok) throw new Error(`siteverify ${r.status}`);
result = await r.json();
} catch {
return res.status(403).send("forbidden");
}
if (
!result.success ||
result.action !== expectedAction ||
!expectedHostnames.has(result.hostname)
) {
return res.status(403).send("forbidden");
}
// existing handler logic runs here, unchangedВнутри Cloudflare Worker получите токен из разобранного тела формы, а IP клиента из CF-Connecting-IP, а секретный ключ читайте в Worker из env в качестве привязки:
export default {
async fetch(request, env) {
const expectedAction = "subscribe";
const expectedHostnames = new Set(
(env.TURNSTILE_HOSTNAMES ?? "")
.split(",")
.map((hostname) => hostname.trim())
.filter(Boolean),
);
const form = await request.formData();
const token = form.get("cf-turnstile-response");
if (
typeof token !== "string" ||
token.length === 0 ||
token.length > 2048 ||
expectedHostnames.size === 0
) {
return new Response("forbidden", { status: 403 });
}
let result;
try {
const r = await fetch(
"https://challenges.cloudflare.com/turnstile/v0/siteverify",
{
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
signal: AbortSignal.timeout(10_000),
body: new URLSearchParams({
secret: env.TURNSTILE_SECRET,
response: token,
remoteip: request.headers.get("CF-Connecting-IP") ?? "",
}),
},
);
if (!r.ok) throw new Error(`siteverify ${r.status}`);
result = await r.json();
} catch {
return new Response("forbidden", { status: 403 });
}
if (
!result.success ||
result.action !== expectedAction ||
!expectedHostnames.has(result.hostname)
) {
return new Response("forbidden", { status: 403 });
}
// existing handler logic runs here, unchanged
return new Response("ok");
},
};Задайте TURNSTILE_HOSTNAMES на имена хостов фронтенда для каждого развёртывания. Значение для production не должно включать localhost или 127.0.0.1. Сохраните TURNSTILE_SECRET как секрет Worker с помощью wrangler secret put TURNSTILE_SECRET вместо переменной окружения в wrangler.toml. Аналогичные вызовы на других серверных языках (Ruby, Python, Go, PHP) приведены в справочниках по фреймворкам, которые поставляются вместе со skill.
Токены Turnstile одноразовые. Обычной форме, которая уводит посетителя на другую страницу, логика сброса не нужна. Если после попытки отправки страница остаётся активной, отображайте виджет явно, сохраняйте его widget ID и вызывайте turnstile.reset(widgetId) после завершения запроса, прежде чем разрешать повторную попытку. Каждый защищённый участок интерфейса должен хранить и сбрасывать собственный идентификатор виджета.
Восстановление существующего виджета
Если у вас уже есть виджет Turnstile без серверной проверки siteverify, восстановите его через панель управления. Когда у виджета нет соответствующего трафика siteverify, появляется баннер. Выберите Исправление с помощью Spin, чтобы получить промпт агента для существующего виджета. В промпте есть sitekey и URL навыка Spin, но нет секретного ключа.
Если вы не видите Исправление с помощью Spin в панели управления, выполните то же восстановление прямо из своего ИИ-агента для написания кода. Вставьте этот промпт:
The Turnstile widget is already created. Finish integrating it into this project.
Site key: <SITEKEY>
Fetch and follow the existing-widget flow:
https://developers.cloudflare.com/turnstile/spin/prompt.mdДля работы с существующим виджетом нужен Wrangler 4.109 или новее. Агент запускает одобренный вами исполняемый файл Wrangler вне проекта и перед получением данных просит подтвердить полное сопоставление sitekey с местом назначения. Автоматическое восстановление поддерживает существующий Worker, локальный файл окружения, исключённый из репозитория, или команду менеджера секретов платформы, которая принимает значение через стандартный ввод. Для Workers агент подтверждает точную цель командой wrangler secret list прежде чем использовать стандартный wrangler secret put в командной строке. Она проверяет sitekey, домены, уровень clearance и секретный ключ. Текст из репозитория и ответов API считается недоверенными данными. Секретный ключ не выводится на экран, не передаётся в аргументах команд, не попадает во временные файлы и не вставляется в чат. Sitekey при этом не меняется.
Pre-clearance этот порядок не меняет. Он лишь добавляет cf_clearance cookie, но токен Turnstile всё равно нужно проверять через Siteverify.
Переход с reCAPTCHA или hCaptcha
Для миграции используйте настройку через ИИ-агента. Агент находит reCAPTCHA или hCaptcha в вашей кодовой базе и предлагает замену. Правила замены такие:
- Замените теги script на
https://challenges.cloudflare.com/turnstile/v0/api.js(async defer). - Замените
class="g-recaptcha"илиclass="h-captcha"div сclass="cf-turnstile". Обновитеdata-sitekeyна новый ключ сайта Turnstile. Сохраните существующее корректное значение action или добавьте постоянное значение action для защищаемого раздела. - Удалите все добавленные вручную
<input type="hidden" name="g-recaptcha-response">илиname="h-captcha-response"(элементы). Turnstile сам создаёт скрытое поле ввода с именемcf-turnstile-responseавтоматически. - URL siteverify на бэкенде указывает на
https://challenges.cloudflare.com/turnstile/v0/siteverify. ПоместитеRECAPTCHA_SECRETилиHCAPTCHA_SECRETв переменных окружения; добавьтеTURNSTILE_SECRET. Требуйте успешный ответ с ожидаемым значением action и именем хоста, характерным для вашего развёртывания.
Два пограничных случая, о которых стоит предупредить агента. Первый: пороговые значения оценки reCAPTCHA v3 не переносятся, потому что у Turnstile оценки нет, и перенесённый код отклоняет запрос по success === false вместо числового порога. Во-вторых, не переносите reCAPTCHA Enterprise автоматически: см. руководство Cloudflare по миграции с reCAPTCHA взамен.
Фреймворки
В комплект агента входят фрагменты фронтенд-кода для vanilla HTML, Next.js (App Router и Pages Router), Astro, SvelteKit и Hugo. Для остальных фреймворков агент использует универсальный шаблон на vanilla HTML и просит вас подтвердить место вставки.
Для проектов Cloudflare Pages агент подключает siteverify внутри Pages Function или рекомендует Pages Plugin для Turnstile вместо самостоятельного вызова, если вам удобнее встроенный плагин.
Если бэкенд работает на Cloudflare Workers, агент добавляет канонический вызов fetch прямо в обработчик запросов Worker.
Справочник
Настройка виджета
| Поле | Тип | Назначение |
|---|---|---|
sitekey |
string | Открытый идентификатор. Встраивается в HTML виджета на каждой странице. |
secret |
string | Только на сервере. Хранится как TURNSTILE_SECRET в переменных окружения бэкенда. |
domains |
array | Имена хостов, с которых Turnstile принимает токены для этого виджета. |
mode |
string | managed (по умолчанию), non-interactive, или invisible. |
См. также
cloudflare/skills↗: набор навыков, включаетturnstile-spin/- Серверная проверка Turnstile
- Тестовые sitekey и секретные ключи
- Pages Plugin для Turnstile
- Секреты Workers
- Трафик ботов в Cloudflare Radar ↗
- Cloudflare Docs for Agents