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

Отправка событий с бэкенда

Прямой HTTP-контракт ActionPulse — заголовок авторизации, формат батча, ответ 202, коды отказов, повторы и лимиты. Рабочие примеры на curl, Node.js, Go и Python.

Кому
Бэкенд-разработчикам
Проверено
На этой странице

Прямой HTTP-запрос — рабочий и полностью доступный способ отправлять события с бэкенда. Отдельные библиотеки для Node.js, Go и Python в репозитории продукта существуют, но во внешние реестры пакетов не опубликованы, поэтому примеры ниже используют обычный HTTP. Клиент на любом языке — это один POST.

Запрос

POST https://YOUR_ACTIONPULSE_HOST/v1/track
Authorization: Bearer $ACTIONPULSE_SERVER_KEY
Content-Type: application/json

Тело — объект с массивом batch, даже если событие одно:

{
  "batch": [
    {
      "event_id": "8b0d3f2e-1c4a-4f0e-9b1d-2a3c4d5e6f70",
      "schema_version": 1,
      "event": "order_created",
      "user_id": "YOUR_USER_ID",
      "time": "2026-07-27T12:00:00Z",
      "props": { "plan": "pro", "total": 990 }
    }
  ]
}

Поля события

Поле Обязательно Что важно знать
event да ^[a-z0-9_.]{1,128}$ — строчные латинские буквы, цифры, _ и .
user_id / anon_id нужен хотя бы один до 256 символов, без нулевого байта
event_id нет, но нужен UUID; без него повторная отправка создаст второе событие
time нет RFC 3339; вне окна ±48 ч заменяется временем приёма
schema_version нет целое ≥ 0; 0 означает текущую версию из реестра
props нет до 64 ключей, ключ до 64 символов, значение до 8 КБ
session_id нет обрезается до 256 символов
url, referrer нет обрезаются до 2048 символов

Ответ

Успех — 202 Accepted:

{ "accepted": 1, "n": 1, "rejected": [] }

n — сколько элементов вы прислали, accepted — сколько принято, rejected — список отказов с индексом элемента в исходном батче:

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

Основные коды отказа отдельных элементов:

Код Причина
invalid_event имя не подходит под ^[a-z0-9_.]{1,128}$
missing_actor нет ни user_id, ни anon_id
invalid_actor идентификатор слишком длинный или содержит нулевой байт
invalid_event_id event_id не разбирается как UUID
too_many_props больше 64 свойств
prop_key_too_long имя свойства длиннее 64 символов
prop_value_too_large значение свойства больше 8 КБ
credential_event_forbidden ключ не имеет права отправлять это имя события
schema_violation событие нарушает контракт из реестра в строгом режиме

Примеры

curl

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

curl -X POST https://YOUR_ACTIONPULSE_HOST/v1/track \
  -H "Authorization: Bearer $ACTIONPULSE_SERVER_KEY" \
  -H "Content-Type: application/json" \
  -d '{"batch":[{
    "event_id":"8b0d3f2e-1c4a-4f0e-9b1d-2a3c4d5e6f70",
    "schema_version":1,
    "event":"order_created",
    "user_id":"YOUR_USER_ID",
    "time":"2026-07-27T12:00:00Z",
    "props":{"plan":"pro","total":990}
  }]}'

Node.js

import { randomUUID } from 'node:crypto';

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_id: randomUUID(),
        schema_version: 1,
        event: 'order_created',
        user_id: 'YOUR_USER_ID',
        time: new Date().toISOString(),
        props: { plan: 'pro', total: 990 },
      },
    ],
  }),
});

if (response.status !== 202) {
  throw new Error(`track failed: ${response.status}`);
}

const result = await response.json();
if (result.rejected.length > 0) {
  console.error('events rejected', result.rejected);
}

Go

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

body, err := json.Marshal(map[string]any{"batch": []map[string]any{{
    "event_id":       uuid.NewString(),
    "schema_version": 1,
    "event":          "order_created",
    "user_id":        "YOUR_USER_ID",
    "time":           time.Now().UTC().Format(time.RFC3339Nano),
    "props":          map[string]any{"plan": "pro", "total": 990},
}}})
if err != nil {
    return err
}

req, err := http.NewRequestWithContext(ctx, http.MethodPost,
    "https://YOUR_ACTIONPULSE_HOST/v1/track", bytes.NewReader(body))
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()

if resp.StatusCode != http.StatusAccepted {
    return fmt.Errorf("track failed: %s", resp.Status)
}

Python

import datetime, os, uuid, requests

server_key = os.environ["ACTIONPULSE_SERVER_KEY"]

response = requests.post(
    "https://YOUR_ACTIONPULSE_HOST/v1/track",
    json={"batch": [{
        "event_id": str(uuid.uuid4()),
        "schema_version": 1,
        "event": "order_created",
        "user_id": "YOUR_USER_ID",
        "time": datetime.datetime.now(datetime.UTC).isoformat(),
        "props": {"plan": "pro", "total": 990},
    }]},
    headers={"Authorization": f"Bearer {server_key}"},
    timeout=5,
)
response.raise_for_status()

for item in response.json()["rejected"]:
    print("rejected", item)

Батчи и лимиты

Ограничение Значение
Элементов в одном запросе до 500
Размер тела запроса до 256 КБ

Превышение количества элементов — 422, превышение размера тела — 413. Пустой массив batch тоже 422.

Повторы

Повторяйте запрос с тем же телом при ошибке транспорта, 429 и 5xx, используя экспоненциальную задержку. При 429 учитывайте заголовок Retry-After — он приходит в секундах.

Ответы 401, 402, 403, 413 и 422 окончательные: повтор их не исправит.

Ошибки уровня запроса

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

Все ошибки приходят в одном формате:

{ "error": { "code": "validation_failed", "message": "batch is empty", "details": {} } }

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

Всё, чему нужно доверять: подтверждённые оплаты, созданные и оплаченные заказы, возвраты, регистрации, старт и конверсия подписки, выдача доступа.

Часть имён вообще принимается только с серверным ключом — registered, subscription_started, trial_started, trial_activated, trial_converted, trial_expired, payment_succeeded, payment_attached, order_created, first_order_created, order_paid, invoice_paid, refund. Попытка отправить такое имя браузерным токеном вернёт credential_event_forbidden.

Обратное тоже верно: имена из пространства pp.* серверным ключом отправить нельзя — их формирует браузерный сбор.