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

Свой счётчик — это не просто адрес для POST-запроса
Если сайт отправляет просмотры страниц и клики во внешний сервис, часть данных уходит за пределы вашей инфраструктуры. Собственный сбор событий возвращает контроль над маршрутом данных, но вместе с ним — ответственность за доставку, хранение и качество аналитики. Самая частая ошибка — принять запрос в обработчике и считать задачу решённой: событие может потеряться при сбое, задвоиться при повторе или оказаться бесполезным из-за несогласованной схемы.
Практичная отправная точка для сайта: браузер отправляет короткое событие на endpoint вашего домена, сервер проверяет его и передаёт в очередь или хранилище. Ниже — как выбрать способ доставки и что предусмотреть до запуска. Пример клиентского кода использует стандартный браузерный API, без внешних библиотек.
Сначала определите, что именно считать
Не собирайте «всё, что можно». Составьте небольшой перечень событий, по которым вы действительно принимаете продуктовые решения: например, просмотр страницы товара, отправка формы или завершение оформления. Для каждого зафиксируйте название, обязательные поля и момент отправки. «Кнопку нажали» и «заказ создан» — разные события: первое сообщает о действии в интерфейсе, второе подтверждает результат на сервере.
Полезный минимум для события: имя, время, путь страницы, идентификатор события и ограниченный набор свойств. Время клиента удобно для порядка действий, но оно может быть неверным; серверу стоит дополнительно проставлять время при приёме. Не включайте в свойства пароли, текст переписки, адрес электронной почты и другие данные, без которых отчёт обойдётся.
Термин. Схема события — договорённость о его полях и типах: например, что product_id всегда строка, а quantity всегда целое число. Без такой схемы одно и то же событие быстро расползается на несовместимые варианты.
Выберите способ отправки
У браузера есть несколько вариантов доставки, но у каждого своя цена. Для аналитики на уходе со страницы особенно важна разница между запросом, который браузер старается отправить в фоне, и запросом, который может задержать завершение действия.
| Подход | Когда подходит | Ограничение |
|---|---|---|
navigator.sendBeacon() | Небольшое событие при закрытии страницы или переходе на другую | Не подходит, если клиенту нужно прочитать ответ сервера; успешный возврат означает постановку в очередь браузера, а не подтверждённую запись |
fetch() с keepalive | Нужен обычный HTTP-запрос, в том числе с обработкой ответа | Завершение страницы всё равно не гарантирует доставку; есть ограничения браузера на объём и время фонового запроса |
| Отправка с сервера | Событие известно серверу достоверно: заказ оплачен, аккаунт создан | Не фиксирует действия, которые произошли только в интерфейсе и не дошли до сервера |
Для клиентских событий обычно разумно начать с sendBeacon, а для подтверждённых бизнес-результатов отправлять отдельное серверное событие. Не пытайтесь выбрать один способ для всех случаев: клик и успешная оплата имеют разный источник истины.
Собственный сбор не делает данные автоматически точными. Он лишь переносит границу доверия к вам: теперь вы отвечаете и за то, что приняли, и за то, что ошибочно назвали аналитикой.
Минимальный клиентский отправитель
Пример рассчитан на современные браузеры с поддержкой navigator.sendBeacon и crypto.randomUUID(); последняя функция доступна в безопасном контексте, например на HTTPS. Путь /events должен вести на ваш endpoint того же origin. Перед вызовом подключите проверку согласия, если она требуется для вашего сценария.
function sendEvent(name, properties = {}) {
const event = {
event_id: crypto.randomUUID(),
name,
occurred_at: new Date().toISOString(),
path: window.location.pathname,
properties
};
const body = new Blob(
[JSON.stringify(event)],
{ type: "application/json" }
);
const queued = navigator.sendBeacon("/events", body);
if (queued) return;
fetch("/events", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(event),
keepalive: true,
credentials: "same-origin"
}).catch(() => {});
}Вызов может выглядеть так: sendEvent("product_view", { product_id: "sku-42" }). Не отправляйте целиком URL с query-параметрами: там могут оказаться поисковые запросы, идентификаторы или другие чувствительные значения. В примере фиксируется только путь.
Возврат true из sendBeacon не подтверждает, что сервер записал событие. Поэтому endpoint должен быть быстрым, а потерю части фоновых запросов нужно учитывать в аналитике. Не используйте этот канал для действий, от которых зависит пользовательский результат: оформление заказа должно подтверждаться обычным ответом приложения.
Что обязан делать сервер
Endpoint — часть вашей системы, а не открытый приёмник произвольных JSON-документов. На входе проверяйте тип содержимого, размер тела, допустимые имена событий и поля. Ограничивайте частоту запросов и не доверяйте значениям, которые прислал браузер: клиент может изменить любое поле. Если сайт работает за reverse proxy, настройте маршрут так, чтобы запрос не попадал в сторонний аналитический сервис и не обходил обычные ограничения инфраструктуры.
После проверки сервер должен передать запись в надёжное хранилище или очередь. Не считайте успешный ответ признаком долговечной записи, если обработчик только положил данные в память процесса. Для повторной доставки используйте event_id и дедупликацию: повтор одного идентификатора не должен увеличивать метрику дважды. Отдельно логируйте отклонённые события, но не копируйте в лог весь исходный payload без фильтрации.
Как понять, что сбор работает
- Зафиксируйте контракт. Опишите названия событий, обязательные поля, типы и допустимый размер. Проверка: фронтенд и сервер используют одну и ту же схему.
- Поднимите endpoint на своём домене. Он должен проверять запрос и передавать событие дальше, а не молча подтверждать приём. Проверка: тестовое событие видно в выбранном хранилище или очереди.
- Добавьте отправку одного события. Начните с события, у которого легко проверить результат, например просмотра конкретной страницы. Проверка: в браузере запрос уходит на ожидаемый адрес, а в хранилище появляется запись с нужными полями.
- Проверьте уход со страницы. Отправьте событие перед переходом и повторите тест с медленной сетью. Проверка: вы понимаете, что именно теряется, и не принимаете постановку в очередь браузера за подтверждение записи.
- Сравните клиентский и серверный результат. Для бизнес-события сопоставьте число отправок из интерфейса с числом подтверждений приложения. Проверка: расхождение измеряется, а не объясняется догадками.
Где схема ломается первой
Блокировщики и ограничения браузера
Собственный домен не гарантирует, что запрос пройдёт: блокировщики, сетевые фильтры и расширения могут блокировать известные пути или типы запросов. Не маскируйте сбор под другой трафик. Сделайте endpoint частью документированной архитектуры, а полноту клиентских данных оценивайте сравнением с событиями, которые сервер знает независимо.
События дублируются или пропадают
Повторная отправка может создать дубль, а закрытие вкладки — оборвать запрос. Решение — идентификатор события, идемпотентная запись и отдельные показатели доставки: сколько запросов принято, отклонено и дошло до хранилища. Если повторный запрос создаёт новую запись, отчёты будут завышать конверсии.
Свойства превращаются в персональные данные
Путь страницы, поисковая строка и произвольные параметры формы способны раскрыть больше, чем кажется. Задайте разрешённый список полей, исключите содержимое форм по умолчанию и определите срок хранения. Собственный сервер не отменяет требований к обработке данных и не делает сбор анонимным сам по себе.
Что делать дальше
Начните с трёх-пяти событий, которые отвечают на конкретные вопросы продукта. Затем настройте дедупликацию по event_id, метрики потерь и проверку схемы на сервере. Когда базовый поток стабилен, решите, какое хранилище подходит объёму и типу запросов; инвалидация данных и построение витрин — отдельные задачи. В первую неделю следите не только за числом событий, но и за долей отклонённых запросов, задержкой до записи и расхождением клиентских событий с подтверждениями сервера.
Аналитик звонков: Разбор записи звонка по этапам сделки: где сорвалось, что упущено, три исправляемые рекомендации с цитатами.
Спросить за 90 ₽Источники: MDN: Navigator.sendBeacon(), MDN: Request.keepalive
Частые вопросы
Если sendBeacon вернул true, событие точно записалось?
Можно ли полностью отказаться от внешней аналитики?
Нужны ли cookies для собственного сбора?
Что делать, если события дублируются?
Можно ли отправлять через этот endpoint данные заказа?
Почему событие не появляется, хотя запрос виден в инструментах разработчика?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.