К содержанию
ActionPulse

← Все статьи

обновлено
  • события
  • backend
  • frontend
Автор — Редакция ActionPulse

Серверные и клиентские события: что отправлять откуда

ActionPulse — матрица клиентских и серверных событий с проверкой идентификаторов

Клиентские события показывают взаимодействие с интерфейсом, серверные — подтверждённые операции системы. Клик по кнопке оплаты не равен полученным деньгам: браузер фиксирует намерение, а платёж подтверждает бэкенд по состоянию заказа или ответу платёжной системы.

Ниже — матрица источников, готовые синтетические сценарии и автономная проверка для Node.js. Материалы не содержат данных клиентов, действующих ключей или обещаний результата.

Скачать JSON со сценариями источников и повторной доставки · Скачать автономный проверяющий скрипт для Node.js

Матрица: какой источник подтверждает событие

У каждого факта должен быть основной источник истины. В ActionPulse источник определяется проверенным типом ключа, а не значением source в JSON или произвольным заголовком запроса.

Факт Событие Основной источник Что действительно означает
Человек начал оформление checkout_started браузер начало сценария в интерфейсе, но не успешная оплата
Форма регистрации заполнена signup_completed браузер пользователь дошёл до шага; аккаунт ещё должен подтвердить сервер
Аккаунт действительно создан registered сервер запись создана и подтверждена системой-источником
Заказ действительно оплачен order_paid сервер платёж подтверждён, а не просто инициирован
Операция оплаты завершена payment_succeeded сервер сервер получил подтверждённый результат операции
Средства возвращены refund сервер возврат подтверждён учётной или платёжной системой

Браузерные пользовательские события дополнительно должны входить в разрешённый список соответствующего браузерного ключа. Критичные имена registered, order_paid, payment_succeeded и refund браузер отправлять не может: элемент будет отклонён с кодом credential_event_forbidden.

Правила именования, полный словарь и версии схемы остаются в материале про разметку событий B2B SaaS. Здесь задача уже другая: определить, кто вправе подтвердить факт и как отличить повтор доставки от нового события.

Один сценарий: браузер начал, сервер подтвердил оплату

Возьмём полностью синтетический заказ. Браузер отправляет checkout_started и фиксирует путь пользователя. После подтверждения оплаты сервер создаёт отдельное событие order_paid.

{
  "credential_kind": "browser",
  "event": "checkout_started",
  "event_id": "11111111-1111-4111-8111-111111111111",
  "user_id": "demo-user-001",
  "session_id": "demo-session-001",
  "time": "2026-08-25T09:00:00Z",
  "props": {
    "business_event_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
    "order_id": "demo-order-001"
  }
}

Поле credential_kind в файле сценариев показывает, каким типом ключа выполнена отправка. В рабочем запросе это свойство нельзя использовать для назначения прав: ActionPulse определяет доверенный источник по проверенному ключу.

После успешной транзакции сервер отправляет подтверждённый факт:

{
  "credential_kind": "server",
  "event": "order_paid",
  "event_id": "22222222-2222-4222-8222-222222222222",
  "user_id": "demo-user-001",
  "session_id": "demo-session-001",
  "time": "2026-08-25T09:00:04Z",
  "props": {
    "business_event_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
    "order_id": "demo-order-001"
  }
}

Результат проверки: два транспортных идентификатора, два продуктовых факта, одна подтверждённая оплата. Общий business_event_id связывает одну операцию, но не склеивает checkout_started и order_paid: имена разные, поэтому смысл и шаги воронки остаются раздельными.

Значения event_id и корректного business_event_id должны быть UUID. Формат и варианты идентификаторов определены в RFC 9562: Universally Unique IDentifiers. Идентификаторы в примере придуманы для демонстрации; пользователь, заказ и сессия не относятся к клиенту ActionPulse.

Почему одинаковый event_id не заменяет business_event_id

На практике нужно различать два уровня идентичности.

Транспортная идентичность включает проект, подтверждённый класс отправителя и event_id:

transport = project_id + producer_class + event_id

Повтор того же серверного события с тем же event_id сохраняет одну транспортную идентичность. Браузер и сервер с одинаковым event_id образуют две разные транспортные идентичности: браузер не должен заранее занять идентификатор и подавить доверенный серверный факт.

Продуктовая идентичность включает проект, имя события и либо корректный business_event_id, либо запасной event_id:

logical = project_id + event_name + business_event_id
logical = project_id + event_name + event_id, если business_event_id отсутствует

Отсюда следуют четыре проверяемых случая:

  1. Один серверный источник, один event_id, две попытки доставки: один транспортный идентификатор и один продуктовый факт.
  2. Браузер и сервер, одинаковое имя и одинаковый event_id: два транспортных идентификатора и один продуктовый факт; при чтении канонической версии приоритет у сервера.
  3. Браузер и сервер, разные event_id, одинаковое имя и корректный общий business_event_id: два транспортных идентификатора и один продуктовый факт; приоритет снова у сервера.
  4. checkout_started и order_paid с общим business_event_id: два продуктовых факта, потому что шаг интерфейса и подтверждённая оплата отвечают на разные вопросы.

Если у одинакового факта разные event_id, а business_event_id отсутствует или не является UUID, канонического объединения не будет: получится два продуктовых факта вместо одного. Поэтому общий идентификатор бизнес-операции должен быть осознанной частью контракта, а не случайной строкой.

Повторная доставка: что действительно работает

Создавайте серверный event_id один раз в момент подтверждённого факта и используйте его во всех повторных попытках. Новый UUID при каждом retry превращает одну операцию в несколько самостоятельных событий.

Для ActionPulse /v1/track ключ дедупликации находится в теле события: это поле event_id. Заголовок Idempotency-Key не заменяет event_id для /v1/track. У отдельных операций, например отметки релиза из CI/CD, может быть собственный контракт с таким заголовком, но переносить его на приём аналитических событий нельзя.

Серверный ключ остаётся только на бэкенде. Не помещайте его в браузерный код, JSON-сценарий, URL, клиентские логи или текст ошибки. В скачиваемых материалах используются только названия классов browser и server, а не реальные учётные данные.

HTTP 202: почему нужно смотреть rejected

HTTP 202 Accepted не означает, что каждый элемент пакета принят. RFC 9110, раздел 15.3.3 отдельно объясняет, что обработка такого запроса может быть не завершена к моменту ответа.

Для ActionPulse статус нужно читать вместе с телом ответа. Если браузер отправил разрешённый checkout_started и запрещённый order_paid, ответ может выглядеть так:

{
  "accepted": 1,
  "n": 2,
  "rejected": [
    {
      "i": 1,
      "code": "credential_event_forbidden"
    }
  ]
}

Итог: HTTP 202, accepted: 1, rejected: 1. Проверка только HTTP-статуса скрывает потерянную оплату. Обработчик должен разбирать rejected, сохранять безопасный диагностический код и передавать подтверждённый платёж с сервера.

Воспроизводимая проверка на десяти синтетических сценариях

Скачайте оба файла в один каталог:

  1. JSON-сценарии источников, повторов и канонического выбора.
  2. Автономный проверяющий скрипт без зависимостей.

Запустите проверку локально:

node actionpulse-event-source-checker.mjs actionpulse-event-source-fixtures.json

Проверка использует только стандартные модули Node.js, сверяет три заранее вычисленных контрольных значения SHA-256, ничего не отправляет по сети и не обращается к действующему проекту. Ожидаемый результат:

PASS 10/10 synthetic event-source scenarios

В набор входят повтор с тем же UUID, одинаковый UUID у браузера и сервера, общий бизнес-идентификатор, два разных шага одной оплаты, отсутствие и неправильный формат business_event_id, новый UUID при retry, запрещённая браузерная оплата, подмена source и частично отклонённый пакет с HTTP 202.

Этот скрипт проверяет контракт идентичности и синтетические ожидания; он не заменяет проверку настоящей интеграции, браузерных разрешений, реестра событий, сетевой доставки или конфигурации конкретного проекта.

Что проверить после внедрения

  1. У каждого бизнес-факта есть основной источник; подтверждённая оплата приходит только с сервера.
  2. checkout_started и order_paid используют разные event_id, общий корректный business_event_id и одного user_id.
  3. Повторная отправка серверного факта сохраняет исходный UUID.
  4. Браузер не может назначить себе серверный источник через source или заголовок.
  5. Обработчик читает rejected, даже когда HTTP-статус равен 202.
  6. Два действия с разным смыслом не объединяются только потому, что относятся к одному заказу.
  7. В событиях нет токенов, паролей, текста платёжной формы и лишних персональных данных.

Последовательность действий проверяйте в истории пользователя, идентификацию между браузером и сервером — в материале про идентификацию пользователей, а контроль пропусков и дублей — в статье о качестве данных продуктовой аналитики.

Итог

Браузер описывает интерфейсный путь, сервер подтверждает бизнес-результат. Назначьте основной источник, сохраните стабильный event_id для каждого повтора, связывайте этапы корректным business_event_id и проверяйте элементы rejected. Тогда воронка показывает отдельные шаги пользователя и ровно один подтверждённый факт там, где он действительно произошёл.

Продолжить разбор

Связанные материалы

Все статьи →

Проверьте это на своих данных

Пробный период Growth на 14 дней: отчёты и SQL-консоль, карта не нужна. До 30 млн событий за 14 дней. Выгрузка событий через API недоступна во время пробного периода; после оплаты доступна с тарифа Growth.

Попробовать 14 дней бесплатно