Каталог events
Каждый webhook delivery содержит один event. Подписка на event-types —
при создании webhook'а (events: [...]).
Каталог событий — закрытый список из 12 кодов. Подписка на любой код вне
него отклоняется с 400 unknown events. Пустой массив events отклоняется с
422 (валидация схемы, min_length=1).
Полный список (5 групп):
| Группа | События |
|---|---|
| Messaging | message.created, message.updated, message.deleted, message.reaction.added |
| Member | member.joined, member.left |
| Voice | voice.joined, voice.left |
| Commands | command.invoked |
| Bot lifecycle | bot.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.createdX-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.addedvoice.joined,voice.leftcommand.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 часа. Любая успешная доставка обнуляет счётчик.