UTM-разметка: почему у тебя бардак и как навести порядок раз и навсегда
Реальные проблемы UTM: legacy, редиректы, мэтчинг фронта и бэка, нормализация, мониторинг каналов. SQL-запросы для ClickHouse и PostgreSQL.
Открой отчёт по источникам. Видишь google, Google, GOOGLE, google.com и гугл — всё как отдельные строки? Это ещё цветочки. Настоящий UTM-ад начинается, когда ты приходишь в компанию, где «у нас так заведено», и пытаешься из этого хаоса собрать хоть какую-то аналитику.
Масштаб проблемы: не только регистр
Все статьи про UTM начинаются с «используйте нижний регистр». Спасибо, капитан. А что делать, когда:
- У тебя 5 команд, и каждая размечает по-своему уже 2 года
- Legacy-метки невозможно поменять задним числом, потому что на них завязаны отчёты для директора
- Редиректы на сайте молча убивают твою разметку
- Бизнес хочет видеть каналы
paid_performanceиpaid_brand, а неcpcиcpm
Вот об этом и поговорим. Без воды, по порядку.
Топ-5 ошибок: быстрая диагностика
1. Разный регистр
UTM-параметры регистрозависимы. utm_source=Google и utm_source=google — две строки в отчёте. Всегда нижний регистр, без исключений.
2. Нет единой конвенции
Один пишет email, другой e-mail, третий newsletter. Без зафиксированного стандарта каждый будет творить что хочет. И будет прав — ты же не дал правил.
3. Кириллица и пробелы
utm_campaign=весенняя распродажа — кошмар. Пробелы превращаются в %20, кириллица кодируется. Только латиница, только дефисы.
4. Нет utm_campaign
Многие ставят только source и medium. Ты знаешь, что трафик из Яндекс.Директ, но не знаешь из какой кампании. Бюджет «размазан», оптимизировать нечего.
5. UTM на внутренних ссылках
Ставишь UTM на баннер внутри сайта — перезаписываешь оригинальный источник. Человек пришёл из рекламы, кликнул по баннеру на главной, и теперь его источник — internal_banner. Атрибуция сломана. Для внутренних переходов используй отдельные параметры, не UTM.
Проблемы, о которых молчат курсы
Команды не понимают, зачем это нужно
Не все участники маркетинга осознают проблему. Маркетолог запускает кампанию с utm_source=facebook, таргетолог пишет Facebook, агентство — fb. Три строки вместо одной, бюджет «размывается».
И если ты пришёл в компанию, где уже кто-то что-то размечал до тебя — готовься к legacy. На вопрос «почему у вас utm_medium=paid-social-fb-retarget?» ты услышишь: «у нас так заведено». Это нормально. Ненормально — оставить всё как есть.
Редиректы убивают UTM-метки
Об этом точно никто не пишет. Представь: пользователь переходит на сайт, попадает на страницу авторизации, редирект бросает его в личный кабинет. URL выглядит так:
https://site.ru/login?return_to=https://site.ru/dashboard?utm_source=yandex&utm_medium=cpcUTM-метки вложены в параметр return_to, а не стоят на верхнем уровне URL. Яндекс.Метрика их просто игнорирует. Хит уходит на сервер без разметки, и трафик падает в (direct).
✓ Правильно
site.ru/page?utm_source=yandex&utm_medium=cpc
Метрика видит: yandex / cpc
✗ Неправильно
site.ru/login?return_to=…?utm_source=yandex
Метрика видит: (direct) / (none)
Стандарты Метрики ≠ потребности бизнеса
Яндекс.Метрика рекомендует свои стандарты разметки для красивых отчётов «Источники, сводка». Но если бизнесу нужны каналы paid_performance, paid_brand, crm_email, partner_referral — стандарты Метрики не подойдут.
И это нормально. Бизнес отвечает на вопрос «как больше заработать», а не «как красиво выглядит в стандартных отчётах». Данные по выручке, сделкам, транзакциям живут на бэкенде, и именно их нужно мэтчить с фронтовой разметкой.
Конвенция: бери и внедряй
| Параметр | Формат | Пример |
|---|---|---|
utm_source | платформа, lowercase | yandex, google, vk, telegram |
utm_medium | тип трафика | cpc, cpm, email, social, referral |
utm_campaign | YYYYMM_категория_название | 202603_promo_spring-sale |
utm_content | формат или вариант | banner_1200x628, text_v2 |
utm_term | ключевое слово | kupit_krossovki |
Правила:
- Только нижний регистр, только латиница
- Разделитель слов — дефис (
-), разделитель сегментов — нижнее подчёркивание (_) - Дата в начале
campaignдля сортировки - Никаких пробелов, кириллицы, спецсимволов
Как привести всё в порядок: алгоритм
-
Зафиксируй стандарт. Одна страница в Confluence / Notion / Google Docs. Таблица выше — минимум. Отправляй каждому новому подрядчику до начала работы.
-
Сделай генератор. Google-таблица с выпадающими списками и формулой, которая собирает URL. Никто не вбивает UTM руками. Или разверни генератор на внутреннем сайте — решение любое, главное чтобы было одно.
-
Напиши маппинг для legacy. Функция или словарь, который превращает старые метки в новый стандарт. Да, это ручная работа. Да, без неё никак.
-
Назначь ответственных. Один-два человека, к которым команды обращаются с вопросами «как размечать». Без ответственного стандарт умрёт через месяц.
-
Нормализуй на уровне SQL. Все отчёты строятся через функции нормализации, а не на сырых данных.
-
Присвой бизнес-каналы. Поверх нормализованных UTM — бизнес-логика:
paid_performance,paid_brand,organic,crm_emailи т.д. Именно эти каналы видит бизнес. -
Повесь тесты. Мониторинг доли каналов в динамике. Аномалия = кто-то снова накосячил.
SQL: находим бардак и нормализуем
Сначала масштаб бедствия — все вариации одного источника:
-- PostgreSQL: дубликаты utm_source по нижнему региструSELECT lower(utm_source) AS source_normalized, array_agg(DISTINCT utm_source) AS variants, COUNT(DISTINCT utm_source) AS variant_count, COUNT(*) AS total_sessionsFROM marketing_eventsWHERE utm_source != '' AND event_date >= CURRENT_DATE - INTERVAL '90 days'GROUP BY source_normalizedHAVING COUNT(DISTINCT utm_source) > 1ORDER BY total_sessions DESC;Этот запрос покажет, что google имеет варианты ['Google', 'google', 'GOOGLE', 'google.com'] — 4 варианта. А теперь представь это по каждому источнику.
Создаём словарь нормализации:
-- PostgreSQL: таблица нормализацииCREATE TABLE utm_normalization ( raw_value TEXT PRIMARY KEY, normalized_value TEXT NOT NULL);
INSERT INTO utm_normalization VALUES ('Google', 'google'), ('GOOGLE', 'google'), ('google.com', 'google'), ('Facebook', 'facebook'), ('FB', 'facebook'), ('fb', 'facebook'), ('ВКонтакте', 'vk'), ('vkontakte', 'vk');Чистый отчёт с бизнес-каналами:
-- PostgreSQL: отчёт с бизнес-каналамиSELECT coalesce(n.normalized_value, lower(m.utm_source)) AS source_clean, lower(m.utm_medium) AS medium_clean, CASE WHEN lower(m.utm_medium) IN ('cpc', 'ppc') AND lower(m.utm_campaign) LIKE '%brand%' THEN 'paid_brand' WHEN lower(m.utm_medium) IN ('cpc', 'ppc') THEN 'paid_performance' WHEN lower(m.utm_medium) = 'email' THEN 'crm_email' WHEN lower(m.utm_medium) IN ('social', 'smm') THEN 'organic_social' WHEN m.utm_source = '' AND m.utm_medium = '' THEN 'direct' ELSE 'other' END AS business_channel, COUNT(DISTINCT m.user_id) AS users, COUNT(*) FILTER (WHERE m.event_type = 'purchase') AS purchases, SUM(m.revenue) FILTER (WHERE m.event_type = 'purchase') AS revenueFROM marketing_events mLEFT JOIN utm_normalization n ON m.utm_source = n.raw_valueWHERE m.event_date >= CURRENT_DATE - INTERVAL '30 days'GROUP BY source_clean, medium_clean, business_channelORDER BY revenue DESC;Попробуй: найди дубликаты UTM
Мониторинг: ловим аномалии до того, как они сломают отчёт
Алгоритм мониторинга:
-
Собери референс. Возьми 3-6 месяцев данных с корректной разметкой. Посчитай среднюю долю визитов на каждый бизнес-канал.
-
Следи за долей
other. Каналother— это всё, что не смэтчилось по правилам. Если его доля больше 15-20% — проблема. Если растёт — кто-то снова косячит. -
Вешай тесты на пороги. Если доля канала вырастает на 20% и более от среднего — это аномалия. Лезь в сырые данные и смотри, что попадает в
utm_medium,utm_source,utm_campaignконкретного канала.
-- PostgreSQL: мониторинг доли каналов по неделямWITH channel_stats AS ( SELECT date_trunc('week', event_date)::date AS week, CASE WHEN lower(utm_medium) IN ('cpc', 'ppc') THEN 'paid' WHEN lower(utm_medium) = 'email' THEN 'email' WHEN lower(utm_medium) IN ('social', 'smm') THEN 'social' WHEN utm_source = '' THEN 'direct' ELSE 'other' END AS channel, COUNT(*) AS visits FROM marketing_events WHERE event_date >= CURRENT_DATE - INTERVAL '180 days' GROUP BY week, channel),weekly_totals AS ( SELECT week, SUM(visits) AS total FROM channel_stats GROUP BY week)SELECT cs.week, cs.channel, cs.visits, ROUND(cs.visits::numeric / wt.total * 100, 1) AS share_pctFROM channel_stats csJOIN weekly_totals wt ON cs.week = wt.weekORDER BY cs.week DESC, share_pct DESC;Хаос → Порядок: до и после
До (хаос):
| utm_source | utm_medium | sessions |
|---|---|---|
| cpc | 1,200 | |
| CPC | 800 | |
| google.com | paid | 350 |
| Гугл | cpc | 150 |
Четыре строки. Ты думаешь, что Google приносит 1,200 визитов. На самом деле — 2,500.
После (порядок):
| source | medium | business_channel | sessions |
|---|---|---|---|
| cpc | paid_performance | 2,500 |
Одна строка. Чистая картина. Правильные решения.
Динамические параметры: автоматизируй или страдай
Ручная UTM-разметка — это путь в ад при масштабе. Яндекс.Директ и Google Ads умеют подставлять значения автоматически через макросы.
Яндекс.Директ
| Макрос | Что подставит |
|---|---|
{campaign_id} | ID кампании |
{ad_id} | ID объявления |
{keyword} | Ключевое слово |
{source} | Площадка (домен сайта РСЯ или none для поиска) |
{source_type} | Тип площадки: search или context |
{position_type} | Тип блока: premium, other, none |
Пример: ?utm_source=yandex&utm_medium={source_type}&utm_campaign={campaign_id}&utm_content={ad_id}&utm_term={keyword}
Google Ads
| Макрос | Что подставит |
|---|---|
{campaignid} | ID кампании |
{creative} | ID объявления |
{keyword} | Ключевое слово |
{network} | Сеть: g (поиск), d (КМС), y (YouTube) |
{matchtype} | Тип соответствия: e, p, b |
Пример: ?utm_source=google&utm_medium=cpc&utm_campaign={campaignid}&utm_content={creative}&utm_term={keyword}
Атрибуция: кому засчитать конверсию
Ты навёл порядок в UTM. Нормализовал, замэппил, повесил бизнес-каналы. И тут приходит вопрос: пользователь за 2 недели зашёл 5 раз из разных каналов — кому засчитать покупку?
Это задача атрибуции, и она заслуживает отдельной статьи. Но вот суть: большинство компаний используют Last Paid Click — конверсия засчитывается последнему платному касанию. Если платных не было — последнему значимому бесплатному (органика, SMM). Если и таких нет — Direct.
-- PostgreSQL: LPC-атрибуция с приоритетамиSELECT customer_id, event_type, channel_group, visit_datetime, ROW_NUMBER() OVER ( PARTITION BY customer_id, event_type ORDER BY CASE WHEN channel_group IN ('paid_performance', 'paid_display', 'smm_paid') THEN 1 WHEN channel_group NOT IN ('direct', 'other') THEN 2 ELSE 3 END, visit_datetime DESC ) AS rn_lpcFROM user_visitsWHERE visit_datetime >= event_datetime - INTERVAL '90 days';-- Берём rn_lpc = 1Подробнее — в статье про модели атрибуции в SQL: Last Click, Last Paid Click с 3-уровневым приоритетом, линейная, U-shape, Time Decay — с готовыми запросами и интерактивными примерами.
Как не допустить отката
-
Генератор UTM. Google-таблица / внутренний сервис с выпадающими списками. Никто не вбивает UTM руками.
-
Валидация. Если есть свой трекер — проверяй UTM при приёме. Отвергай кириллицу, заглавные, пробелы.
-
Регулярный аудит. Запускай SQL-запрос для поиска дубликатов раз в неделю. Новый мусор появляется, когда подключаются подрядчики.
-
Документация. Одна страница с правилами + генератор. Отправляй до начала работы.
-
Санкции. Звучит жёстко, но без последствий стандарт не будут соблюдать. Точки контроля в процессах — обязательно.
FAQ
UTM-метки не передаются в Яндекс.Метрику. Что проверить?
Три вещи: 1) метки стоят на верхнем уровне URL, а не внутри параметров редиректа, 2) нет JavaScript-редиректов, которые теряют query string, 3) счётчик Метрики загружается до редиректа.
Можно ли использовать свои параметры вместо utm_?
Можно, но Яндекс.Метрика и GA4 распознают только стандартные utm_source, utm_medium, utm_campaign, utm_content, utm_term. Свои параметры придётся обрабатывать на уровне SQL из сырых данных или Logs API.
Как быть с legacy-метками: переделывать или маппить?
Маппить. Менять старые метки задним числом невозможно — данные уже собраны. Создай словарь raw → normalized и применяй в SQL. Новые метки — по стандарту. Подробнее про работу с SQL-воронками на размеченных данных — в статье про анализ воронок.
Какой процент канала other считается нормальным?
Ниже 10% — хорошо. 10-20% — терпимо, но надо разбираться. Больше 20% — проблема с разметкой. Смотри в динамике: если доля растёт — значит кто-то снова размечает не по стандарту.
Нужен ли UTM для SEO-трафика?
Нет. Поисковый трафик определяется автоматически по referer. Если повесить UTM на органическую ссылку — перезапишешь источник и испортишь данные по SEO. UTM только для платного и контролируемого трафика.
Как отследить, что подрядчик разметил неправильно?
Запрос на поиск дубликатов (выше в статье) + мониторинг доли other. Если после запуска новой кампании other вырос — смотри сырые данные: что туда попало, от кого, с какими метками. Подробнее про построение мониторинговых дашбордов — в статье про DataLens.
Итого
UTM-разметка — не скучная бюрократия и не «базовый навык из курса». Это фундамент, без которого вся аналитика — от отчётов по воронкам до дашбордов — построена на песке. Стандарт, генератор, маппинг, бизнес-каналы, тесты — делай один раз, экономь часы каждую неделю. А когда кто-то скажет «у нас так заведено» — покажи ему долю канала other в 40% и спроси, нравится ли ему принимать решения на мусорных данных.
Нравится контент? Подписывайся на Telegram
SQL-задачки, кейсы из практики и закулисье аналитики.
Похожие статьи
Модели атрибуции в SQL: от Last Click до Time Decay на реальных данных
6 моделей атрибуции в SQL: Last Click, LPC с 3-уровневым приоритетом, линейная, U-shape, Time Decay. Готовые запросы PostgreSQL + ClickHouse.
Сквозная аналитика без Roistat: считаем ROI по каналам на чистом SQL
Сквозная аналитика на SQL: расходы, визиты, лиды, сделки → ROI по каналам. Готовые запросы PostgreSQL + ClickHouse. Без Roistat.
Анализ воронки на чистом SQL: от визита до покупки
Строим полный анализ воронки конверсий в ClickHouse: расчёт CR между шагами, поиск узких мест, когортные воронки.