Первое событие
Самый короткий рабочий путь до первого события: один вызов из браузера, один 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, а не рядом с ним.
Что дальше
- Проверьте, что событие дошло — пять проверок по порядку.
- Разберите экран проверки интеграции — живой поток, нарушения и наблюдавшаяся форма данных.
- Опишите событие в реестре, чтобы имена и типы свойств не расползлись между командами.