Перейти к основному содержимому

Каталог events

Каждый webhook delivery содержит один event. Подписка на event-types — при создании webhook'а (events: [...]).

Каталог событий — закрытый список из 12 кодов. Подписка на любой код вне него отклоняется с 400 unknown events. Пустой массив events отклоняется с 422 (валидация схемы, min_length=1).

Полный список (5 групп):

ГруппаСобытия
Messagingmessage.created, message.updated, message.deleted, message.reaction.added
Membermember.joined, member.left
Voicevoice.joined, voice.left
Commandscommand.invoked
Bot lifecyclebot.invited, bot.removed, bot.scopes_changed

Базовая структура

Любой event-payload:

{
"id": "deedccc1-...", // UUID delivery (для idempotency)
"type": "message.created", // event-type
"space_id": "019bfa33-...", // null для глобальных
"delivered_at": "2026-05-27T13:00:00Z",
"bot_user_id": "e8e311f6-...",
"data": { /* event-specific */ }
}

Headers:

  • X-Reasonspace-Event: message.created
  • X-Reasonspace-Delivery: <id>
  • X-Reasonspace-Signature: t=<unix>,v1=<hex(hmac)> (signing)

Events

message.created

{
"type": "message.created",
"space_id": "...",
"data": {
"message_id": "019e68ec-...",
"channel_id": "019c3e64-...",
"author_user_id": "09a5df2b-...",
"author_is_bot": false,
"content": "AT4HM3W1...", // E2E base64; бот НЕ читает
"created_at": "2026-05-27T13:07:05Z"
}
}

content зашифрован E2E — бот его не расшифрует. Платформа не передаёт никакого plain-text дубликата сообщения. Единственный канал, по которому бот получает текст от пользователя — это slash-команды через событие command.invoked (структурированные options, opt-in пользователя). Это privacy hard-rule, не настраивается. См. Команды.

member.joined

{
"type": "member.joined",
"data": { "user_id": "...", "role": "member" }
}

member.left

{
"type": "member.left",
"data": { "user_id": "...", "kicked_by_bot": "..." }
}

member.left эмитится как webhook только при кике ботом — тогда в data присутствует kicked_by_bot (id бота). Уход пользователя по другим причинам webhook'ом боту не доставляется.

voice.joined / voice.left

{
"type": "voice.joined",
"data": { "user_id": "...", "channel_id": "..." }
}

message.updated

{
"type": "message.updated",
"data": {
"message_id": "...",
"channel_id": "...",
"content": "AT4HM3W1...", // E2E base64; бот НЕ читает
"edited_at": "..."
}
}

message.deleted

{
"type": "message.deleted",
"data": {
"message_id": "...",
"channel_id": "..."
}
}

message.reaction.added

Реальный код события реакции — именно message.reaction.added. События удаления реакции в платформе нет.

{
"type": "message.reaction.added",
"data": {
"message_id": "...",
"channel_id": "...",
"user_id": "...",
"emoji": "👍"
}
}

command.invoked

Пользователь вызвал зарегистрированную боту slash-команду. Событие доставляется адресно — только целевому боту (см. ниже), а не всем подписчикам канала. См. Команды.

{
"type": "command.invoked",
"data": {
"target_bot_user_id": "...", // адресная доставка — только этому боту
"interaction_id": "...",
"interaction_token": "...", // HMAC-SHA256, TTL 900с — для эфемерного ответа
"channel_id": "...",
"actor_user_id": "...",
"command": "play",
"options": { /* типизированные значения по схеме команды */ },
"command_version": 1,
"created_at": "..."
}
}

Бот отвечает эфемерно через POST /api/bot/interactions/respond ({interaction_token, content}204). См. Ответ на команду.

bot.invited

Бот добавлен в space (owner выдал membership + scopes). Хорошая точка для инициализации состояния.

bot.removed

Бот удалён из space (owner кликнул Revoke / удалил bot membership). Должен localize cleanup своего состояния.

bot.scopes_changed

Owner изменил набор scope'ов бота в space. Бот должен пере-проверить, какие действия ему теперь доступны.

Channel-scoped события

7 из 12 событий привязаны к конкретному каналу:

  • message.created, message.updated, message.deleted, message.reaction.added
  • voice.joined, voice.left
  • command.invoked

Для них доставка работает fail-closed: бот получает событие только по тем каналам, которые входят в его allowed_channel_ids (выданные при invite). Если у события нет валидного channel_id (или space_id отсутствует), оно молча не доставляется боту — это защита от утечки контента во все боты space.

Остальные события (member.*, bot.*) не привязаны к каналу и доставляются без фильтра по allowed_channel_ids.

Идемпотентность

X-Reasonspace-Delivery — уникальный UUID. Один event может прилететь несколько раз (network retry). Храни последние ~10000 delivery_id и дропай duplicates.

SDK делает это автоматически (через BotEvent.id).

Retry policy

См. Retry подробнее. TL;DR:

  • 200-299 → success (сбрасывает failure_count в 0)
  • 410 (как и любой 4xx/5xx) → неуспешная доставка: логируется как bot_webhook_gone и учитывается в failure_count; отдельной логики permanent-disable по 410 в текущей реализации нет.
  • 4xx/5xx/network → 5 попыток; паузы применяются между попытками: 1s, 5s, 30s, 5min (после 5-й попытки паузы нет — итого ~5.6 мин на цикл). Значение 30min присутствует в _RETRY_DELAYS, но как пауза не применяется (6-й попытки нет).
  • После полного цикла из 5 неуспешных доставок подряд → failure_count++. Как только failure_count достигает 5, webhook приостанавливается на фиксированные 24 часа. Любая успешная доставка обнуляет счётчик.