#API событий
Статус: Планируется. Метод
POST /analytics/eventsпроектируется и пока не доступен. Страница фиксирует согласованный контракт; значения, помеченные «предварительно», уточнятся в OpenAPI 3.1. До выхода используйте JS-трекер и модули.
Назначение: принять пакет событий из ваших систем: сайт-SPA, мобильное приложение, сервер. Это четвёртый путь сбора аналитики.
#Содержание
- Когда использовать
- Доступ
- Запрос
- Типы событий
- Поля события
- Ответ
- Ошибки
- Дедупликация и время
- Примеры
- Рецепты
- Связь с диалогами чата и атрибуцией лидов
- Заметки
- Открытые вопросы
#Когда использовать
| Ситуация | Что выбрать |
|---|---|
| Одностраничное приложение (SPA) | этот метод; JS-трекер SPA не поддерживает |
| Мобильное приложение | этот метод через ваш сервер |
| Нужны события: заявка, регистрация, покупка | этот метод |
| Только посещения страниц на обычном сайте | JS-трекер проще |
| Оплаты и сделки в CRM, не связанные с браузером | офлайн-конверсии |
#Доступ
| Метод и путь | POST https://<домен ячейки>/api/public/v1/analytics/events |
| Аутентификация | Authorization: Bearer <ключ> (Аутентификация) |
| Область | analytics:write (Аутентификация); платных областей здесь нет |
| Платно | нет |
| Предел | 120 запросов в минуту на ключ и арендатора; в пакете не более 100 событий (Соглашения) |
| Персональные данные | не принимаются. Область analytics:write допускает только псевдонимные идентификаторы. Почта и телефон в properties отклоняются с validation_failed |
| Идемпотентность | заголовок Idempotency-Key (срок 24 часа, Соглашения) и поле event_id у каждого события |
| Ключ в браузере | нельзя. Ключ — серверный секрет; отправляйте события через свой сервер |
Адреса ячеек: RU https://formula-cream.pro, EN https://wfacademy.org, ID https://wfacademy.id. Ключ действует только в ячейке, где выпущен.
#Запрос
Заголовки
| Заголовок | Обязателен | Значение |
|---|---|---|
Authorization | да | Bearer <ключ> |
Content-Type | да | application/json |
Idempotency-Key | да | уникальная строка (обычно UUID v4); повтор с тем же ключом и телом возвращает прежний результат |
Тело
| Поле | Тип | Обязательно | Ограничения | Пример |
|---|---|---|---|---|
events | array | да | от 1 до 100 событий; тело ≤ 256 КБ | см. ниже |
Один пакет может смешивать типы событий.
{
"events": [
{
"event_id": "evt_2026_10_11_0001",
"type": "page_view",
"session_id": "sess_EXAMPLE_01",
"anonymous_id": "anon_EXAMPLE_01",
"page_key": "/blog/emulsions",
"title": "Как сделать эмульсию",
"referrer": "https://www.google.com/",
"utm": { "source": "google", "medium": "cpc", "campaign": "emulsion-course", "content": "banner-a", "term": "эмульсия курс" },
"occurred_at": "2026-10-11T08:15:30Z",
"consent": true
}
]
}
#Типы событий
type | Смысл | Особые поля |
|---|---|---|
page_view | просмотр страницы или экрана | page_key, title, referrer |
event | цель или действие (клик по кнопке, регистрация) | properties (имя цели — в properties.name, предварительно) |
lead_form | отправка формы заявки | properties без контактов |
conversion | действие с суммой (оплата, подписка) | value в формате денег |
#Поля события
| Поле | Тип | Обязательно | Ограничения | Пример |
|---|---|---|---|---|
event_id | string | да | уникален в пределах интеграции; служит ключом дедупликации | evt_2026_10_11_0001 |
type | string | да | page_view | event | lead_form | conversion | page_view |
session_id | string | да | идентификатор сессии на стороне клиента; длина уточнится | sess_EXAMPLE_01 |
anonymous_id | string | да | псевдоним посетителя; не почта и не телефон | anon_EXAMPLE_01 |
user_id | string | нет | ваш внутренний псевдонимный id; не контакт | user_EXAMPLE_42 |
utm | object | нет | source, medium, campaign, content, term — пять меток UTM | {"source":"google"} |
click_ids | object | нет | метки кликов рекламных систем, например gclid, yclid, fbclid; точный перечень уточнится | {"yclid":"123"} |
page_key | string | для page_view | путь без языкового префикса, например /blog/emulsions; главная — /home | /blog/emulsions |
title | string | нет | заголовок страницы | Как сделать эмульсию |
referrer | string | нет | адрес источника | https://www.google.com/ |
occurred_at | string | нет | ISO 8601 UTC; по умолчанию время приёма; не раньше, чем 7 дней назад (предварительно) | 2026-10-11T08:15:30Z |
consent | boolean | да | согласие посетителя на аналитику на момент события | true |
properties | object | нет | произвольные пары «ключ — скаляр»; без персональных данных | {"plan":"pro"} |
value | object | для conversion | {"amount":"4990.00","currency":"RUB"}; сумма строкой | {"amount":"4990.00","currency":"RUB"} |
События с consent: false принимаются, но не используются для пользовательской атрибуции (как именно, уточнится — см. открытые вопросы).
#Ответ
Успех — 202 Accepted: пакет принят, агрегация данных идёт после ответа. Это не асинхронная операция с объектом operation: итог приёма по каждому событию уже в теле ответа, в том же порядке, что и в запросе.
{
"accepted": 2,
"rejected": 1,
"results": [
{ "event_id": "evt_2026_10_11_0001", "status": "accepted" },
{ "event_id": "evt_2026_10_11_0002", "status": "duplicate" },
{
"event_id": "evt_2026_10_11_0003",
"status": "rejected",
"error": { "code": "validation_failed", "field": "/events/2/properties/email", "message": "Контактные данные не принимаются" }
}
]
}
| Поле | Тип | Описание |
|---|---|---|
accepted | integer | количество принятых событий (дубликаты не считаются) |
rejected | integer | количество отклонённых |
results[].status | string | accepted, duplicate, rejected |
results[].error | object | для отклонённых: code, field (JSON Pointer), message — тот же вид, что у элемента errors[] в Соглашениях |
Если невалидно сам пакет (не JSON, нет events, больше 100 событий), возвращается ошибка всего запроса, и ни одно событие не принимается.
#Ошибки
Формат — application/problem+json; общий каталог кодов в Соглашениях API.
| HTTP | Код | Когда |
|---|---|---|
| 401 | invalid_api_key | ключ неверен, отозван или истёк |
| 403 | scope_missing | у ключа нет analytics:write |
| 403 | ip_not_allowed | запрос с адреса вне списка IP интеграции |
| 403 | feature_not_in_plan | лицензия не содержит api_access |
| 409 | idempotency_conflict | тот же Idempotency-Key с другим телом |
| 413 | payload_too_large | тело больше 256 КБ |
| 422 | validation_failed | пакет не прошёл проверку: не JSON, нет events, больше 100 событий (список полей — в errors) |
| 429 | rate_limited | превышен предел частоты |
| 503 | service_unavailable | хранилище пределов временно недоступно |
#Дедупликация и время
- Событие определяется парой «интеграция +
event_id». Повторная отправка того жеevent_idдаётstatus: "duplicate"и не меняет счётчики. Это позволяет безопасно повторять пакет после сетевой ошибки. occurred_atпринимается не старше 7 дней назад (предварительно) и не из будущего более чем на несколько минут. События старше окна отклоняются сvalidation_failed. Для исторической загрузки используется отдельный механизм, когда он появится.- Если
occurred_atне передан, берётся время приёма.
#Примеры
#curl
curl -X POST 'https://formula-cream.pro/api/public/v1/analytics/events' \
-H 'Authorization: Bearer sap_ru_live_EXAMPLE0KEYI_EXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLE' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: batch-2026-10-11-0001' \
-d '{
"events": [
{
"event_id": "evt_2026_10_11_0001",
"type": "event",
"session_id": "sess_EXAMPLE_01",
"anonymous_id": "anon_EXAMPLE_01",
"page_key": "/pricing",
"properties": { "name": "signup_clicked" },
"occurred_at": "2026-10-11T08:15:30Z",
"consent": true
}
]
}'
#JavaScript (на вашем сервере)
// Node.js 18+. Ключ хранится в переменной окружения сервера, не в браузере.
const response = await fetch('https://wfacademy.org/api/public/v1/analytics/events', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
events: [{
event_id: crypto.randomUUID(),
type: 'conversion',
session_id: 'sess_EXAMPLE_01',
anonymous_id: 'anon_EXAMPLE_01',
value: { amount: '49.00', currency: 'GBP' },
consent: true,
}],
}),
});
if (response.status !== 202) throw new Error(`API: ${response.status}`);
console.log(await response.json());
#Python
import os, uuid, requests
response = requests.post(
"https://wfacademy.id/api/public/v1/analytics/events",
headers={
"Authorization": f"Bearer {os.environ['API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={"events": [{
"event_id": str(uuid.uuid4()),
"type": "page_view",
"session_id": "sess_EXAMPLE_01",
"anonymous_id": "anon_EXAMPLE_01",
"page_key": "/home",
"consent": True,
}]},
timeout=5,
)
response.raise_for_status()
#PHP
<?php
$payload = json_encode(['events' => [[
'event_id' => bin2hex(random_bytes(16)),
'type' => 'lead_form',
'session_id' => 'sess_EXAMPLE_01',
'anonymous_id' => 'anon_EXAMPLE_01',
'properties' => ['form' => 'callback'],
'consent' => true,
]]], JSON_UNESCAPED_UNICODE);
$ch = curl_init('https://formula-cream.pro/api/public/v1/analytics/events');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
]);
$body = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
#Рецепты
#Одностраничное приложение
- В браузере при загрузке создайте
anonymous_id(случайная строка) и сохраняйте его на стороне клиента; на каждые 30 минут неактивности заводите новыйsession_id. - При каждой смене маршрута отправляйте событие
page_viewна ваш серверный адрес (например,/analytics), а не напрямую в Формула ИИ. - Ваш сервер добавляет
event_id, проверяетconsentи пересылает пакет в Формула ИИ с ключом. - UTM читайте один раз при первой загрузке и передавайте во всех событиях сессии.
Пересылайте события пакетами до 100 штук раз в несколько секунд — это дешевле, чем по одному.
#Мобильное приложение
- Приложение хранит
anonymous_idв защищённом хранилище и ведёт сессии по правилам платформы. - Экраны —
page_viewсpage_keyвида/screen/checkout. - Оффлайн-очередь на устройстве допускается до 7 дней (окно
occurred_at);event_idделает повторную отправку безопасной. - События уходят на ваш бэкенд, который пересылает их в Формула ИИ. Ключ в приложение не встраивайте.
#Связь с диалогами чата и атрибуцией лидов
Источник лида Формула ИИ определяет по сессии. Когда посетитель пишет в чат сайта, сессия связывается с диалогом, и в карточке лида сохраняется атрибуция: источник, UTM, страница входа, число визитов и просмотров страниц, основа расчёта (basis: funnel, form или none).
Планируется передавать session_id из API событий в виджет, чтобы события из ваших систем вошли в историю визита и в атрибуцию лида (basis: funnel). Сейчас связь строится по сессии JS-трекера; для событий API она ещё не определена (см. открытые вопросы).
#Заметки
- Предел записи — 120 запросов в минуту на ключ и арендатора; заголовки
RateLimit-*и повторы — в Соглашениях API. Приём событий не списывает средства: деньги с кошелька не списываются. - События не содержат и не возвращают персональные данные; чтение результатов — только агрегатами (Чтение аналитики).
- Первые версии принимают только перечисленные типы событий.
#Открытые вопросы
- К владельцу. Окно времени. «Не старше 7 дней» взято из задания на документацию; проектные документы его не фиксируют.
- К владельцу. Семантика
consent: false: отбрасывать событие или хранить без идентификаторов. - Техническое. Формат
event_id, длинаsession_id, переченьclick_ids, имя цели в типеeventне зафиксированы. - Техническое. Связь
session_idс диалогом виджета. По проекту планируется «sidв iframe → атрибуцияbasis: funnel», но контракт не определён. - Техническое. Формат ответа (
accepted/rejected/results) — предварительный. - Техническое. Место в этапах: запись событий нигде не отнесена к конкретному этапу выпуска.