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

Первое событие

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

Кому
Разработчику, который отправляет событие впервые
Проверено
На этой странице

Здесь только самое короткое, что доводит до ответа 202 Accepted: минимальный вызов из браузера, минимальный запрос с бэкенда и разбор того, что именно означает успешный ответ. Полные инструкции с фиксацией версии, проверкой целостности, повторами и лимитами — на страницах Browser script и Server HTTP.

Понадобится ключ проекта: Настройки → Установка. Браузерный токен pp_bt_… — для кода на странице, серверный ключ pp_sk_… — только для бэкенда.

Из браузера

Один тег в разметку страницы:

<script
  src="https://YOUR_ACTIONPULSE_HOST/v1/t.js"
  data-token="YOUR_BROWSER_TOKEN"
  data-consent="required"
></script>

Дальше — в консоли браузера на той же странице:

pp.setConsent('granted');
pp.track('signup_completed', { plan: 'pro' });
pp.flush();

Три строки нужны именно в этом порядке:

  • setConsent('granted') — с атрибутом data-consent="required" сбор до решения посетителя выключен полностью. pp.track() в этом состоянии молча ничего не делает: события не буферизуются и не отправляются позже.
  • track(...) — кладёт событие в очередь отправки, а не отправляет сразу. Очередь уходит на сервер при накоплении 10 событий или через 5 секунд.
  • flush() — отправляет очередь немедленно, чтобы не ждать таймер. В обычной работе вызывать его не нужно.

Полный production-вариант тега — с версионным адресом и integrity — на странице Browser script.

С бэкенда

Минимально достаточное тело — массив batch с одним элементом, у которого есть имя события и хотя бы один идентификатор:

{ "batch": [{ "event": "order_created", "user_id": "YOUR_USER_ID" }] }

Всё остальное сервер подставит сам: event_id сгенерирует, время события возьмёт равным времени приёма. Массив batch обязателен даже для одного события; пустой массив — ошибка 422.

curl

test -n "$ACTIONPULSE_SERVER_KEY" || { echo "ACTIONPULSE_SERVER_KEY is required" >&2; exit 1; }

curl -i -X POST https://YOUR_ACTIONPULSE_HOST/v1/track \
  -H "Authorization: Bearer $ACTIONPULSE_SERVER_KEY" \
  -H "Content-Type: application/json" \
  -d '{"batch":[{"event":"order_created","user_id":"YOUR_USER_ID"}]}'

Node.js

const serverKey = process.env.ACTIONPULSE_SERVER_KEY;
if (!serverKey) throw new Error('ACTIONPULSE_SERVER_KEY is required');

const response = await fetch('https://YOUR_ACTIONPULSE_HOST/v1/track', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${serverKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    batch: [{ event: 'order_created', user_id: 'YOUR_USER_ID' }],
  }),
});

console.log(response.status, await response.json());

Go

serverKey := os.Getenv("ACTIONPULSE_SERVER_KEY")
if serverKey == "" {
    log.Fatal("ACTIONPULSE_SERVER_KEY is required")
}

req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://YOUR_ACTIONPULSE_HOST/v1/track",
    strings.NewReader(`{"batch":[{"event":"order_created","user_id":"YOUR_USER_ID"}]}`))
if err != nil {
    return err
}
req.Header.Set("Authorization", "Bearer "+serverKey)
req.Header.Set("Content-Type", "application/json")

resp, err := http.DefaultClient.Do(req)
if err != nil {
    return err
}
defer resp.Body.Close()

fmt.Println(resp.Status)

Python

import json, os, urllib.request

payload = json.dumps({"batch": [{"event": "order_created", "user_id": "YOUR_USER_ID"}]})
request = urllib.request.Request(
    "https://YOUR_ACTIONPULSE_HOST/v1/track",
    data=payload.encode(),
    headers={
        "Authorization": f"Bearer {os.environ['ACTIONPULSE_SERVER_KEY']}",
        "Content-Type": "application/json",
    },
)

with urllib.request.urlopen(request, timeout=5) as response:
    print(response.status, json.load(response))

Примеры используют только стандартную библиотеку языка: отдельных серверных пакетов ActionPulse во внешних реестрах нет, и клиент здесь — это один POST.

Что означает ответ 202

Успех — ровно 202 Accepted, никогда не 200. Ответ приходит после того, как событие подтверждено очередью приёма, то есть переживёт перезапуск сервиса.

{ "accepted": 1, "n": 1, "rejected": [] }
Поле Что значит
n сколько элементов вы прислали
accepted сколько из них принято
rejected отказы по элементам: индекс в присланном батче и код причины

rejected присутствует всегда — при полном успехе это пустой массив.

Разбор кодов отказа по элементам — в дереве диагностики, полный перечень полей и лимитов — в Server HTTP и в справочнике приёма.

Принято — это ещё не «видно в отчёте»

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

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

Часть батча могла быть отклонена. Смотрите rejected: типовой случай на первой попытке — credential_event_forbidden, когда имя события не разрешено этому ключу.

У экранов свои окна. Список событий по умолчанию показывает последние 30 дней и отбирает строки по времени приёма. Карточка DAU на «Обзоре» считает последний полный день, то есть вчерашний, — сегодняшнее событие её не двигает. Карточка «События сегодня» считает текущий день в часовом поясе проекта.

Время события задаёте вы. Поле time вне окна ±48 часов молча заменяется временем приёма. Внутри окна оно сохраняется как есть, и тогда событие встанет в отчётах на свою дату, а не на сегодняшнюю: отчёты считают по времени события, список событий отбирает по времени приёма.

Строгая проверка реестра исключает событие из отчётов. В строгом режиме элемент, нарушивший контракт, возвращается в rejected с кодом schema_violation и сохраняется без свойств и идентификаторов — он виден на экране проверки интеграции, но в отчёты не попадает.

Пошаговый чеклист, который разводит эти случаи, — на странице Проверьте, что событие дошло.

Какое имя события выбрать

Откуда отправляете Что взять Почему
Браузер signup_completed только что выпущенный браузерный токен разрешает ровно одно пользовательское имя; остальные вернутся с credential_event_forbidden
Бэкенд настоящее имя вашего процесса, например order_created серверный ключ принимает любое имя по правилу ^[a-z0-9_.]{1,128}$, кроме пространства pp.*

Встроенные события pp.* — просмотры страниц, клики, прокрутка — браузер отправляет сам и списком имён не ограничен. Серверному ключу пространство pp.* недоступно вообще.

Почему не «test»

Соблазн отправить test понятен, но проверочное имя остаётся в проекте на весь срок хранения и мешает как раз тогда, когда разметка начинает расти.

Что произойдёт Почему это мешает
Из браузера test не пройдёт его нет в списке разрешённых имён токена — ответ будет 202 с credential_event_forbidden, и легко решить, что «сломалась интеграция»
Событие попадёт в нарушения проекты по умолчанию работают в режиме мониторинга: незарегистрированное имя даёт нарушение event_unregistered, и ваша проверка становится неотличимой от настоящей проблемы
Событие нельзя удалить по одному в продукте нет удаления отдельного события: есть только удаление данных субъекта по user_id, см. приватность
Имя нельзя переименовать event_name в реестре неизменяем: придётся регистрировать новое имя и помечать старое устаревшим
Оно расходует лимит принятые события считаются в месячный лимит тарифа и хранятся весь срок хранения

Практичный компромисс: отправляйте имя, которое действительно будет в вашей разметке, а «проверочность» кладите в идентификатор — например "user_id": "qa-checklist". Такие события легко отфильтровать на экране событий, и при необходимости их можно удалить процедурой удаления данных субъекта.

Если ответ не 202

Код Что проверить в первую очередь
запроса нет вовсе согласие не выдано или скрипт не инициализировался — событие не отправляется
401 ключ обрезан при копировании, отозван или истёк — 401
403 origin не в списке токена либо ключ не того типа — 403
402 приём остановлен по биллингу, а не технически
413, 422 тело больше 256 КБ, пустой batch или лишнее поле в JSON
429 превышен лимит запросов, смотрите заголовок Retry-After

Разбор строгий: неизвестное поле в любом месте структуры отклоняет весь запрос с 422, а не игнорируется. Свои данные кладите в props, а не рядом с ним.

Что дальше

  1. Проверьте, что событие дошло — пять проверок по порядку.
  2. Разберите экран проверки интеграции — живой поток, нарушения и наблюдавшаяся форма данных.
  3. Опишите событие в реестре, чтобы имена и типы свойств не расползлись между командами.