PartyFlow

Концепция: Webhooks#

PartyFlow поддерживает две стороны webhook-интеграций:

  • Incoming — внешний сервис шлёт JSON в канал (Grafana, CI/CD, Sentry).
  • Outgoing — PartyFlow шлёт событие на ваш HTTPS URL (сообщения, реакции, создание каналов). Подходит для ботов, AI/LLM-агентов, аудита.

Эта страница объясняет жизненный цикл каждой стороны и что нужно знать получателю, чтобы не терять события и не попасть в auto-disable.

Для payload'ов, headers и снипетов подписи см. reference/http-webhook-endpoint.md (incoming) и reference/outgoing-webhooks.md (outgoing).


Incoming webhooks#

Жизненный цикл#

create webhook → receive POST → verify (token + HMAC) → deliver to channel → log → (optional) auto-disable
  1. Create — admin создаёт webhook в UI, привязывает к конкретному каналу. Получает URL POST /api/v1/webhooks/incoming/{token} и signing secret (показываются один раз).
  2. Receive — внешний сервис шлёт POST с JSON-телом. Endpoint проверяет токен в URL, затем (если require_signature: true — default) HMAC-подпись и timestamp window.
  3. Format auto-detect — endpoint определяет Slack или Pachca формат по структуре payload. Один endpoint принимает оба.
  4. Deliver — сообщение попадает в канал как отдельное сообщение от имени webhook'а (с настроенным display name и иконкой). Push-уведомления применяются согласно silent/severity/mentions[].
  5. Log — каждая попытка пишется в delivery log (status code, processing time, payload preview). Доступна admin'у через UI.
  6. Auto-disable — 100 подряд ошибочных ответов → webhook автоматически отключается, последующие запросы получают HTTP 410 Gone, пока admin не включит webhook вручную. Счётчик сбрасывается на первом 2xx.

Формат auto-detect#

Признак в payload Формат
text, attachments, blocks Slack
message.content, message.entity_type Pachca
Ни то ни другое HTTP 400

Endpoint определяет по содержимому, без заголовков — не нужно настраивать формат явно.

Модель доставки#

  • At-most-once со стороны PartyFlow: принятое и валидное сообщение гарантированно попадает в канал или возвращает клиенту HTTP 5xx.
  • Дедупликация на стороне отправителя: если вы ретраите 5xx, PartyFlow не дедуплицирует запросы по содержимому. Либо делайте идемпотентный по смыслу payload, либо не ретраите.
  • Порядок сообщений внутри одного webhook'а сохраняется при последовательной доставке; параллельные отправки могут прийти в любом порядке.

Outgoing webhooks#

Outgoing webhook — подписка на события PartyFlow. Когда случается событие в space'е (сообщение, реакция, новый канал), PartyFlow шлёт POST с JSON на ваш URL. Получатель отвечает 2xx → доставка успешна; любой другой ответ → retry по расписанию или (при исчерпании попыток) dead-letter.

Жизненный цикл одного события#

chat event → match filters → sign payload → POST receiver → (2xx? done : retry) → dead-letter → auto-disable
  1. Event — в канале создано сообщение / добавлена реакция / создан канал. PartyFlow формирует envelope с event_id, event_type, space_id, conversation_id, actor_user_id, payload_json.
  2. Match — для каждой активной подписки в space'е проверяются три фильтра: event_types, channel_ids (пусто = все каналы), trigger (см. ниже). Несовпавшие подписки пропускаются.
  3. Enrich (опционально) — если у подписки include_context=true, подтягиваются последние context_size сообщений канала (1..50). Полезно для AI/LLM-получателей. Ошибка enrichment не блокирует доставку — добавляется поле context_error: true.
  4. Sign — формируется payload, считается HMAC подпись, тело и заголовки фиксируются для всех повторных попыток. Это гарантирует, что ретраи доставят точно те же bytes с той же подписью, даже если admin ротирует signing secret между попытками.
  5. Deliver — PartyFlow делает HTTPS POST получателю. Ответ 2xx → success. Всё остальное → retry или dead-letter по правилам ниже.
  6. Dead-letter — попытки исчерпаны (или получили не-ретраибельный 4xx). Доставка не повторяется. Счётчик consecutive failures webhook'а инкрементируется.
  7. Auto-disable — 100 подряд dead-letter'ов → webhook автоматически деактивируется. Admin получает уведомление. Новые события webhook больше не получает, пока не будет включён вручную.

Event types#

Canonical name Когда шлётся
MESSAGE_CREATED Новое сообщение в канале (включая сообщения ботов и webhook'ов)
MESSAGE_UPDATED Сообщение отредактировано
MESSAGE_DELETED Сообщение удалено
REACTION_ADDED Добавлена реакция на сообщение
REACTION_REMOVED Снята реакция
CONVERSATION_CREATED Создан новый канал в space'е
CONVERSATION_ARCHIVED Канал заархивирован

MEMBER_JOINED и MEMBER_LEFT объявлены в API, но пока не публикуются (появятся в следующем релизе). Если вы подпишетесь на них, события не будут приходить — матчер сработает, когда publisher начнёт их эмитить.

Trigger modes#

Для MESSAGE_CREATED и MESSAGE_UPDATED можно сузить доставку:

Trigger Когда срабатывает Config
all (default) На каждое совпавшее событие {}
mention Только если указанный бот @упомянут в сообщении {"bot_id": "<uuid>"}
keywords Только если сообщение содержит хотя бы одно ключевое слово как отдельный токен {"keywords": ["error", "fail"], "case_sensitive": false}

Keyword matching — по границам слов (\p{L}\p{N}): fail не срабатывает на failover или facepalm. По умолчанию регистронезависимый.

Для остальных event types trigger mention/keywords игнорируется — они имеют смысл только на сообщениях.

Channel filter#

Поле channel_ids подписки фильтрует события по конкретным каналам:

  • Пустой массив → все каналы space'а.
  • Непустой → только перечисленные conversation_id.

Threads наследуют conversation_id родительского канала, так что фильтр работает и для thread-сообщений.

Retry schedule#

Расписание — экспоненциальный backoff, конфигурируемый на стороне платформы. Формула:

delay_before_attempt(N+1) = min(Initial · 2^(N-1), Max) ± Jitter

Текущие production-дефолты: Initial = 5s, Max = 1h, всего 8 попыток (первая + 7 ретраев), Jitter = ±20%. Клиент получает такую последовательность:

Попытка Задержка перед ней (база) Кумулятивно с момента события
1 — (сразу) T0
2 5s ±20% ~T0 + 5s
3 10s ±20% ~T0 + 15s
4 20s ±20% ~T0 + 35s
5 40s ±20% ~T0 + 1m 15s
6 80s ±20% ~T0 + 2m 35s
7 160s ±20% ~T0 + 5m 15s
8 320s ±20% ~T0 + 10m 35s
После 8-й неудачной → dead-letter

Полное retry-window (база, без jitter) — ~10 минут 35 секунд; с учётом ±20% jitter — примерно [8m 28s, 12m 42s]. Clamp Max = 1h при текущих значениях не срабатывает (320s < 3600s), но защищает от разгона, если админ платформы увеличит Initial или MaxAttempts.

Jitter — симметричный random в пределах ±20% базовой задержки, чтобы при восстановлении flappy-получателя все отложенные ретраи не попали ровно в одну миллисекунду.

Значения Initial, Max, MaxAttempts могут быть изменены оператором платформы. Если ваши алерты завязаны на "доставка не приходит за N минут = endpoint down", ориентируйтесь на полное retry-window, а не на конкретные числа попыток — расписание может расшириться в будущем без breaking-уведомления.

Ретраибельные статусы: 408, 429, 5xx, transport errors (DNS/TCP/TLS). Не-ретраибельные: остальные 4xx (включая 400, 401, 403, 404, 422). Dead-letter сразу.

Если получатель прислал 429 или 503 с заголовком Retry-After, PartyFlow уважает указанное время (RFC 7231: delta-seconds или HTTP-date), ограничивая диапазоном 1s..1h. Некорректные значения игнорируются — fallback на exponential schedule.

Auto-disable#

consecutive_failures инкрементируется на каждом dead-letter'е. При достижении 100 webhook автоматически отключается. Admin получает уведомление в UI, новые события больше не доставляются этой подписке. Включить обратно — вручную в Admin UI. Счётчик сбрасывается при первом 2xx.

Что гарантируется получателю#

  • At-least-once доставка: каждое матчнувшееся событие будет доставлено хотя бы один раз при нормальной работе. При сетевых сбоях возможны дубликаты — дедуплицируйте по X-PartyFlow-Delivery-Id.
  • Frozen payload + frozen signature: ретраи приходят с точно тем же телом и теми же заголовками подписи. Если вы однажды поймали валидный запрос, все его ретраи пройдут ту же проверку подписи.
  • HTTPS only в production. HTTP допускается только в dev-окружениях.
  • Стабильное event_id на все ретраи — это UUIDv7 исходного события, delivery_id — отдельный UUID каждого row'а в очереди, но внутри одного row не меняется между попытками.

Что НЕ гарантируется#

  • Порядок событий между разными conversation_id. События из одного канала обычно приходят в порядке возникновения, но это не строгая гарантия — retry одного события может запоздать на час и прийти после следующего.
  • Доставка событий, случившихся пока webhook был отключён. Auto-disable не буферизует — пропущенные события не перепосылаются после включения.
  • Доставка за границу space'а. Подписка изолирована по space_id; события из других space'ов не попадают.

Что дальше#