Привет, Хабр! Меня зовут Андрей Бирюков. Я являюсь независимым экспертом в области ИТ и ИБ, преподаю в учебных центрах и пишу книги.

Для любого аналитика одной из самых больших проблем являются некачественные исходные данные. Например, вы как-то утром открыли таблицу с сырыми событиями и увидели там такое:

  • event_name = “Click

  • event_name = “form_submit_2

  • event_name = “send

  • event_name = “Пользователь нажал кнопку” (кириллицей, да)

Вас можно поздравить, вы стали жертвой событийного бардака. Типичная история: разработчики просто «запилили трекинг, как просили». А ведь просили вы, скорее всего, что-то вроде: «Сделайте, чтобы видно было, кто что делает».

И что мы имеем в итоге – у нас есть воронка, но считать по ней нельзя. Потому что этапы размыты, события дублируются, а пользователь может скачать файл 50 раз и засчитать себе 50 лидов.

В этой статье мы представили гайд, посвященный тому, как спроектировать систему событий, которая будет работать как часы, начиная с первого касания и до закрытой сделки.

Шаг 1. Забудьте слово «Click»

Начнем с названий событий. Событие не должно называться по типу действия в интерфейсе. То есть название события «Click» — это плохой вариант. Вместо этого называем события по их бизнес‑смыслу.

Примеры боли и решений:

Вы говорите разработчику

Он делает

Аналитик потом

«Отправь событие на клик по кнопке “Оставить заявку”»

click_button_feedback

Не поймет, это заявка, подписка или просто нажатие

«Отследи отправку формы»

form_send

А какая форма? Карьеры? Скидки? Консультации?

«Событие на успешную отправку»

success

Простите, что? Успех чего? Регистрации? Платежа? Скачивания?

В общем, как в той «баянистой» картинке:

А теперь серьезно.

Правило. Схема именования: [объект]__[действие]_[результат]

Вот несколько примеров:

- lead__created_success — лид создался;

- payment__completed_initial — первая оплата прошла;

- file__downloaded_brochure — скачали брошюру.

Здесь в качестве разделителей выступает двойное подчеркивание, если событие с частями. И никакого CamelCase и no_spaces.

Для лучшего понимания давайте выполним небольшой тест-драйв. Откройте любой сырой лог событий и найдите три самых непонятных названия. Затем переименуйте их в своей голове по новой схеме.

Шаг 2. Нарисуйте воронку на салфетке перед тем, как писать техническое задание

Аналитик и разработчик часто говорят на разных языках. Аналитик — про «лиды, клиенты, жизненный цикл». Разработчик — про «DOM-дерево, асинхронные вызовы, колбэки». Ваша задача — переложить воронку на события до того, как разработчик начнет писать код.

Возьмите лист бумаги (да, да, реальную бумагу). Напишите сверху «Пришел на сайт». Снизу — «Купил». Между ними — этапы вашей воронки.

Например:

  • Пришел на сайт.

  • Посмотрел товар (страница карточки).

  • Добавил в корзину.

  • Начал оформление.

  • Оплатил.

Теперь для каждого этапа укажите одно единственное событие, которое означает переход на этот этап.

Этап

Событие

Пришел на сайт

session__started (срабатывает на любой странице)

Посмотрел товар

product__viewed_item (сработало — человек минимум 5 секунд был на странице, иначе не считаем)

Добавил в корзину

cart__item_added (идентификатор товара, количество)

Начал оформление

checkout__started

Оплатил

payment__completed (статус "success", сумма, метод платежа)

Важнейшее правило: один этап — одно ключевое событие. Не надо на этап «Оплатил» вешать click_pay_button, payment_processing_started, payment_success. Аналитик сам запросит детали, если надо. Ему нужна чистая воронка.

Что вы можете сделать прямо сейчас? Нарисуйте эту схему на стикере (да, тоже на бумажном) и повесьте на монитор разработчика. Пусть он видит её каждый день.

Шаг 3. Убейте 99% мусора на этапе отправки события

Самая частая жалоба аналитика: «В логах 70% — боты, роботы сканирования и наши же тестировщики, которые накликали по 300 заявок».

Здесь есть два простых фильтра, которые нужно резать на стороне клиента до отправки, а не в аналитике.

Фильтр 1. Режим разработчика

Событие не отправляется, если:

- localhost, 127.0.0.1, dev- или test- в домене.

- Пользователь залогинен как tester@company.com или любой адрес с доменом вашей компании (если это внутренний тест).

Фильтр 2. Боты по User-Agent

Перед отправкой события делаете простую проверку на JavaScript:

const botPattern = /bot|crawl|spider|scrape|scan|headless/i;
if (botPattern.test(navigator.userAgent)) {
    return; // не отправляем событие
}
```

И обязательно — фильтр на window.innerWidth > 0 (безобидный способ отсечь headless-браузеры, которые часто не инициализируют размер окна).

На этом шаге, прямо сейчас проверьте, в каком проценте ваших сырых событий есть userAgent со словами bot, crawl или headless. Если больше 5%, — вы потеряли кучу денег на обработку мусора.

Шаг 4. Проектируйте обязательные поля как у пистолета — с предохранителем

Запомните три простых правила: событие без идентификатора пользователя — мусор. Событие без метки времени — мусор. Событие без идентификатора сессии — полумусор.

Ваша задача сделать так, чтобы эти поля были обязательными на уровне сбора. В Google Tag Manager — это настройка «Require» для полей. В собственном SDK — это проверка на бэкенде перед записью в лог.

Минимальный обязательный набор для любого события:

- event_name (схема из шага 1);

- user_id (если есть авторизация) или anonymous_id (если нет);

- session_id (один и тот же для всей сессии);

- timestamp (ISO 8601, с часовым поясом);

- page_url (без UTM-меток, они отдельно).

Если разработчик говорит: «А можно без session_id, мы его через IP и User-Agent склеим», — не соглашайтесь. Склейка вероятностная, точность упадет. Генерируйте session_id на клиенте при первом событии и тащите его во все последующие.

Напишите однострочный документ «Обязательные поля для всех событий» и отправьте в чат с разработкой. Скажите: «Без этих полей ваши события уходят в черную дыру и не попадают в отчеты к гендиректору».

Шаг 5. Боритесь с «двойным счетом» через один флаг

Вспомним еще одну классическую проблему: пользователь нажал кнопку «Отправить» 5 раз, потому что форма долго грузилась. У вас в логах — 5 событий lead__created_success. В CRM — 1 лид. Получаем разрыв.

Решение: событие отправляется один раз на бизнес-результат, а не на действие в интерфейсе.

Как это выглядит в коде (псевдокод для понимания):

// Плохо
document.getElementById('submit').addEventListener('click', () => {
    sendEvent('lead__created_success');
});

// Хорошо
form.addEventListener('submit', async (e) => {
    e.preventDefault();
    const response = await sendFormData();
    if (response.status === 'success' && !alreadySent) {
        sendEvent('lead__created_success', { lead_id: response.lead_id });
        alreadySent = true;
    }
});
```

Здесь нашим спасением может стать флаг alreadySent, хранящийся в памяти сессии. Пользователь может тыкать кнопку 100 раз — событие уйдет один раз.

Прямо сейчас найдите в вашей системе самое часто дублирующееся событие (подсказка: это обычно form_submit). Проверьте, есть ли там защита от двойного счета. Если нет — это ваша первая точка для исправления.

Шаг 6. Сделайте «песочницу» для новых событий, а не правку в боевых

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

Здесь вам поможет следующее правило: все новые события сначала идут в тестовый контейнер (тестовый GTM-контейнер, dev-ветку SDK). Там они живут не менее 2 дней, за это время вы проверяете:

  • Имя события соответствует схеме.

  • Все обязательные поля есть.

  • Нет ложных срабатываний (например, событие purchase не уходит при добавлении в корзину).

  • Только после этого событие передается в прод.

Прямо сейчас вы можете создать в Jira/Notion/Trello простой чек-лист для принятия нового события. Три пункта: «схема имени соблюдена?», «обязательные поля есть?», «протестировано на dev 3 дня?». Без этих галочек — не выпускать.

Шаг 7. Добавьте в события «контекст сделки», а не просто «действие»

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

Пример:

  • Вы ловите событие file__downloaded_pdf.

  • Но не знаете, к какому продукту относится этот PDF.

  • И не знаете, был ли пользователь уже лидом.

  • И не знаете, какая рекламная кампания привела этого пользователя.

Итог: все, что вы знаете, это что кто-то что-то скачал, но продажи от этого не приблизились. Здесь нужен обязательный блок для событий, которые находятся в середине воронки (между первым касанием и сделкой):

{
  "event_name": "file__downloaded_brochure",
  "user_id": "12345",
  "session_id": "sess_67890",
  "context": {
    "product_id": "PROD-42",
    "campaign_source": "google_cpc_summer_sale",
    "current_step": "consideration",
    "lead_id": "LD-100500",
    "is_known_lead": true
  }
}

Эти поля позволяют ответить на вопрос: «А люди, которые скачали брошюру по продукту X из кампании Y, купили потом или нет?» Без этого контекста вы никогда не построите нормальную атрибуцию.

 Для начала возьмите три самых частых события в вашей системе (например, просмотр карточки, добавление в корзину, отправка контактной формы). Допишите к ним контекст из примера выше. Увидите, как много не хватает.

Итоговый чек-лист для внедрения (повесьте над рабочим столом)

Перед тем как сказать разработчику «давай добавим событие», пробегитесь по списку:

  1. Имя — по схеме объект__действие_результат? (да/нет)

  2. Один этап = одно событие? (нет второго события для той же бизнес-точки)

  3. Обязательные поля — user_id, session_id, timestamp есть?

  4. Защита от дублей — флаг alreadySent стоит?

  5. Отсев ботов — проверка User-Agent и размера окна есть?

  6. Песочница — сначала на dev, потом в прод?

  7. Контекст сделки — привязано к продукту, кампании, этапу воронки?

Если хотя бы один пункт — НЕТ, событие не принимается.

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

Если в событийной аналитике хаос начинается с Click, send и form_submit_2, то в финансовых моделях — с разрозненных таблиц, ручных сверок и данных, которым никто до конца не доверяет.

На бесплатных уроках OTUS разберем, как перейти от AS IS-хаоса к TO BE-контролю: собрать единую автоматизированную финансовую модель, сократить ручные операции и убрать «человеческий фактор» из расчетов.

  • 3 июня в 20:00. «От хаоса к контролю: как построить финансовую модель, которой можно верить». Записаться
    Обсудим, как перестать тратить большую часть времени на сбор данных и сверки, а БДР и ДДС собирать автоматически.

  • 17 июня в 20:00. «Как убрать “человеческий фактор” из финансовых моделей: от расчёта NPV до сложных систем оплаты труда». Записаться
    Поговорим о том, как построить единую автоматизированную модель, где покупка оборудования, найм специалиста и KPI сотрудников считаются без ошибок и ручных правок.

А если хотите выбрать другое занятие под свои задачи — загляните в календарь открытых уроков.

Больше материалов про IT, аналитику, разработку и обучение — на канале OTUS в MAX. Подписывайтесь, чтобы не пропускать новые статьи, разборы и анонсы открытых уроков.

Комментарии (1)


  1. UniInter
    26.05.2026 19:20

    Гениальный видеоролик ровно на 1 минуту о том, как молодой аналитик спроектировала детскую игрушку и что показало тестирование: https://rutube.ru/video/25e3fcc4eb9152bd02c0fb13e8651ba8