Подписка

Подписка — это один долгоживущий GET, который потоково отдаёт пакеты как server-sent events. Эта страница подробно описывает формат передачи, контракт keepalive, возобновление по курсору и операционные реалии соединений, остающихся открытыми часами.

Запрос

Отправьте Accept: text/event-stream. Именно этот заголовок превращает обычный метаданный GET в подписку.

GET /v1/streams/{id}
curl -N "https://runnev.dev/v1/streams/$STREAM?cursor=head" \
  -H "Accept: text/event-stream" \
  -H "Authorization: Bearer $RUNNEV_API_KEY"

Заголовки ответа

200 OK
content-type: text/event-stream; charset=utf-8
cache-control: no-store
x-accel-buffering: no
x-request-id: req_0Kj2wq8ULn4mAe1s
connection: keep-alive

Два из этих заголовков достаточно важны, чтобы их пояснить:

  • cache-control: no-store говорит каждому кэшу на пути, включая любой CDN, не буферизовать и не хранить ответ. Закэшированный поток событий — это сломанный поток событий.
  • x-accel-buffering: no говорит обратным прокси, которые его соблюдают, сбрасывать каждую запись немедленно, а не накапливать буфер. Без него прокси может удерживать ваши пакеты, пока не заполнится его буфер, добавляя секунды задержки или полностью стопоря простаивающий поток.

Формат передачи

Пакеты приходят как SSE-события. У каждого есть id (номер), тип event и строка data с JSON-пакетом:

server-sent events
: runnev

id: 41823
event: batch
data: {"seq":41823,"ts":"2026-08-04T11:02:13.418Z","events":[{"type":"order.paid","id":"o_5521"}]}

id: 41824
event: batch
data: {"seq":41824,"ts":"2026-08-04T11:02:16.902Z","events":[{"type":"order.created","id":"o_5523"}]}

: keepalive

Типы событий, которые вы увидите:

eventЗначение
batchПакет событий. data — это JSON-объект пакета.
endПоток был удалён. Затем соединение закрывается.
(комментарий)Строка, начинающаяся с :, — это keepalive или баннер. Игнорируйте её.

Кадр keepalive

Когда поток простаивает, Runnev отправляет кадр-комментарий (: keepalive) каждые 15 секунд. Он не несёт данных и не является событием; его единственная задача — держать соединение наблюдаемо живым, чтобы ни клиент, ни какой-либо посредник не приняли тихий поток за мёртвый. Считайте приход любых байтов, включая комментарий, доказательством жизнеспособности, а промежуток дольше примерно 30 секунд вообще без байтов — поводом переподключиться.

Курсор и возобновление

Параметр запроса cursor управляет тем, откуда начинается подписка:

ЗначениеНачинается с
?cursor=head (по умолчанию)Только новые пакеты, с этого момента
?cursor=0Старейший уцелевший пакет
?cursor=NПервый пакет после номера N

Чтобы возобновить после разрыва, переподключитесь с ?cursor=, равным номеру последнего полностью обработанного вами пакета. Runnev также соблюдает стандартный заголовок запроса Last-Event-ID, который нативный браузерный EventSource отправляет автоматически при переподключении; если присутствуют оба, побеждает параметр запроса.

возобновление после номера 41823
curl -N "https://runnev.dev/v1/streams/$STREAM?cursor=41823" \
  -H "Accept: text/event-stream" \
  -H "Authorization: Bearer $RUNNEV_API_KEY"

Долгоживущие соединения

Здоровый подписчик держит один ответ открытым неограниченно долго. Это нормальный, поддерживаемый режим работы, а не крайний случай — подписка может оставаться открытой часами, — и SDK построены так, чтобы держать одну открытой и прозрачно переподключаться, когда сеть к этому вынуждает. Если вы ставите перед сервисом собственный обратный прокси, задайте ему дружелюбные к стримингу настройки:

  • Отключите буферизацию ответов на любом обратном прокси на пути. Runnev выставляет x-accel-buffering: no; убедитесь, что ваш прокси его соблюдает, либо отключите буферизацию явно.
  • Поднимите таймауты простоя и чтения выше 15-секундного интервала keepalive, с запасом. 10-секундный таймаут чтения у прокси оборвёт каждый простаивающий поток. Разумно ставить минуту и больше.
  • Не кэшируйте /v1. Ответы помечены no-store не просто так; кэширующий слой, игнорирующий это, сломает и публикацию, и подписку.

В полевом отчёте о длинных HTTP-ответах разобраны дружелюбные к стримингу настройки, которые нужны обратному прокси.

Непотоковый запасной вариант

Клиент, который не может держать потоковый ответ или предпочёл бы опрос, может вовсе обойтись без SSE. Тот же GET без заголовка Accept: text/event-stream возвращает метаданные потока, включая его текущий cursor. Опрашивайте курсор, и когда он продвинется, забирайте новые пакеты по номеру:

опрос вместо потока
# 1. насколько продвинулся поток?
curl "https://runnev.dev/v1/streams/$STREAM/cursor" \
  -H "Authorization: Bearer $RUNNEV_API_KEY"
# {"cursor":41825,"updated_at":"2026-08-04T11:03:01.220Z"}

# 2. заберите недостающие пакеты по номеру
curl "https://runnev.dev/v1/streams/$STREAM/batches/41824" \
  -H "Authorization: Bearer $RUNNEV_API_KEY"

Потоковая передача дешевле и с меньшей задержкой, но опрос доступен всегда и требует не больше, чем обычный клиент «запрос/ответ».

Дедупликация при переподключении

Доставка — не менее одного раза, поэтому переподключение вокруг сбоя может повторно доставить последний пакет. Поскольку каждый пакет несёт свой seq, дедупликация — это одно сравнение:

javascript
let last = -1;
for await (const batch of runnev.subscribe(streamId, { cursor: saved })) {
  if (batch.seq <= last) continue; // уже обработано
  handle(batch);
  last = batch.seq;
  persist(last); // чтобы после перезапуска возобновиться отсюда
}

Сохраняйте last там, где вы держите надёжное состояние, и передавайте его как курсор при старте. Это весь рецепт подписчика, переживающего перезапуски и сетевые сбои без потери или двойной обработки данных.