Почему теряются UTM-метки после редиректа

Проверяем, на каком редиректе пропадают UTM-метки. Локальный стенд, журнал каждого перехода и разбор потери параметров в JavaScript и при отправке формы.

UTM-метки пропадают после редиректа — это ещё не диагноз. ИИ-агент может собрать разрешённые URL, CSV и инструкции, сравнить каждый переход с ожидаемыми параметрами и отметить первое расхождение. Проверяйте отдельно адрес, первый запрос счётчика, данные, которые отправляет форма (payload), и фактически сохранённую запись CRM: один слой не доказывает результат в другом. Человек уточняет отсутствующие бизнес-условия и решает вопросы по исключениям; вручную перепроверять каждую строку не нужно.

Выполненный ниже локальный пример использует Python 3.14 http.server.ThreadingHTTPServer с обработчиком на основе BaseHTTPRequestHandler. Это учебный стенд, а не аудит сайта, производственная настройка или проверка модели.

Сначала локализуйте слой

Запишите четыре наблюдения, а для диагностики CRM добавьте пятое — связанную с тестовой отправкой запись. Конечный URL сам по себе не показывает, что получил счётчик или сохранила CRM.

Слой Что записать На какой вопрос отвечает
Исходная ссылка Полный URL и ожидаемые utm_source, utm_medium, utm_campaign Были ли нужные значения до перехода?
Переходы и конечный URL URL каждого запроса, статус, Location, следующий URL и конечный адрес На каком переходе адрес изменился?
Первый hit Первый запрос счётчика и видимые в нём значения Какие параметры дошли до первого запроса аналитики?
Форма Точные UTM-поля в данных отправленной тестовой формы Что форма передала дальше?
CRM ID связанной записи и фактически сохранённые UTM-поля Что действительно осталось в записи CRM?

Архивный материал Яндекса о проблемах с UTM опубликован 5 февраля 2018 года и помечен как архивный. Материал Яндекса о проблемах с UTM-метками В нём среди возможных случаев описаны редирект на страницу без установленного счётчика и редирект, при котором UTM отбрасываются. Материал Яндекса о проблемах с UTM-метками Это исторические примеры для проверки, а не исчерпывающий перечень причин и не инструкция, которая гарантированно подходит любому сайту.

Документация Яндекс Метрики описывает сервис как инструмент веб-аналитики, который помогает получать наглядные отчёты и записи действий посетителей, отслеживать источники трафика и оценивать эффективность онлайн- и офлайн-рекламы. Документация Яндекс Метрики Справка об отчёте «Метки UTM» говорит, что он содержит статистику по UTM-меткам, если они используются в ссылках на сайт. Справка об отчёте «Метки UTM»

В справке о проблемах с источниками среди возможных случаев названа страница входа, на которой не установлен код счётчика. Справка Яндекс Метрики о проблемах с источниками Там же описан вариант, когда JavaScript выполняется до кода счётчика и перенаправляет пользователя внутри сайта. Справка Яндекс Метрики о проблемах с источниками Справка предлагает обнаруживать такой случай по редиректу, происходящему на сайте. Справка Яндекс Метрики о проблемах с источниками

Исчезновение UTM из адресной строки не означает автоматически, что потерялась атрибуция. Сверьте ожидаемые правила сайта с тем, что видно в первом запросе счётчика, данных формы и связанной записи CRM. В локальном сценарии /clean адрес очищается после записи параметров в учебный hit, а значения остаются в учебной форме. Это результат только нашего стенда: он не доказывает, что так же работает Метрика, форма или CRM на реальном сайте.

Снимите цепочку

Один переход в цепочке — это hop. Для каждого hop запишите URL запроса, статус ответа, заголовок Location и следующий URL. В ответе 3xx заголовок Location указывает URL, на который перенаправляется страница. Документация MDN о заголовке Location

Сравнивайте UTM в URL запроса и в следующем адресе. Наличие параметров в исходной ссылке ещё не показывает, что они сохранились после редиректа.

В выполненном локальном стенде self.path обрабатывается через urlsplit(self.path), а затем код стенда задаёт целевой адрес. В документации Python BaseHTTPRequestHandler.path описан как путь запроса, который включает query-компонент, если он есть. Документация Python о BaseHTTPRequestHandler.path В ветке keep обработчик добавляет query к целевому адресу; в ветке drop на втором переходе задаёт /landing без параметров. Поэтому в этом локальном прогоне первое расхождение возникает из-за выбранного обработчиком целевого адреса, а не из-за универсального свойства статуса 302 или 307.

В стенде send_header() задаёт значение Location. В документации Python сказано, что send_header() добавляет заголовок во внутренний буфер. Документация Python о send_header() и end_headers() Метод end_headers() завершает заголовки ответа пустой строкой и отправляет буфер. Документация Python о send_header() и end_headers() Само решение о том, включать ли query в Location, принимает обработчик, когда формирует целевой адрес.

http.server здесь нужен только для локальной учебной диагностики. Документация Python не рекомендует этот модуль для production и отмечает, что он реализует только базовые проверки безопасности. Документация Python о send_header() и end_headers() Не переносите код или логику настройки на nginx, Apache либо другой сервер без документации именно этой системы.

Для локального стенда с адресом http://127.0.0.1:8000 команды проходят HTTP-цепочки и сохраняют заголовки и трассировочный вывод:

curl -sS -L -v -D keep.headers -o /dev/null 'http://127.0.0.1:8000/keep/start?utm_source=demo&utm_medium=cpc&utm_campaign=lesson' 2>keep.trace
curl -sS -L -v -D drop.headers -o /dev/null 'http://127.0.0.1:8000/drop/start?utm_source=demo&utm_medium=cpc&utm_campaign=lesson' 2>drop.trace

Для своего сайта используйте только разрешённые URL. HTTP-журнал фиксируйте отдельно от браузерной проверки JavaScript: в локальном примере переход /js-loss исследовали в браузере, а не выводили из строки HTTP-журнала.

Заполненные цепочки выполненного локального прогона:

Сценарий Hop URL запроса Статус Location Следующий URL UTM в запросе / следующем URL
keep 0 /keep/start?utm_source=demo&utm_medium=cpc&utm_campaign=lesson 302 /keep/middle?utm_source=demo&utm_medium=cpc&utm_campaign=lesson /keep/middle?utm_source=demo&utm_medium=cpc&utm_campaign=lesson да / да
keep 1 /keep/middle?utm_source=demo&utm_medium=cpc&utm_campaign=lesson 307 /landing?utm_source=demo&utm_medium=cpc&utm_campaign=lesson /landing?utm_source=demo&utm_medium=cpc&utm_campaign=lesson да / да
keep 2 /landing?utm_source=demo&utm_medium=cpc&utm_campaign=lesson 200 — — да / —
drop 0 /drop/start?utm_source=demo&utm_medium=cpc&utm_campaign=lesson 302 /drop/middle?utm_source=demo&utm_medium=cpc&utm_campaign=lesson /drop/middle?utm_source=demo&utm_medium=cpc&utm_campaign=lesson да / да
drop 1 /drop/middle?utm_source=demo&utm_medium=cpc&utm_campaign=lesson 302 /landing /landing да / нет
drop 2 /landing 200 — — нет / —

В drop первое расхождение — переход /drop/middle → /landing: UTM есть в URL запроса, но отсутствуют в Location. В keep параметры сохранены в обоих переходах и в конечном адресе.

Полный http-hops.csv из выполненного стенда:

case,hop,request_url,status,location,utm_present
keep,0,/keep/start?utm_source=demo&utm_medium=cpc&utm_campaign=lesson,302,/keep/middle?utm_source=demo&utm_medium=cpc&utm_campaign=lesson,True
keep,1,/keep/middle?utm_source=demo&utm_medium=cpc&utm_campaign=lesson,307,/landing?utm_source=demo&utm_medium=cpc&utm_campaign=lesson,True
keep,2,/landing?utm_source=demo&utm_medium=cpc&utm_campaign=lesson,200,,True
drop,0,/drop/start?utm_source=demo&utm_medium=cpc&utm_campaign=lesson,302,/drop/middle?utm_source=demo&utm_medium=cpc&utm_campaign=lesson,True
drop,1,/drop/middle?utm_source=demo&utm_medium=cpc&utm_campaign=lesson,302,/landing,True
drop,2,/landing,200,,False
js-loss,0,/js-loss?utm_source=demo&utm_medium=cpc&utm_campaign=lesson,200,,True
clean,0,/clean?utm_source=demo&utm_medium=cpc&utm_campaign=lesson,200,,True
form-loss,0,/form-loss?utm_source=demo&utm_medium=cpc&utm_campaign=lesson,200,,True

В http-hops.csv поле utm_present показывает наличие UTM в URL запроса этой строки. Сравнивайте его с Location и следующим URL, а не используйте отдельно как доказательство сохранения параметров после перехода.

Проверьте браузер и заявку

Для реального сайта проводите тест в тестовой среде или с заранее согласованным разрешением. В браузере включите сохранение журнала Network, например Preserve log. Запишите переходы, конечный URL и первый реальный запрос счётчика. Фиксируйте только значения, видимые в запросе; неизвестные поля не восстанавливайте по догадке.

Результаты локальной браузерной проверки:

Сценарий Конечный URL utm_source в первом локальном hit utm_source в учебной форме
/js-loss /landing пусто пусто
/clean /clean demo demo
/form-loss /form-loss?utm_source=demo&utm_medium=cpc&utm_campaign=lesson demo пусто

Полный browser-results.csv:

scenario,final_url,first_hit_utm_source,form_utm_source
/js-loss,/landing,,
/clean,/clean,demo,demo
/form-loss,/form-loss?utm_source=demo&utm_medium=cpc&utm_campaign=lesson,demo,

В стенде /hit — учебная точка, а не Метрика; /form — имитация формы, а не CRM. Внешних запросов аналитики и реальных заявок не было. В /js-loss браузерный сценарий приводит к /landing до локального hit. В /clean параметры есть в hit и форме, хотя адрес очищен. В /form-loss параметры видны в конечном URL и hit, но отсутствуют в учебной форме. Это результаты локального примера, а не доказательство работы интеграции на реальном сайте.

Чтобы проверить реальную заявку, пройдите весь путь от ссылки до связанной записи CRM:

  1. Создайте test_id. Запишите ожидаемые utm_source, utm_medium, utm_campaign, исходный URL, дату и время с часовым поясом, тестовую среду и выбранную цель. Не отправляйте персональные данные и не создавайте рабочий заказ или оплату.
  2. Откройте ссылку с включённым Preserve log. Сохраните HTTP- и JavaScript-переходы, конечный URL и первый реальный запрос счётчика. Запишите только поля, которые видны в запросе.
  3. С разрешения отправьте одну маркированную тестовую форму. Зафиксируйте endpoint, время, статус, точные UTM-поля в данных запроса формы, ответ и ID заявки или correlation_id, если он выдан. HTTP 200 сам по себе не доказывает появление записи в CRM.
  4. По тому же техническому ID найдите разрешённую запись CRM через доступный read-only API, экспорт или интерфейс. Запишите record_id, created_at и фактически сохранённые UTM-поля. Связь с формой подтверждайте техническим ID, а не похожим именем или близким временем.
  5. Если UTM есть в данных запроса формы, но отсутствуют в связанной записи CRM, проверьте обработку формы, интеграцию и сопоставление полей. Запросите разрешённый серверный лог этой отправки; без него не назначайте конкретную систему виновной. Если параметры отсутствуют уже в запросе формы, проверяйте заполнение формы. Если расхождение появилось раньше — проверяйте соответствующий HTTP- или JavaScript-переход.
  6. Если нет доступа, ID, лога или связанной записи, отметьте слой CRM как «не проверен». Не утверждайте, что CRM сохранила или потеряла значения без подтверждения.
  7. После исправления повторите сценарий с новым test_id и теми же ожидаемыми UTM. Сравните все слои, а тестовую запись обработайте по заранее согласованным правилам тестовой среды.

Копируемый шаблон журнала для реального теста:

test_id,started_at,expected_utm,final_url,first_hit_observed,form_payload_utm,submission_id,crm_record_id,crm_saved_utm,evidence,status,next_observation
заполнить,заполнить,source=...;medium=...;campaign=...,не проверено,не проверено,не проверено,не проверено,не проверено,не проверено,ссылки на разрешённые наблюдения,не проверено,указать недостающее свидетельство или следующий шаг

В этом заполненном шаблоне нет данных реальной CRM: фактические значения нужно внести по наблюдениям. Названия полей реальной формы и CRM сопоставляйте с документацией установленной интеграции; неизвестные значения отмечайте как непроверенные.

Сбор разрешённых журналов, отбор строк и сверку с ожидаемыми параметрами можно поручить ИИ-агенту по заданным правилам. Агент должен указать точный переход или поле первого расхождения и отметить, каких данных не хватает. Человек уточняет отсутствующие бизнес-факты и решает вопросы по исключениям; ручная вычитка каждой строки не требуется.

Запрос аналитическому помощнику

Это заготовка для анализа разрешённых данных. Запрос не выполнялся моделью и не является результатом проверки ИИ.

Ты — аналитический помощник. Найди, на каком слое и переходе UTM перестают передаваться. Не делай вывод о потере атрибуции только по конечному URL.

Входные данные:
1. Исходные размеченные URL, дата и время теста, ожидаемые значения utm_source, utm_medium и utm_campaign.
2. Журнал HTTP-переходов: test_id, hop, request_url, status, location, следующий URL, конечный URL и наблюдаемые UTM.
3. Браузерные наблюдения: переходы, конечный URL и первый реальный запрос счётчика с видимыми полями.
4. Разрешённая тестовая отправка формы: endpoint, время, статус, фактические UTM в данных запроса формы и ответ, submission_id или correlation_id.
5. Если доступно разрешённое свидетельство CRM: технический ID связи с отправкой, record_id, created_at и фактически сохранённые UTM-поля.
6. Правила сайта: какие значения и в каких слоях должны сохраниться, когда разрешена очистка URL и какие исключения допустимы.

Правила анализа:
- Разделяй исходную ссылку, каждый HTTP-запрос, статус и Location, следующий и конечный URL, первый запрос счётчика, данные запроса формы и связанную запись CRM.
- Для каждой строки HTTP-журнала сравнивай UTM в URL запроса и следующем URL. Укажи первый hop, где ожидаемые параметры исчезают, либо отметь, что в HTTP-цепочке расхождения не найдено.
- Не делай вывод о JavaScript по одной HTTP-трассе. Используй для этого только переданные браузерные наблюдения.
- Не называй локальный /hit Метрикой, а локальный /form — CRM. Не выдавай локальные результаты за доказательство работающей интеграции.
- Исчезновение UTM из адреса после подтверждённой фиксации не объявляй потерей автоматически. Сверь, сохранены ли значения в слоях, которые требуются по правилам сайта.
- Считай запись CRM проверенной только при наличии разрешённого свидетельства о фактически сохранённой записи и технической связи с тестовой отправкой. Не угадывай связь по похожему имени или времени.
- Если данные запроса формы содержат UTM, а связанная запись CRM их не содержит, укажи участок обработки формы, интеграции или сопоставления полей как область дальнейшей проверки. Запроси разрешённый серверный лог отправки; не назначай конкретную систему виновной без свидетельства.
- Если UTM отсутствуют уже в данных запроса формы, укажи участок заполнения формы. Если потеря произошла раньше, укажи соответствующий HTTP- или JavaScript-переход.
- Если нет ожидаемых значений, правил сайта, доступа, ID, лога или связанной записи, напиши «не проверено» и перечисли, какого свидетельства не хватает. Не восстанавливай неизвестные значения.
- Не предлагай универсальную конфигурацию nginx, Apache или другого сервера. Подготовь для разработчика наблюдаемые URL, hop и фактическое расхождение.

Для каждого сценария выведи:
1. test_id, исходный URL и ожидаемые UTM.
2. Краткую цепочку: request_url → status → Location → следующий URL.
3. Первый hop, где результат расходится с ожиданием, либо отметку, что расхождения в HTTP-цепочке нет.
4. Отдельные статусы проверки: URL, браузер/JavaScript, первый запрос счётчика, данные запроса формы и связанная запись CRM. Для непроверенных слоёв укажи нужное свидетельство.
5. Вывод: потеря в URL/HTTP, JavaScript-переход, заполнение формы, обработка/интеграция CRM, нормальная очистка по правилам сайта или недостаточно данных.
6. Точное сообщение разработчику с воспроизводимым URL, проблемным переходом или полем и приложенными наблюдениями.
7. Проверки для повторного прогона после исправления с новым test_id и теми же ожидаемыми параметрами.

Не объединяй выводы по разным слоям. Если свидетельств недостаточно, обозначь неопределённость и не выдавай предположение за установленную причину.

Исправление и повтор

Передайте задачу ответственному за слой, где зафиксировано первое расхождение. Для HTTP-перехода приложите исходный URL, hop, статус и Location. Для JavaScript — браузерный сценарий, конечный URL и наблюдение первого запроса счётчика. Для формы — ожидаемые и фактические значения в данных запроса. Для CRM — ID отправки, ID связанной записи и сохранённые поля либо отметку, что слой не проверен.

После исправления повторите все пять сценариев локального стенда и сохраните новый результат:

Сценарий Что перепроверить
keep Ожидаемые параметры в каждом запросе, каждом Location и конечном URL.
drop Переход /drop/middle → /landing; сверить, должны ли метки сохраняться на этом шаге или уже быть зафиксированы в нужном слое.
js-loss В браузере проверить JavaScript-переход, первый запрос счётчика и конечный URL; HTTP-журнала для этого сценария недостаточно.
clean Проверить, что адрес очищается после нужной фиксации, а значения остаются в требуемых слоях.
form-loss Сопоставить первый запрос счётчика, данные разрешённой тестовой формы и связанную запись CRM; не приписывать расхождение HTTP-переходу, если его там нет.

На реальном сайте повторите проверяемый путь с новым test_id и теми же ожидаемыми параметрами. Сравните исходный URL, переходы, первый запрос счётчика, данные формы и связанную запись CRM. Если слой недоступен, оставьте его непроверенным и укажите, какого свидетельства не хватает.

Для диагностики качества исходных данных пригодится материал «Почему данные врут тихо», а для сверки общей таблицы метрик — «Анализ маркетинговых данных с нейросетью».

Учебный пример

Это выполненный локальный прогон с синтетическими данными: не клиентский кейс, не полевой тест и не проверка качества модели. В журнале 9 HTTP-запросов по пяти сценариям. keep прошёл со статусами 302, 307, 200; drop — 302, 302, 200. Параметры пропали на /drop/middle → /landing. В отдельном браузерном прогоне /js-loss не передал utm_source в первый локальный hit; /clean сохранил demo в локальном hit и учебной форме после очистки адреса; /form-loss сохранил demo в локальном hit, но не в учебной форме. Внешних запросов аналитики не было.

Скачать архив с исходными URL, локальными HTTP-редиректами, CSV переходов, ожидаемыми параметрами, заполненным отчётом и отдельной инструкцией браузерной проверки JavaScript. Отдельные файлы: http-hops.csv, browser-results.csv, cases.csv.

Полный cases.csv:

case,start,expected_loss
keep,/keep/start,none
drop,/drop/start,second_redirect
js-loss,/js-loss,client_navigation_before_first_hit
clean,/clean,none_after_first_hit_url_clean
form-loss,/form-loss,form_payload_only

Проверенный результат локального прогона:

Проверка Результат
HTTP-цепочки keep 302 → 307 → 200; query сохранён
HTTP-цепочки drop 302 → 302 → 200; потеря на /drop/middle → /landing
/js-loss Первый локальный hit не содержит utm_source; конечный URL — /landing
/clean Конечный URL — /clean; первый hit и учебная форма содержат demo
/form-loss Первый hit содержит demo, учебная форма — нет
Внешние запросы аналитики 0

Чек-лист повторной проверки

  • Сохранить test_id, исходный URL, дату и время с часовым поясом, тестовую среду и ожидаемые UTM.
  • Записать каждый HTTP hop: URL запроса, статус, Location, следующий URL и конечный адрес.
  • Проверить JavaScript отдельно в браузере с сохранённым журналом Network.
  • Зафиксировать первый реальный запрос счётчика и только значения, видимые в нём.
  • С разрешения отправить одну тестовую форму без персональных данных, заказа и оплаты; сохранить данные запроса, статус и ID отправки.
  • Найти связанную разрешённую запись CRM по техническому ID и записать фактически сохранённые поля.
  • Если нет доступа, ID, лога или связанной записи, отметить слой как «не проверен» и указать нужное свидетельство.
  • Считать исчезновение из адреса нормальным только после подтверждения сохранения значений в требуемых слоях.
  • После исправления повторить сценарий с новым test_id и теми же ожидаемыми UTM.
  • Передать разработчику точный hop или поле, где наблюдаемое значение отличается от ожидаемого.

Валерий Курземнек

Автор метода «Машиночитаемый маркетинг»

Что ещё почитать