Reference: Bot Event Polling API
Справочник по доставке событий боту через long-polling (GET /api/v1/bot/events).
Для концептуального обзора Bot account см. concepts/bots.md. Для отправки сообщений — reference/bot-rest-api.md.
Обзор
Bot Event Polling — альтернатива outgoing webhook'ам. Вместо того чтобы PartyFlow делал POST на ваш URL, бот сам периодически забирает накопившиеся события через GET.
[Bot] ──GET /api/v1/bot/events──▶ [PartyFlow] ──events[]──▶ [Bot]
▲ │
└───────── next_cursor ────────────────┘Преимущества:
- Не требует публичного HTTPS endpoint'а (удобно для dev, CI, on-prem).
- Не требует HMAC-подписи и верификации — авторизация через Bearer token.
- Простейший retry: если запрос упал, повторите с тем же
cursor.
Недостатки:
- Задержка доставки = частота опроса (рекомендуется 1–5 сек).
- Больше нагрузки на PartyFlow при высокой частоте опроса.
Настройка бота
Чтобы включить poll-доставку, при создании или обновлении бота установите:
| Поле | Тип | Описание |
|---|---|---|
event_delivery_mode |
string | "poll" (по умолчанию "none"). |
event_types[] |
string[] | События, на которые подписан бот. Например: ["MESSAGE_CREATED", "MESSAGE_UPDATED"]. |
channel_ids[] |
string[] | UUID каналов. Пусто = все каналы space'а. |
Интерактивные события (button.click, select.submit, modal.submit) и
custom slash события (slash.command) доставляются owning-боту через poll
queue автоматически, если у бота event_delivery_mode="poll". Добавлять их
в event_types[] не нужно.
Установить можно через Admin UI (Integrations → Bots → Edit).
GET /api/v1/bot/events
Забрать накопившиеся события.
Authentication
Authorization: Bearer fri_bot_<token>Тот же Bearer token, что и для отправки сообщений. См. reference/bot-rest-api.md.
Query parameters
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
cursor |
string | "" |
ID последнего полученного события. Пусто — первая страница. |
limit |
int | 50 |
Сколько событий вернуть. Минимум 1, максимум 500. |
Response (200)
{
"events": [
{
"id": "1234567890",
"event_id": "01923f5c-a2c8-7890-b4d0-5a2c8a4b6e0c",
"event_type": "MESSAGE_CREATED",
"payload_json": "{\"message_id\":\"msg-1\",\"conversation_id\":\"conv-1\",\"author_id\":\"u-1\",\"text\":\"Hello world\",\"msg_index\":1042,\"sent_at\":\"2026-05-01T12:00:00.123456789Z\",\"conversation_context\":{\"conversation_id\":\"conv-1\",\"type\":\"channel\",\"scope\":\"public\",\"is_direct\":false,\"is_public\":true}}",
"created_at": "2026-05-01T12:00:00Z"
}
],
"next_cursor": "1234567890"
}| Поле | Тип | Описание |
|---|---|---|
id |
string (int64) | Монотонный ID записи в очереди. Используется как cursor. |
event_id |
string (uuid) | Оригинальный ID события. Уникален в рамках (bot_id, event_id). |
event_type |
string | Canonical тип: MESSAGE_CREATED, MESSAGE_UPDATED, REACTION_ADDED, ... или direct bot тип button.click / select.submit / modal.submit / slash.command. |
payload_json |
string | JSON-строка с event-specific payload события. Клиент должен сделать JSON.parse(payload_json). Poll API не оборачивает payload в data и не возвращает data.message. |
created_at |
string (RFC3339) | Время появления события в очереди. |
next_cursor |
string | Передайте в следующем запросе как cursor. Пустая строка "" — больше нет событий. |
Корневой объект события содержит только поля из примера выше:
id, event_id, event_type, payload_json, created_at. В Poll API нет
top-level type, нет объекта data и нет вложенного data.message.
Если новых событий нет, ответ остаётся успешным и явно возвращает пустой cursor:
{
"events": [],
"next_cursor": ""
}Cursor pagination
PartyFlow гарантирует стабильную сортировку по id. Используйте это поле как непрозрачный cursor и не завязывайтесь на его внутренний формат.
import requests, time
cursor = ""
while True:
resp = requests.get(
f"{BASE}/api/v1/bot/events",
headers={"Authorization": f"Bearer {TOKEN}"},
params={"cursor": cursor, "limit": 100},
)
resp.raise_for_status()
data = resp.json()
for ev in data["events"]:
handle_event(ev)
cursor = data.get("next_cursor", "")
if not cursor:
time.sleep(2) # нет событий — подождёмВажно: события удаляются из очереди через retention_hours (по умолчанию 48 часов) после создания. Если бот не опрашивал дольше retention — события будут утрачены. Мониторьте next_cursor и не допускайте больших перерывов.
Ошибки
| Status | Причина |
|---|---|
| 400 | Невалидный limit (вне диапазона 1..500) или бот не настроен на poll доставку. |
| 401 | Невалидный или отозванный Bearer token. |
| 503 | Функция Bot Event Polling отключена на инстансе. |
| 500 | Внутренняя ошибка. Ретраить с exponential backoff. |
Payload событий
payload_json — это строка с JSON-объектом. В Poll API top-level envelope уже вынесен в поля event_id, event_type, created_at. Для обычных message-событий внутри payload_json нет envelope-обёртки, а event-specific поля лежат напрямую: например, у MESSAGE_CREATED нет обёртки message, а текст сообщения лежит в поле text. Interactive-события используют тот же публичный payload, что callback mode, поэтому внутри payload_json есть event_type, space_id, actor_user_id и occurred_at.
const payload = JSON.parse(event.payload_json)Пример MESSAGE_CREATED:
{
"message_id": "msg-1",
"conversation_id": "conv-1",
"author_id": "u-1",
"text": "Hello world",
"msg_index": 1042,
"sent_at": "2026-05-01T12:00:00.123456789Z",
"mentions": ["bot-1"],
"thread_id": "thread-root-message-id",
"parent_message_id": "thread-root-message-id",
"conversation_context": {
"conversation_id": "conv-1",
"type": "thread",
"scope": "public",
"subtype": "call_thread",
"is_direct": false,
"is_public": false,
"parent_conversation_id": "voice-1",
"parent_conversation_type": "voice",
"parent_is_public": true
}
}conversation_id и parent_message_id всегда пишутся в snake_case. mentions
присутствует только если в сообщении есть упоминания. thread_id и
parent_message_id — legacy flat-поля для reply-сообщений, у которых реально
есть parent_message_id; сообщение, отправленное напрямую в thread
conversation, может не иметь этих flat-полей. Для определения треда используйте
conversation_context.type="thread" и, при необходимости,
conversation_context.parent_conversation_id.
conversation_context — канонический способ понять, куда пришло сообщение:
type содержит raw conversation type (chat, channel, voice, thread или
будущее значение как есть), scope — клиентский scope (dm, public,
private). Для тредов scope наследуется от родителя, а subtype появляется
только для специальных случаев: сейчас call_thread означает тред гостевого
voice-чата. Flat-поля conversation_id, thread_id, parent_message_id
сохраняются для обратной совместимости.
Автор сообщения всегда передаётся плоским полем author_id. Объект author или вложенное поле author.user_id в Poll API не возвращаются.
Interactive payload
Если бот отправил сообщение с кнопками/списками и работает в poll mode, клик пользователя приходит в эту же очередь:
{
"event_type": "button.click",
"payload_json": "{\"interaction_id\":\"int-1\",\"event_type\":\"button.click\",\"space_id\":\"sp-1\",\"actor_user_id\":\"u-1\",\"conversation_id\":\"conv-1\",\"message_id\":\"msg-1\",\"action_id\":\"approve\",\"data\":\"{\\\"env\\\":\\\"prod\\\"}\",\"occurred_at\":\"2026-04-18T10:00:00Z\"}"
}После JSON.parse(payload_json) бот получает interaction_id и canonical
поля event_type, space_id, actor_user_id, occurred_at. Ответ нужно
отправить отдельным запросом:
POST /api/v1/bot/interactions/{interaction_id}/respondBody поддерживает update_message, send_message, ephemeral_text,
open_modal или noop. modal.submit имеет те же поля контекста плюс
values с отправленными пользователем значениями и не содержит data.
Interactive payload_json полностью повторяет callback body из
reference/interactive-components.md:
| Event | Обязательные поля внутри payload_json |
|---|---|
button.click |
interaction_id, event_type, space_id, actor_user_id, conversation_id, message_id, action_id, data, occurred_at; optional thread_id |
select.submit |
interaction_id, event_type, space_id, actor_user_id, conversation_id, message_id, action_id, data, selected_value, occurred_at; optional thread_id |
modal.submit |
interaction_id, event_type, space_id, actor_user_id, conversation_id, message_id, action_id, values, occurred_at; optional thread_id |
Slash command payload
Если poll-бот зарегистрировал custom slash command, invocation приходит как
slash.command вне подписки event_types[]:
{
"event_type": "slash.command",
"payload_json": "{\"trigger_id\":\"trig-1\",\"command\":\"deploy\",\"command_id\":\"cmd-1\",\"space_id\":\"sp-1\",\"conversation_id\":\"conv-1\",\"user_id\":\"u-1\",\"text\":\"prod\",\"raw_text\":\"/deploy prod\",\"invoked_at\":\"2026-05-13T10:00:00Z\"}"
}После JSON.parse(payload_json) бот отвечает финальным сообщением через:
POST /api/v1/bot/messages
X-PartyFlow-Trigger-Id: <trigger_id>Body поддерживает content и optional metadata_json. Routing берётся из
сохранённый trigger context: space, канал, тред и bot_id нельзя переопределить
полями body.
Rate limiting
Рекомендуемая частота опроса — не чаще 1 раза в секунду. PartyFlow может применять rate limit на GET /api/v1/bot/events (конфигурируется администратором инстанса). При превышении лимита — 429 Too Many Requests.
Сравнение с Outgoing Webhooks
| Критерий | Poll API | Outgoing Webhook |
|---|---|---|
| Публичный endpoint | Не нужен | Нужен |
| Задержка | Частота опроса | Near-realtime |
| Retry | Простой (повторить запрос) | PartyFlow управляет retry + backoff |
| Idempotency | ON CONFLICT DO NOTHING | X-PartyFlow-Delivery-Id |
| Масштабируемость | Линейная от числа ботов | Push-модель, эффективнее при высокой нагрузке |
SDK / Примеры
Python (requests)
import os, requests, time
BASE = os.environ["PARTYFLOW_BASE"]
TOKEN = os.environ["BOT_TOKEN"]
cursor = ""
while True:
r = requests.get(
f"{BASE}/api/v1/bot/events",
headers={"Authorization": f"Bearer {TOKEN}"},
params={"cursor": cursor, "limit": 50},
timeout=30,
)
r.raise_for_status()
data = r.json()
for ev in data["events"]:
print(f"[{ev['event_type']}] {ev['payload_json']}")
cursor = data.get("next_cursor", "")
if not cursor:
time.sleep(2)Go
Используйте стандартный net/http с тем же алгоритмом.