Reference: Outgoing Webhooks
Полный справочник для получателя событий из PartyFlow: payload, headers, retry, верификация подписи.
Для концептуального обзора (жизненный цикл, trigger modes, auto-disable) см. concepts/webhooks.md. Для best practices безопасности — concepts/security.md.
Обзор
Outgoing webhook — подписка space'а на события PartyFlow. Когда в канале происходит событие (сообщение, реакция, создание канала), PartyFlow шлёт POST с JSON на URL, который вы указали при создании подписки. Получатель отвечает 2xx, чтобы подтвердить доставку.
[PartyFlow event] ──POST──▶ [your HTTPS endpoint] ──2xx──▶ doneСоздание подписки
Подписка создаётся admin'ом space'а в UI PartyFlow. При создании вы указываете:
| Поле | Описание |
|---|---|
name |
Человекочитаемое имя. Для admin UI. |
url |
HTTPS URL, на который будут приходить POST. SSRF-фильтр отвергает приватные диапазоны (RFC 1918, loopback, link-local). |
event_types[] |
Хотя бы один event type из списка ниже. |
channel_ids[] |
UUID'ы каналов. Пусто = все каналы space'а. |
trigger |
all / mention / keywords. Для MESSAGE_CREATED/MESSAGE_UPDATED. |
trigger_config |
JSON, зависит от trigger (см. concepts/webhooks.md). |
include_context + context_size |
Подтягивать ли последние N сообщений канала в payload (1..50). Для AI/LLM. |
После создания вы получаете signing secret — показывается один раз. Сохраните в secrets manager немедленно.
Request от PartyFlow
Каждая доставка — отдельный HTTP-запрос:
POST https://your-service.example.com/webhooks/partyflow
Content-Type: application/json
User-Agent: PartyFlow-Webhook/1.0
X-PartyFlow-Event: MESSAGE_CREATED
X-PartyFlow-Delivery-Id: 01923f5c-a2c8-7890-b4d0-5a2c8a4b6e0c
X-PartyFlow-Timestamp: 1744934400
X-PartyFlow-Signature: sha256=2c1a9f3e8b7d4a6c5e2f1d0b3a9c8e7f4d5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c
X-PartyFlow-Webhook-Version: 1
<JSON body, см. ниже>Headers
| Header | Назначение |
|---|---|
Content-Type |
Всегда application/json |
User-Agent |
PartyFlow-Webhook/1.0 |
X-PartyFlow-Event |
Canonical event type (MESSAGE_CREATED, REACTION_ADDED, ...). Быстрая диспетчеризация без парсинга body. |
X-PartyFlow-Delivery-Id |
UUIDv7, уникален для каждой доставки. Стабилен на все retry одной и той же доставки. Используйте для идемпотентности. |
X-PartyFlow-Timestamp |
Unix seconds. Используется в signing string. Проверяйте разрыв с now() ≤ 5 минут, чтобы отвергать replay. |
X-PartyFlow-Signature |
sha256=<hex(HMAC-SHA256)> от v1:{timestamp}:{body} с вашим signing secret. |
X-PartyFlow-Webhook-Version |
Мажорная версия схемы payload'а. Сейчас 1. PartyFlow передаёт её как строку — при major-bump'е вы можете ветвить парсинг без чтения тела. |
Timestamp, Delivery-Id и Signature фиксируются перед первой отправкой и не меняются между retry. Это означает, что HMAC одной и той же доставки всегда сходится — даже если admin ротирует signing secret между попытками.
Body: envelope
{
"event_id": "01923f5c-9876-7000-b4d0-5a2c8a4b6e0c",
"event_type": "MESSAGE_CREATED",
"space_id": "550e8400-e29b-41d4-a716-446655440000",
"conversation_id": "660e8400-e29b-41d4-a716-446655440001",
"thread_id": "",
"actor_user_id": "770e8400-e29b-41d4-a716-446655440002",
"occurred_at": "2026-04-18T14:32:05.123Z",
"schema_version": 1,
"data": { /* event-specific, см. ниже */ },
"context": { /* опционально, только если include_context=true */ }
}Поля envelope:
| Поле | Тип | Описание |
|---|---|---|
event_id |
UUIDv7 | Уникальный ID исходного события. Одно событие → одна доставка на подписку. Одно и то же событие может получить несколько подписок — event_id у них одинаковый, delivery_id — разный. |
event_type |
string | Canonical event name (см. таблицу ниже). |
space_id |
UUID | Space, в котором случилось событие. |
conversation_id |
UUID | Беседа, где произошло событие. Для thread-сообщения это ID треда; родитель доступен в data.conversation_context.parent_conversation_id. Пусто для space-scope событий. |
thread_id |
UUID | "" | Thread root message ID, если событие внутри треда. Пусто иначе. |
actor_user_id |
UUID | "" | Кто инициировал событие (автор сообщения, поставивший реакцию). Пусто для системных событий. |
occurred_at |
ISO 8601 UTC | Время события. Timezone — всегда UTC (Z). |
schema_version |
int | Версия схемы envelope'а. Начинается с 1, будет повышаться при breaking changes. |
data |
object | Event-specific payload. Может быть null для событий без данных. |
context |
object? | Присутствует только если у подписки include_context=true. См. Context enrichment. |
Body: per-event data
Поле data — сериализованный event-specific payload. Текущий формат:
MESSAGE_CREATED, MESSAGE_UPDATED
{
"message_id": "...",
"conversation_id": "...",
"thread_id": "...",
"parent_message_id": "...",
"text": "Deploy to prod starting now",
"mentions": ["bot_id_1", "user_id_2"],
"msg_index": 1042,
"author_id": "...",
"sent_at": "2026-04-18T14:32:05.123456789Z",
"conversation_context": {
"conversation_id": "...",
"type": "thread",
"scope": "public",
"subtype": "call_thread",
"is_direct": false,
"is_public": false,
"parent_conversation_id": "...",
"parent_conversation_type": "voice",
"parent_is_public": true
}
}conversation_id— канал/конверсация, где находится сообщение.thread_idиparent_message_id— legacy flat-поля для reply-сообщений, у которых реально естьparent_message_id. Сообщение, отправленное напрямую в thread conversation, может не иметь этих flat-полей; определяйте тред поconversation_context.type="thread"и parent-полям внутриconversation_context.mentions[]— ID'ы упомянутых участников (через@name).msg_index— монотонный counter в канале. Используется для unread-логики в UI.author_id— ID автора сообщения. Вложенный объектauthor/author.user_idне используется.sent_at— время отправки сообщения в UTC.conversation_context— канонический контекст беседы для ботов и webhook-получателей.type— raw conversation type (chat,channel,voice,threadили будущее значение),scope—dm,publicилиprivate. Для тредов scope берётся от родителя;subtype="call_thread"ставится только для Call Thread гостевого voice-чата.- Для
MESSAGE_UPDATEDтакже может присутствоватьedited_at.
MESSAGE_DELETED
{
"message_id": "...",
"conversation_id": "...",
"text": "",
"msg_index": 1042,
"author_id": "...",
"sent_at": "2026-04-18T14:32:05.123456789Z",
"conversation_context": {
"conversation_id": "...",
"type": "channel",
"scope": "private",
"is_direct": false,
"is_public": false
}
}Тело удалённого сообщения не передаётся: text приходит пустой строкой. Это сохраняет routing-поля (message_id, conversation_id, msg_index) без раскрытия удалённого содержимого.
REACTION_ADDED, REACTION_REMOVED
{
"message_id": "...",
"conversation_id": "...",
"emoji": "+1",
"user_id": "..."
}CONVERSATION_CREATED, CONVERSATION_ARCHIVED
{
"conversation_id": "...",
"name": "deploy-notifications",
"type": "public",
"created_by": "..."
}Примечание: точные field names data могут отличаться по историческим причинам — парсите защищённо (игнорируйте неизвестные поля, используйте optional-доступ). Envelope-уровень (event_id, event_type, space_id, ...) стабилен.
Context enrichment
Если подписка создана с include_context=true, payload содержит дополнительный блок:
{
"event_id": "...",
"event_type": "MESSAGE_CREATED",
"data": { ... },
"context": {
"messages": [
{
"id": "...",
"content": "Previous message in the thread",
"author_id": "...",
"created_at": "2026-04-18T14:30:10.000Z",
"edited_at": "2026-04-18T14:30:15.000Z"
}
],
"context_error": false
}
}messages[]— доcontext_sizeпоследних сообщений канала до триггерного события. Полезно для AI/LLM, которым нужен контекст для ответа.context_error: true— попытка enrichment'а не удалась (transient ошибка между integration и chat). Сообщение всё равно доставлено, но без контекста; решайте на своей стороне, нужно ли prompting пользователя про retry.edited_atприсутствует только если сообщение было отредактировано.
Response от получателя
Отвечайте как можно быстрее — разумный target < 1 сек, жёсткий timeout client'а — несколько секунд.
Коды
| Статус | Что произойдёт |
|---|---|
2xx |
Success. Доставка помечена как успешная, consecutive_failures сбрасывается. |
408, 429, 5xx |
Retry по exponential schedule (см. Retry schedule ниже). После исчерпания попыток → dead-letter. |
429 или 503 с Retry-After |
Вместо exponential PartyFlow ждёт указанное время (clamp 1s..1h). RFC 7231 — delta-seconds или HTTP-date. |
Остальные 4xx (400, 401, 403, 404, 422, ...) |
Dead-letter немедленно — нет смысла ретраить, payload/URL/auth misconfig'ят. consecutive_failures инкрементируется. |
| Transport error (DNS, TCP, TLS) | Retry как 5xx. |
Retry-After форматы
PartyFlow читает Retry-After в следующем порядке:
- HTTP header
Retry-After: 30(delta-seconds). - HTTP header
Retry-After: Wed, 18 Apr 2026 14:35:00 GMT(HTTP-date). - Body JSON
{"retry_after": 30}— legacy fallback для сервисов, не умеющих заголовки. - Body — просто число
30в plaintext.
Диапазон — [1s, 1h]. Значения вне диапазона игнорируются → fallback на exponential.
Требования к receiver'у
- Idempotency по
X-PartyFlow-Delivery-Id. Если вы уже обработали это delivery_id, не делайте side effect повторно — просто отвечайте2xx. Retry может прийти через час и более. - Верификация подписи до любой бизнес-логики. См. guides/verify-signatures.md.
- Constant-time compare при сверке подписей (защита от timing attack).
- Возвращайте 2xx быстро. Если обработка тяжёлая — ставьте задачу в свою очередь и сразу отвечайте. Иначе словите
408-подобный timeout с нашей стороны и retry. - Не ретраить 4xx. Если вы возвращаете
400— PartyFlow не повторит запрос с этим payload'ом никогда.
Retry schedule
Расписание — экспоненциальный backoff, конфигурируемый на стороне платформы. Формула:
delay_before_attempt(N+1) = min(Initial · 2^(N-1), Max) ± JitterТекущие production-дефолты: Initial = 5s, Max = 1h, MaxAttempts = 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 |
| — | dead-letter |
Полное retry-window от первой попытки до dead-letter — ~10 минут 35 секунд базы; с учётом симметричного ±20% jitter'а — примерно [8m 28s, 12m 42s]. Clamp Max = 1h при текущих значениях не срабатывает (максимальная задержка в серии — 320s), но активируется, если оператор увеличит Initial или MaxAttempts.
Jitter симметричный — задержка в [0.8 × base, 1.2 × base].
Значения Initial, Max, MaxAttempts могут быть скорректированы оператором платформы. Ориентируйтесь на полное retry-window, а не на конкретное число попыток — привязка к "после 4-й неудачной — точно dead-letter" не гарантирована.
Если circuit breaker сработал (подписка много раз подряд упала), retry автоматически откладывается на ~30 секунд без траты attempts. Rate limit (per-plan) тоже откладывает без траты attempts.
Auto-disable
Когда подписка получает 100 подряд dead-letter'ов, она автоматически деактивируется:
- Подписка получает статус
inactive. - Admin space'а получает уведомление в UI.
- Новые события больше не попадают в очередь этой подписки до ручной активации.
Счётчик consecutive failures сбрасывается при первом 2xx. События, случившиеся пока подписка была disabled, не буферизуются — они не будут доставлены после включения.
Верификация подписи (краткая версия)
Полные snippets — в guides/verify-signatures.md. Кратко:
import hmac, hashlib, time
def verify(secret: str, ts: str, body: bytes, sig_header: str) -> bool:
if not ts.isdigit():
return False
if abs(int(time.time()) - int(ts)) > 300:
return False
if not sig_header.startswith("sha256="):
return False
base = f"v1:{ts}:".encode() + body
expected = hmac.new(secret.encode(), base, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, sig_header[len("sha256="):])Ключевые моменты:
- Signing string —
v1:{timestamp}:{raw_body_bytes}. Префиксv1:— версия схемы. - Используйте
raw_body_bytesкак они пришли, без re-parse / re-serialize JSON. - Сначала проверяйте timestamp window, потом HMAC — экономит CPU на replay attempts.
hmac.compare_digest(constant-time) обязателен.
Event types enum
| Canonical name | Доставляется |
|---|---|
MESSAGE_CREATED |
✅ |
MESSAGE_UPDATED |
✅ |
MESSAGE_DELETED |
✅ |
REACTION_ADDED |
✅ |
REACTION_REMOVED |
✅ |
CONVERSATION_CREATED |
✅ |
CONVERSATION_ARCHIVED |
✅ |
MEMBER_JOINED |
Planned — не публикуется в текущем релизе. |
MEMBER_LEFT |
Planned — не публикуется в текущем релизе. |
Подписаться на MEMBER_JOINED/MEMBER_LEFT можно уже сейчас, но события не будут приходить, пока publisher не включит их в следующем релизе.
Ограничения
- URL — только HTTPS в production. HTTP допустим только в dev.
- SSRF блокировка: приватные диапазоны (RFC 1918, loopback, link-local) отвергаются при настройке URL и перед каждой отправкой (через отдельный DNS-resolve).
- Payload size — не декларирован публично; envelope остаётся компактным (обычно < 10 KB), но context enrichment может добавить до 50 сообщений с текстом.
- Параллельные доставки от одной подписки возможны. Если порядок критичен — обрабатывайте на своей стороне с учётом
event_idиoccurred_at. - Context enrichment —
context_sizeограничен 1..50. Больше не поддерживается. - Keywords trigger — до 20 ключевых слов на подписку, каждое 2..100 символов.
Управление подписками в Admin UI
Admin'ы space'а управляют outgoing webhook'ами в разделе Integrations → Outgoing webhooks. Для операций нужна роль admin или owner; обычные участники не видят управление подписками.
Доступные действия:
| Операция | Что делает |
|---|---|
| Создание | Создаёт подписку и показывает plaintext signing_secret один раз. |
| Список и детали | Показывает подписки space'а, фильтры, статус и последние доставки. |
| Обновление | Меняет URL, события, каналы, trigger, context и активность подписки. |
| Удаление | Отключает подписку и удаляет запланированные доставки. |
| Тестовая отправка | Отправляет пробный POST на URL подписки. |
| Ротация секрета | Выдаёт новый plaintext signing_secret один раз; URL не меняется. |
| Лог доставок | Показывает историю отправок с фильтром all / success / failed. |
После создания или ротации сохраните signing_secret сразу: повторно получить plaintext нельзя. В списках, деталях и уведомлениях секрет не отображается.
Что дальше
- concepts/webhooks.md — концептуальный обзор.
- concepts/security.md — security-модель, подписи, best practices.
- guides/verify-signatures.md — snippets для Python/Node/Go/Bash.
- concepts/rate-limits.md — retry стратегии и auto-disable с точки зрения отправителя/получателя.