Вы гоните платный или партнёрский трафик через редирект и хотите, чтобы каждый клик приходил на целевую сторону со своим идентификатором — тем, по которому потом получится связать его с конверсией. Очевидный инструмент — воркер, и для всего, что должно клик записать, воркер по-прежнему единственный примитив, который на это способен. Но если вам нужно только, чтобы каждый клик нёс уникальный id, есть способ получить его на бесплатном тарифе, внутри обычного Single Redirect, вообще без кода на пути запроса.
Мы проверили это на зоне самого сайта 30 июля 2026 года. Работает, но кусается в трёх местах. Дальше — само правило, замеры и ловушки.
Запрещённая функция и разрешённое поле
В языке выражений Rulesets есть uuidv4(). Здесь её использовать нельзя. Документация
ограничивает её выражениями перезаписи в Transform Rules, и API это проверяет на
валидации. Вот живой ответ на попытку поставить её в цель редиректа:
"'concat(\"https://301.sh/?click_id=\", uuidv4(cf.random_seed))' is not a valid
value for target_url because the use of field cf.random_seed is not allowed,
the use of function uuidv4 is not allowed" (code 20083)
Запрещёнными названы и функция, и её seed-поле. То есть язык выражений не может сгенерировать
случайность внутри редиректа. Зато он может переиспользовать идентификатор, который Cloudflare
уже выдала этому запросу: cf.ray_id, тот самый ray ID, который получает каждый
проходящий через Cloudflare запрос, — то же значение, что показывают заголовок ответа CF-RAY
и логи в дашборде. В документации этого поля никаких ограничений по продуктам нет, и валидатор
принимает его в цели редиректа.
Одно правило, с замерами
Динамический Single Redirect. Условие обычное, а цель — выражение вместо статического адреса:
Expression: (http.host eq "301.sh" and http.request.uri.path eq "/go")
Target URL: concat("https://destination.example/?click_id=", cf.ray_id)
Status: 302, preserve query string off
Пять запросов подряд через это правило, на этой зоне:
Location: https://301.sh/…/?click_id=a23684c8a885b183-BTS CF-RAY: a23684c8a885b183-BTS
Location: https://301.sh/…/?click_id=a23684cbfd5a2915-BTS CF-RAY: a23684cbfd5a2915-BTS
Location: https://301.sh/…/?click_id=a23684cf493d696b-BTS CF-RAY: a23684cf493d696b-BTS
Location: https://301.sh/…/?click_id=a23684d2ddcf1948-BTS CF-RAY: a23684d2ddcf1948-BTS
Location: https://301.sh/…/?click_id=a23684d64c0a46d5-BTS CF-RAY: a23684d64c0a46d5-BTS
Три свойства, и все три видны прямо в выводе. Каждый запрос получил свой id. Каждый id побайтно
совпадает с заголовком CF-RAY того же ответа — а значит, click id в логах вашей целевой
стороны напрямую сходится с логами самой Cloudflare и с дашбордом. И подставляется
полная форма с суффиксом дата-центра (здесь -BTS), а не только шестнадцать hex-символов, так что разбирайте её соответственно.
Ловушка preserve_query_string
Очевидный следующий шаг — сохранить параметры кампании, с которыми клик пришёл. У правила для этого есть переключатель — и ломается всё именно там, где он встречается с целью-выражением, у которой уже есть своя строка запроса. Это замер, а не догадка:
| Настройка | Входящий запрос | Что реально уходит в Location |
|---|---|---|
preserve_query_string: true |
/go?utm_source=tg&gclid=abc123 |
…/?utm_source=tg&gclid=abc123 — click_id пропал |
preserve_query_string: true |
/go |
…/ — строки запроса нет вообще, даже той, что была в цели |
| ручная склейка, переключатель выключен | /go?utm_source=tg&gclid=abc123 |
…/?click_id=a2368ff0…-BTS&utm_source=tg&gclid=abc123 |
preserve_query_string не подмешивает входящую строку запроса в вашу цель. Он полностью
заменяет строку запроса цели — в том числе когда входящая пуста, а это стирает только что
собранный click_id. Одно исключает другое.
Работающая форма оставляет переключатель выключенным и делает склейку прямо в выражении:
concat("https://destination.example/?click_id=", cf.ray_id, "&", http.request.uri.query)
Одна косметическая мелочь: если входящая строка запроса пуста, результат заканчивается голым &.
Любой парсер строки запроса, который нас интересует, его игнорирует, но в адресе он есть, так что знайте о нём
до того, как начнёте сравнивать логи.
На 301 к вам вернётся вчерашний id
То же правило со статусом 301 подставляет на краю сети свежий ray ID на каждый запрос — это мы
тоже замерили. Проблема не на краю, а перед ним: ответ 301 приходит без заголовка
Cache-Control, а постоянный редирект без явных сведений о свежести — ровно то, что браузеру
разрешено кешировать эвристически. Вернувшегося посетителя тогда перенаправит его собственный
кеш, с click id из первого визита, и запрос вообще не доедет до Cloudflare: в логах дубль id и
недосчитанный клик. Для этой схемы нужен статус 302 или 307.
Где это ломается
На стороне Cloudflare id никто не записывает. Редирект — терминирующее действие первой фазы
обработки запроса,
ни один аналитический продукт этого запроса не видит, и само
правило никуда писать не умеет. Id существует только в адресе Location. Если целевая сторона
не логирует свою строку запроса, id испаряется по дороге.
Ray ID — идентификатор, а не секрет. Он уникален, но его непредсказуемость не рассчитана на злоумышленника: не используйте его как токен, который что-то разрешает. Ключ для соединения данных — да. Право доступа — нет.
Id достаётся каждому обращению, а не каждому человеку. Префетчеры ссылок, сканеры и сборщики превью тоже ходят по редиректам, и каждый получает свой совершенно валидный click id. Правило их не различает; дедупликация и фильтрация остаются работой целевой стороны.
Квота настоящая, но не тесная. На бесплатном тарифе — десять правил Single Redirect на зону; именно на зону, а не на аккаунт, дальше 25 на Pro, 50 на Business, 300 на Enterprise (проверено 30 июля 2026 года). Одно правило с вайлдкардом закрывает целое семейство путей, так что десяти правил хватает на большее, чем кажется. Регулярные выражения доступны с Business, но этой схеме они не нужны.
Когда метки недостаточно
Если целевая сторона ваша, всей системой может оказаться это правило и ваша собственная
аналитика, читающая click_id: ни воркера, ни кода, нечего сопровождать, а id сходится с
логами Cloudflare сам собой.
Схема перестаёт быть достаточной ровно там, где начинается статья про атрибуцию: целевая сторона не ваша, или клик нужно записать даже тогда, когда целевая сторона не логирует ничего, или всё это работает на портфеле доменов, а не на одной зоне. Записывать на стороне редиректа — ровно то, чего терминирующее правило не умеет, а значит, эту сторону кто-то должен держать. Этим и занимается 301.st: сторона редиректа его, id он выдаёт и записывает у себя и передаёт тот же id дальше — связка работает независимо от того, логирует ли что-то целевая сторона.