Справочник Ingest API
Ingest API — это точка входа для всей телеметрии, которую ваши приложения передают в TrackLane: метрик производительности, событий, логов и спанов трассировки. Данные принимаются граничными узлами (Edge PoP), ближайшими к клиенту, и асинхронно доставляются в Analytics Engine.
Если вы используете официальный SDK (@tracklane/sdk для JavaScript, tracklane-python, tracklane-go), формирование пакетов, сжатие, подпись и повторная доставка выполняются автоматически. Этот справочник описывает протокол на уровне HTTP — он понадобится, если вы пишете собственный клиент, отправляете данные с встраиваемых устройств или проксируете телеметрию через свой бэкенд.
Быстрый старт
Создайте проект в консоли, скопируйте ingest-ключ из раздела Настройки → Ключи доступа и установите SDK:
npm install @tracklane/sdk
Инициализируйте клиент как можно раньше — до загрузки остального кода приложения, чтобы SDK успел зарегистрировать наблюдатели Performance API:
import { TrackLane } from '@tracklane/sdk';
export const tl = TrackLane.init({
publishableKey: 'tl_pub_8Kq2vN4xR7mT1pLz', // публичный ключ проекта
signingKeyEndpoint: '/api/telemetry/key', // выдаёт эфемерный ключ подписи
service: 'storefront-web',
environment: 'production',
release: '2026.10.3',
batch: { maxEvents: 500, maxDelayMs: 5000 },
});
Аутентификация
Каждый запрос к Ingest API аутентифицируется ключом проекта в заголовке Authorization. TrackLane использует два типа ключей:
tl_pub_…— публичный ключ. Безопасно встраивается в браузерные и мобильные приложения. Разрешает только запись в конкретный проект и требует подписи пакета эфемерным ключом.tl_sk_…— секретный ключ. Используется только на серверах. Позволяет подписывать пакеты напрямую и выпускать эфемерные ключи подписи для клиентов черезPOST /v1/signing-keys.
Эфемерный ключ подписи действует 15 минут и привязан к session ID. Ваш бэкенд запрашивает его у TrackLane и передаёт клиенту — так секретный ключ никогда не покидает сервер.
tl_sk_… в клиентском коде или в публичных репозиториях. Скомпрометированный ключ отзывается в консоли за одну секунду, и новые запросы с ним будут отклоняться кодом 401.
Протокол TLP/2
TrackLane Lane Protocol — это асинхронный протокол передачи пакетов поверх HTTPS. Клиент накапливает события в буфере, формирует пакет (batch) и отправляет его одним запросом. Граничный узел проверяет подпись, записывает пакет в локальный журнал упреждающей записи (WAL) и сразу отвечает 202 Accepted. Валидация отдельных событий, обогащение и агрегация выполняются уже после ответа.
- 01buffer≤ 500 событий / 5 с
- 02hash + signSHA-256, HMAC
- 03POST /v1/ingestzstd, HTTP/3
- 04202 Accepted≈ 9 мс на Edge
Ключевые свойства протокола:
- Неблокирующая доставка. Ответ не зависит от состояния аналитического ядра: даже при его недоступности узел принимает данные и хранит журнал до 72 часов.
- Упорядоченность внутри сессии. Пакеты одной сессии нумеруются полем
session.seq; пропуски обнаруживаются на стороне сервера по цепочке хэшей. - Идемпотентность. Повторная отправка пакета с тем же
batch_idв течение 24 часов не приводит к дублированию данных. - Транспорт. HTTP/1.1, HTTP/2 и HTTP/3 (QUIC). Для фоновой отправки при закрытии вкладки поддерживается
navigator.sendBeacon(до 64 КБ).
POST/v1/ingest
Принимает один пакет телеметрии. Тело запроса — JSON-объект, опционально сжатый zstd или gzip. Максимальный размер после распаковки зависит от тарифа: 256 КБ, 1 МБ или 5 МБ.
Заголовки запроса
| Заголовок | Обязателен | Описание |
|---|---|---|
| Authorization | да | Bearer tl_pub_… или Bearer tl_sk_… |
| Content-Type | да | application/json |
| Content-Encoding | нет | zstd, gzip или identity |
| X-TL-Session | да | Идентификатор сессии; должен совпадать с session.id в теле |
| X-TL-Signature | да | t=<unix>,v1=<hmac_hex> — см. подпись пакета |
| X-TL-Protocol | нет | Версия протокола, по умолчанию 2 |
Формат пакета
Пакет состоит из четырёх блоков: метаданных пакета, контекста ресурса (сервис, релиз, окружение), сессии и массива событий. Блок integrity содержит хэш массива событий и хэш предыдущего пакета сессии — это позволяет серверу обнаружить искажение данных и пропущенные пакеты.
{
"protocol": 2,
"batch_id": "bat_01JA8ZK4T3M9Q2XW7RVN5HCD6E",
"sent_at": "2026-10-15T09:41:27.384Z",
"resource": {
"service": "storefront-web",
"environment": "production",
"release": "2026.10.3",
"sdk": { "name": "@tracklane/sdk", "version": "4.12.0" }
},
"session": {
"id": "ses_01JA8YQ7C5W2B8N4K6TXMZ3RFP",
"started_at": "2026-10-15T09:32:04.112Z",
"seq": 14,
"page_views": 0,
"device": { "type": "desktop", "os": "macOS 15.1", "browser": "Chrome 130" },
"geo_hint": "DE"
},
"events": [
{
"type": "metric",
"name": "web.lcp",
"ts": "2026-10-15T09:41:22.051Z",
"value": 1184.6,
"unit": "ms",
"attrs": { "route": "/catalog/:slug", "element": "img.hero", "connection": "4g" }
},
{
"type": "metric",
"name": "web.inp",
"ts": "2026-10-15T09:41:24.907Z",
"value": 96,
"unit": "ms",
"attrs": { "route": "/catalog/:slug", "interaction": "click", "target": "button.add-to-cart" }
},
{
"type": "span",
"name": "POST /api/cart/items",
"ts": "2026-10-15T09:41:25.002Z",
"duration_ms": 212.4,
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"span_id": "00f067aa0ba902b7",
"status": "ok",
"attrs": { "http.status_code": 201, "http.response_size": 1840 }
}
],
"integrity": {
"alg": "sha256",
"canonicalization": "jcs",
"events_count": 3,
"events_hash": "8c1f0e6b42d7a95e3f60c2b18d4a7e19f53b0c86d2e17a4f9b3c05e81d6a2f74",
"prev_batch_hash": "e3b4c2a17f9d05864b1ac7e02d93f58a6c41b70e2d895fa3c16e07b24d9a8c51"
}
}
Поля верхнего уровня
| Поле | Тип | Описание |
|---|---|---|
| batch_id | string | Уникальный идентификатор пакета: префикс bat_ + ULID. Ключ идемпотентности. |
| sent_at | RFC 3339 | Время отправки по часам клиента. Используется для коррекции дрейфа часов событий. |
| resource | object | Сервис, окружение и релиз. Применяется ко всем событиям пакета. |
| session | object | Контекст сессии, см. идентификаторы сессий. |
| events | array | От 1 до 5 000 событий. Порядок внутри массива не важен. |
| integrity | object | Хэши для проверки целостности, см. подпись. |
Типы событий
Поле type определяет схему события и полосу, в которую оно будет направлено на граничном узле.
Числовое измерение: name, value, unit. Сворачивается в DDSketch-гистограммы с окном 10 секунд.
Участок трассировки в формате W3C Trace Context: trace_id, span_id, duration_ms.
Структурированная запись: level, message и произвольные атрибуты до 64 КБ.
Исключение со стеком: error.type, error.message, stack. Группируется по отпечатку.
Имена метрик должны соответствовать шаблону ^[a-z][a-z0-9_.]{1,127}$. Атрибуты — плоский объект, не более 64 ключей на событие; значения — строки до 1 024 символов, числа или логические значения.
Идентификаторы сессий
TrackLane использует бесстраничный учёт сессий: сессия не привязана к загрузке страницы и не хранится в cookie. Идентификатор генерируется на клиенте и живёт в памяти приложения (или в sessionStorage для восстановления после перезагрузки вкладки).
- Формат: префикс
ses_и 26 символов ULID в алфавите Crockford Base32. Первые 10 символов кодируют время создания в миллисекундах — это позволяет сортировать сессии без отдельного индекса. - Продление: любая активность пользователя — ввод, сетевой запрос, переход в SPA, возврат вкладки в видимое состояние — продлевает сессию.
- Ротация: новый ID выпускается после 30 минут неактивности (настраивается
sessionTimeoutMs) или через 24 часа непрерывной работы. - Нумерация: поле
session.seqмонотонно растёт с каждым пакетом, начиная с0. При ротации счётчик сбрасывается.
import { ulid } from 'ulid';
const TIMEOUT_MS = 30 * 60 * 1000;
let session = { id: `ses_${ulid()}`, startedAt: Date.now(), lastActive: Date.now(), seq: 0 };
export function currentSession() {
const now = Date.now();
if (now - session.lastActive > TIMEOUT_MS) {
session = { id: `ses_${ulid()}`, startedAt: now, lastActive: now, seq: 0 };
}
session.lastActive = now;
return session;
}
export function nextSeq() {
return currentSession().seq++;
}
Целостность и подпись пакетов
Каждый пакет защищён двумя механизмами: хэшем содержимого, который гарантирует, что события не были изменены при передаче или в промежуточных прокси, и HMAC-подписью, которая подтверждает, что пакет сформирован владельцем ключа.
Алгоритм
- Сериализуйте массив
eventsв каноническую форму JSON по RFC 8785 (JCS): ключи отсортированы, без пробелов, числа в кратчайшем представлении. - Вычислите
events_hash = hex(SHA-256(canonical_events))и запишите его вintegrity.events_hash. - В
integrity.prev_batch_hashпередайтеevents_hashпредыдущего пакета этой сессии. Для первого пакета (seq = 0) — 64 нуля. - Сформируйте строку подписи:
{t}.{batch_id}.{session.id}.{events_hash}, гдеt— текущее Unix-время в секундах. - Вычислите
HMAC-SHA256(signing_key, строка)и передайте заголовокX-TL-Signature: t={t},v1={hex}.
Граничный узел отклоняет пакеты, у которых t отличается от серверного времени более чем на 300 секунд, — это защищает от повторного воспроизведения перехваченных запросов.
// Подпись и отправка пакета (браузер или Node.js 18+, Web Crypto API)
import canonicalize from 'canonicalize'; // RFC 8785
const enc = new TextEncoder();
const hex = (buf) => [...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, '0')).join('');
async function sha256Hex(text) {
return hex(await crypto.subtle.digest('SHA-256', enc.encode(text)));
}
async function hmacHex(keyHex, message) {
const raw = Uint8Array.from(keyHex.match(/../g), (h) => parseInt(h, 16));
const key = await crypto.subtle.importKey('raw', raw, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
return hex(await crypto.subtle.sign('HMAC', key, enc.encode(message)));
}
export async function sendBatch({ batch, signingKey, publishableKey, prevHash }) {
const eventsHash = await sha256Hex(canonicalize(batch.events));
batch.integrity = {
alg: 'sha256',
canonicalization: 'jcs',
events_count: batch.events.length,
events_hash: eventsHash,
prev_batch_hash: prevHash ?? '0'.repeat(64),
};
const t = Math.floor(Date.now() / 1000);
const signature = await hmacHex(signingKey, `${t}.${batch.batch_id}.${batch.session.id}.${eventsHash}`);
const res = await fetch('https://in.tracklane.space/v1/ingest', {
method: 'POST',
keepalive: true,
headers: {
Authorization: `Bearer ${publishableKey}`,
'Content-Type': 'application/json',
'X-TL-Session': batch.session.id,
'X-TL-Signature': `t=${t},v1=${signature}`,
},
body: JSON.stringify(batch),
});
if (res.status === 202) return eventsHash; // станет prev_batch_hash следующего пакета
if (res.status === 409) return eventsHash; // дубликат — пакет уже принят ранее
throw Object.assign(new Error(`ingest failed: ${res.status}`), { retryAfter: res.headers.get('Retry-After') });
}
# pip install tracklane-python httpx rfc8785
import hashlib, hmac, json, time
import httpx
import rfc8785
INGEST_URL = "https://in.tracklane.space/v1/ingest"
def send_batch(batch: dict, secret_key: str, signing_key_hex: str, prev_hash: str | None) -> str:
events_hash = hashlib.sha256(rfc8785.dumps(batch["events"])).hexdigest()
batch["integrity"] = {
"alg": "sha256",
"canonicalization": "jcs",
"events_count": len(batch["events"]),
"events_hash": events_hash,
"prev_batch_hash": prev_hash or "0" * 64,
}
t = int(time.time())
message = f"{t}.{batch['batch_id']}.{batch['session']['id']}.{events_hash}".encode()
signature = hmac.new(bytes.fromhex(signing_key_hex), message, hashlib.sha256).hexdigest()
resp = httpx.post(
INGEST_URL,
content=json.dumps(batch, separators=(",", ":")),
headers={
"Authorization": f"Bearer {secret_key}",
"Content-Type": "application/json",
"X-TL-Session": batch["session"]["id"],
"X-TL-Signature": f"t={t},v1={signature}",
},
timeout=5.0,
)
if resp.status_code in (202, 409):
return events_hash
resp.raise_for_status()
return events_hash
package telemetry
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"net/http"
"time"
"github.com/gowebpki/jcs"
)
func SignAndSend(client *http.Client, body []byte, events []byte, batchID, sessionID, apiKey string, signingKey []byte) error {
canonical, err := jcs.Transform(events) // RFC 8785
if err != nil {
return fmt.Errorf("canonicalize events: %w", err)
}
sum := sha256.Sum256(canonical)
eventsHash := hex.EncodeToString(sum[:])
t := time.Now().Unix()
mac := hmac.New(sha256.New, signingKey)
fmt.Fprintf(mac, "%d.%s.%s.%s", t, batchID, sessionID, eventsHash)
req, _ := http.NewRequest(http.MethodPost, "https://in.tracklane.space/v1/ingest", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-TL-Session", sessionID)
req.Header.Set("X-TL-Signature", fmt.Sprintf("t=%d,v1=%s", t, hex.EncodeToString(mac.Sum(nil))))
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusAccepted && resp.StatusCode != http.StatusConflict {
return fmt.Errorf("ingest: unexpected status %d", resp.StatusCode)
}
return nil
}
# Подпись вычислена заранее; тело сжато zstd
curl -X POST https://in.tracklane.space/v1/ingest \
-H "Authorization: Bearer $TRACKLANE_SECRET_KEY" \
-H "Content-Type: application/json" \
-H "Content-Encoding: zstd" \
-H "X-TL-Session: ses_01JA8YQ7C5W2B8N4K6TXMZ3RFP" \
-H "X-TL-Signature: t=1760521287,v1=5d41c2f8a0b3e967d18c4f02ab7e95c36d0f1a8b2e4c7d93f6a05b18c2e9d740" \
--data-binary @batch.json.zst
Повторная доставка и идемпотентность
Клиент обязан повторять отправку при сетевых ошибках, таймаутах и ответах 429 или 5xx. Пакет при повторе отправляется без изменений — с тем же batch_id и events_hash, но с новой подписью и новым t.
- Задержка: экспоненциальная с джиттером —
min(30 с, 0,5 с × 2n) × random(0,5…1). - Если сервер вернул заголовок
Retry-After, он имеет приоритет над расчётной задержкой. - Неотправленные пакеты SDK сохраняет в IndexedDB (браузер) или на диск (сервер) — до 50 МБ — и досылает после восстановления сети.
- Ответ
409 duplicate_batchозначает, что пакет уже был принят: удалите его из очереди и продолжайте.
{
"status": "accepted",
"batch_id": "bat_01JA8ZK4T3M9Q2XW7RVN5HCD6E",
"events_accepted": 3,
"events_rejected": 0,
"edge": "fra1-07",
"received_at": "2026-10-15T09:41:27.402Z",
"chain": { "expected_seq": 15, "gap_detected": false }
}
Коды ответов
Ошибки возвращаются в едином формате: {"error": {"code": "…", "message": "…", "request_id": "…"}}. Указывайте request_id при обращении в поддержку.
| HTTP | Код | Значение | Повторять |
|---|---|---|---|
| 202 | accepted | Пакет записан в журнал граничного узла | — |
| 400 | invalid_payload | Невалидный JSON или нарушена схема пакета | нет |
| 401 | invalid_key | Ключ не найден, отозван или не принадлежит проекту | нет |
| 403 | signature_mismatch | Подпись не совпала или t вне окна 300 с | после пересчёта |
| 409 | duplicate_batch | Пакет с этим batch_id уже принят | нет |
| 413 | payload_too_large | Размер превышает лимит тарифа — разделите пакет | после разделения |
| 422 | integrity_failed | events_hash не соответствует содержимому | нет |
| 429 | rate_limited | Превышен лимит запросов или месячный объём | да, Retry-After |
| 503 | edge_draining | Узел выводится из ротации; DNS переключит на соседний | да |
Лимиты
Лимиты применяются на уровне проекта. Текущее потребление возвращается в заголовках каждого ответа: X-RateLimit-Remaining, X-TL-Quota-Used-Bytes и X-TL-Quota-Limit-Bytes.
| Лимит | Разработчик | Про | Энтерпрайз |
|---|---|---|---|
| Объём в месяц | 10 ГБ | 500 ГБ + overage | от 10 ТБ |
| Запросов в секунду | 50 | 2 000 | по договору |
| Размер пакета | 256 КБ | 1 МБ | 5 МБ |
| Событий в пакете | 500 | 5 000 | 5 000 |
| Уникальных имён метрик | 200 | 10 000 | без ограничений |
История изменений
- 2026-09-30 · TLP/2
Добавлены поддержка HTTP/3, сжатие zstd и цепочка хэшей
prev_batch_hashдля обнаружения пропущенных пакетов. - 2026-06-12 · API v1.8
Эфемерные ключи подписи для браузерных клиентов, эндпоинт
POST /v1/signing-keys. - 2026-03-04 · API v1.6
Поддержка канонизации RFC 8785; устаревший режим
sorted-keysбудет отключён 1 марта 2027 года.
© 2026 TrackLane · Документация обновлена 15 октября 2026