Для AI-агентов: markdown этой страницы — /docs-content/bots/events.md индекс документации — /llms.txt
События
Получайте входящие события бота методом опроса и обрабатывайте их: новые сообщения, команды, реакции и добавление бота в чат.
Скоуп: imbot | Базовый URL: https://vibecode.bitrix24.tech/v1 | Авторизация: X-Api-Key
Получить события (polling)
GET /v1/bots/:botId/events
Основной механизм получения входящих сообщений и команд. Бот периодически запрашивает новые события. Запрос не ждёт их появления: ответ приходит сразу, а при пустой очереди массив событий пуст.
Вайбкод хранит lastOffset в базе данных — при первом запросе без offset используется сохранённое значение. Это позволяет боту продолжить с места остановки после перезапуска.
Параметры
| Параметр | Тип | Обяз. | По умолч. | Описание |
|---|---|---|---|---|
botId (path) |
number | да | — | ID бота |
offset (query) |
number | нет | из БД | Начальная позиция. Без параметра — сохранённое в БД значение. Переданный offset подтверждает события с меньшим ID: они удаляются из очереди и больше не приходят. offset ниже сохранённого значения события отдаёт, но сохранённое значение не меняет |
limit (query) |
number | нет | 100 |
Максимальное количество событий (1-200). Числа вне диапазона ограничиваются ближайшей границей, а не отклоняются |
withUserEvents (query) |
boolean | нет | false |
Включить user-события (ONIMV2*). Требует предварительной подписки — порядок настройки описан на странице User-события. Без подписки запрос вернёт 422 BITRIX_ERROR: User is not subscribed |
Примеры
curl — личный ключ
curl "https://vibecode.bitrix24.tech/v1/bots/42/events?limit=50" \
-H "X-Api-Key: YOUR_API_KEY"
curl — OAuth-приложение
curl "https://vibecode.bitrix24.tech/v1/bots/42/events?limit=50" \
-H "X-Api-Key: YOUR_APP_KEY" \
-H "Authorization: Bearer USER_SESSION_TOKEN"
JavaScript — личный ключ
const res = await fetch('https://vibecode.bitrix24.tech/v1/bots/42/events?limit=50', {
headers: {
'X-Api-Key': 'YOUR_API_KEY',
},
})
const { success, data } = await res.json()
console.log('События:', data.events.length, 'Ещё:', data.hasMore)
JavaScript — OAuth-приложение
const res = await fetch('https://vibecode.bitrix24.tech/v1/bots/42/events?limit=50', {
headers: {
'X-Api-Key': 'YOUR_APP_KEY',
'Authorization': 'Bearer USER_SESSION_TOKEN',
},
})
const { success, data } = await res.json()
Поля ответа
| Поле | Тип | Описание |
|---|---|---|
success |
boolean | Всегда true при успехе |
data |
object | Пакет событий и состояние очереди |
data.events |
array | Массив событий (см. типы событий) |
data.events[].eventId |
number | ID события. Для следующего запроса берите nextOffset, а не eventId: offset, равный eventId, вернёт это событие повторно |
data.events[].type |
string | Код события (ONIMBOTV2MESSAGEADD, ONIMBOTV2COMMANDADD и т.д.) |
data.events[].date |
string | Дата и время события (ISO 8601) |
data.events[].data |
object | Данные события (структура зависит от типа, ключи в camelCase) |
data.nextOffset |
number | Смещение для следующего запроса |
data.hasMore |
boolean | Есть ещё необработанные события |
data.storedOffset |
number | Смещение, с которым выполнен этот запрос: переданный offset, а без параметра — сохранённое в БД значение |
data.persisted |
boolean | true, если lastOffset в БД продвинулся: доставлено хотя бы одно событие, и переданный offset не ниже сохранённого значения. false — lastOffset не изменился: ответ пустой или передан offset ниже сохранённого значения |
data.needsReregistration |
boolean | Опциональное поле. Приходит только со значением true в пустом ответе, если платформа зафиксировала смену адреса после появления этой диагностики и регистрацию бота нужно заменить. При непустой очереди или без записанной метки поле отсутствует. Отсутствие поля не исключает более ранний переезд |
data.nextPollAfterMs |
number | Сколько миллисекунд подождать перед следующим запросом. Приходит в двух случаях: боту, которому события доставляются вебхуком (eventMode равен webhook) — очередь Event.get у такого бота пуста по устройству Битрикс24, — и любому боту, остановленному из-за неактивной подписки Маркетплейса, пока остановка не снята. Вне этих двух случаев боту в режиме fetch поле не приходит: отсутствие означает «опрашивайте с прежней частотой», поэтому пустым или нулевым оно не бывает. Если hasMore равен true, дочитайте очередь не дожидаясь паузы — она относится к следующему пустому опросу. На запрос с withUserEvents=true поле не приходит ни в одном режиме, поэтому у вебхук-бота и у остановленного бота его отсутствие именно в таком ответе не означает «опрашивайте с прежней частотой» — держите паузу, полученную на запросе без этого параметра |
data.hint |
string | Диагностическое сообщение при устойчиво пустой очереди — указывает на состояние установки на стороне Битрикс24. Счётчик пустых ответов обновляется периодически, а не на каждый запрос, поэтому подсказка появляется после нескольких периодов пустоты, а не строго на пятом запросе (при рекомендованном интервале опроса 2–5 секунд — спустя примерно пару минут непрерывно пустого опроса). Число N в тексте — количество зафиксированных периодов пустоты, а не точное число сделанных запросов. Накопление относится к боту в режиме fetch: у бота с eventMode равным webhook подсказка приходит уже на первый пустой ответ и говорит о том, что события ушли на webhookUrl — Событие, доставленное вебхуком. У бота, остановленного из-за неактивной подписки, подсказка приходит на каждый ответ и объясняет саму остановку. Для ветвления в коде опирайтесь на persisted и наличие событий, а hint трактуйте как подсказку |
Если вместе с пустым events пришло needsReregistration: true, платформа зафиксировала смену адреса. POST /v1/bots/:botId/resubscribe в этом состоянии доставку не восстановит. Если это самостоятельный Bot, удалите прежнюю регистрацию через DELETE /v1/bots/:botId перед повторным использованием того же code либо зарегистрируйте новый уникальный code, затем восстановите привязки открытых линий и WELCOME_BOT. Повторный POST /v1/bots с тем же кодом может переиспользовать прежнюю идентичность и не гарантирует новый ID бота. Для бота, принадлежащего AI-агенту или управляемому боту, не вызывайте DELETE /v1/bots/:botId: используйте восстановление ресурса-владельца, а если оно недоступно — обратитесь в поддержку.
Если поле отсутствует, это не исключает смену адреса до появления диагностики. Если адрес менялся, а переподписка не вернула доставку, выберите тот же безопасный путь с учётом владельца бота.
Пример ответа
Есть новые события (persisted: true):
{
"success": true,
"data": {
"events": [
{
"eventId": 35,
"type": "ONIMBOTV2MESSAGEADD",
"date": "2026-04-13T17:15:00+03:00",
"data": {
"message": { "id": 1501, "text": "Привет, бот!" },
"chat": { "id": 123, "dialogId": "chat123", "type": "chat" },
"user": { "id": 1, "name": "Иван Петров" }
}
}
],
"nextOffset": 36,
"hasMore": false,
"storedOffset": 35,
"persisted": true
}
}
Устойчиво пустая очередь событий (появляется поле hint, число в тексте — количество зафиксированных периодов пустоты, а не сделанных запросов):
{
"success": true,
"data": {
"events": [],
"nextOffset": 36,
"hasMore": false,
"storedOffset": 36,
"persisted": false,
"hint": "Events queue has stayed empty across 7 consecutive checks (the empty-poll counter is sampled periodically, not once per request, so this reflects sustained emptiness rather than the exact number of polls). Bot config: eventMode='fetch', code='support_bot'. If the bot is alive on B24 (chats receive messages, im.bot.list lists it) the most common cause is B24-side event-subscription decay — run POST /v1/bots/42/resubscribe first (lightweight, preserves openline / WELCOME_BOT bindings). If that does not help, verify: (1) eventMode is 'fetch' (current: 'fetch'); (2) your OAuth app's INSTALL event handler responded 200 to Bitrix24; (3) the bot was added to a chat where messages are being sent (it must be a participant for chat-message events); (4) only if all of the above are confirmed — try POST /v1/bots to re-register (destroys openline bindings; last resort)."
}
}
Пример ответа при ошибке
404 — бот не найден:
{
"success": false,
"error": {
"code": "BOT_NOT_FOUND",
"message": "Bot 999 not found. Register it first via POST /v1/bots."
}
}
Ошибки
| HTTP | Код | Описание |
|---|---|---|
| 400 | INVALID_BOT_ID |
botId не является числом |
| 404 | BOT_NOT_FOUND |
Бот с таким ID не найден |
| 403 | BOT_ACCESS_DENIED |
Бот принадлежит другому API-ключу |
| 422 | BITRIX_ERROR |
Ошибка Битрикс24 (текст ошибки в message) |
| 403 | SCOPE_DENIED |
API-ключ не имеет скоупа imbot |
| 410 | BOT_DISABLED |
Бот отключён и не обрабатывает вызовы API. Как вернуть бота — Бот отключён |
| 401 | TOKEN_MISSING |
API-ключ не имеет настроенных токенов |
| 409 | BOT_EVENTS_BUSY |
Пересекающийся опрос этого бота ещё выполняется или уже сдвинул сохранённую позицию. События не выданы. Повторите запрос через секунду — так указано в Retry-After: 1 |
| 503 | BOT_EVENTS_UNAVAILABLE |
Состояние опроса изменилось во время запроса или позицию не удалось сохранить. События не выданы. Повторите запрос через секунду — так указано в Retry-After: 1 |
Полный список общих ошибок API — Ошибки.
Известные особенности
Какой токен использовать. Для личного ключа vibe_api_… достаточно заголовка X-Api-Key. Для ключа авторизации vibe_app_… обязательно добавлять Authorization: Bearer <session_token> — без Bearer запрос вернёт 401 TOKEN_MISSING. Получение session_token для OAuth-приложения — см. Ключи и авторизация.
Неактивная подписка Маркетплейса. Если бот остановлен из-за неактивной подписки, polling не
ходит в Битрикс24 и отвечает успешной пустой очередью: events: [], hasMore:false,
persisted:false, сохранённый offset, nextPollAfterMs:60000 и hint с пояснением про
остановку. Поле nextPollAfterMs не приходит при withUserEvents=true. Продолжайте polling с этим темпом: события снова появятся после восстановления подписки и свежего подтверждения на платформе
Вайбкод — не позже чем через одну паузу опроса. Другие операции с этим ботом до восстановления отвечают 403 B24_MARKET_SUBSCRIPTION_REQUIRED.
Подробности восстановления — ошибки доступа.
Серверное хранение offset: Вайбкод хранит lastOffset в базе. Каждый запрос без offset читает сохранённое значение. Если запрос продвигает позицию, платформа сначала сохраняет её и только потом возвращает события с persisted: true. Если сохранить позицию не удалось, приходит 503 BOT_EVENTS_UNAVAILABLE без событий.
Один опрос за раз. Дождитесь ответа, прежде чем отправлять следующий запрос. Если опросы одного бота пересекаются, сохранённую позицию сдвигает и выдаёт эти события с persisted: true только один из них. Пересекающийся опрос получает 409 BOT_EVENTS_BUSY без событий. Опрос, который позицию не сдвигает (явный offset ниже сохранённого значения или пустая очередь), может вместо этого ответить 200. Пересекающийся запрос может успеть обратиться к Битрикс24 до отказа и расходует лимит запросов к нему. На 409 и 503 повторите запрос через секунду — так указано в заголовке Retry-After: 1. Опросы разных ботов друг друга не ждут, даже если у них общая очередь пользовательских событий.
Однократная обработка не гарантируется. Соединение может оборваться уже после того, как позиция сохранена. Поэтому держите в приложении защиту от повторной обработки событий и учитывайте в ней тип события.
offset=0: повторить историю событий нельзя — подтверждённые события удалены из очереди. Явный offset=0 ничего не подтверждает и показывает события, которые ещё ждут подтверждения, включая уже полученные последним запросом, — это способ посмотреть очередь при отладке. Если бот уже получал события, offset=0 ниже сохранённого значения, поэтому lastOffset не меняется, в ответе persisted: false, и следующий запрос без offset продолжит с сохранённого значения, не потеряв новых событий.
offset=N (конкретное значение): само событие с указанным ID попадает в ответ. Чтобы не получить дубли, всегда передавайте nextOffset из предыдущего ответа, а не eventId последнего обработанного события. Запрос с N, равным сохранённому значению или больше него, сдвигает lastOffset так же, как запрос без offset.
Цепочка запросов:
GET /events → { nextOffset: 42, hasMore: true }
GET /events?offset=42 → { nextOffset: 55, hasMore: false }
GET /events?offset=55 → { events: [], hasMore: false }
Рекомендуемый интервал polling: 2-5 секунд между запросами — если платформа не прислала nextPollAfterMs (условия присутствия поля, включая запрос с withUserEvents=true, — в таблице полей ответа). Прислала — ждите столько, сколько там указано: значение может меняться между релизами платформы, поэтому зажимайте его своими границами, а не полагайтесь на конкретное число.
Опрос не удерживает машину в сети. Если цикл ниже работает на сервере Black Hole, отключите на нём авто-сон. Таймер простоя считает входящие обращения к серверу, а опрос делает только исходящие вызовы — с точки зрения таймера сервер простаивает и через час засыпает вместе с процессом бота. Отключается вызовом PATCH /v1/infra/servers/:id/sleep со значением sleepAfterMinutes: null.
Polling-цикл (готовый пример):
const BOT_ID = 42
const API_KEY = 'YOUR_API_KEY'
const BASE = 'https://vibecode.bitrix24.tech/v1'
async function pollEvents() {
let offset = undefined
while (true) {
// Объявление вне `try`: пауза ниже читает ответ уже после `catch`.
let data
try {
const url = new URL(`${BASE}/bots/${BOT_ID}/events`)
if (offset !== undefined) url.searchParams.set('offset', String(offset))
const res = await fetch(url, {
headers: { 'X-Api-Key': API_KEY },
})
data = (await res.json()).data
for (const event of data.events ?? []) {
await handleEvent(event)
}
if (data.nextOffset !== undefined) {
offset = data.nextOffset
}
} catch (err) {
console.error('Poll error:', err.message)
}
// Пауза от платформы (`nextPollAfterMs`) — условия присутствия поля в таблице полей ответа.
// Боту в режиме `fetch` поле обычно не приходит, и тогда работает свой интервал; но у бота,
// остановленного из-за неактивной подписки, оно придёт — поэтому читаем его, а не игнорируем.
await new Promise(r => setTimeout(r, Math.min(data?.nextPollAfterMs ?? 3000, 3600000)))
}
}
async function handleEvent(event) {
const { data } = event
switch (event.type) {
case 'ONIMBOTV2MESSAGEADD':
// Ответить на сообщение
await fetch(`${BASE}/bots/${BOT_ID}/messages`, {
method: 'POST',
headers: { 'X-Api-Key': API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({
dialogId: data.chat.dialogId,
fields: { message: `Получил: ${data.message.text}` },
}),
})
break
case 'ONIMBOTV2COMMANDADD':
// Ответить на команду
await fetch(`${BASE}/bots/${BOT_ID}/commands/${data.command.id}/answer`, {
method: 'POST',
headers: { 'X-Api-Key': API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({
dialogId: data.chat.dialogId,
messageId: data.message.id,
fields: { message: `Команда /${data.command.command}: ${data.command.params}` },
}),
})
break
}
}
pollEvents()