Для AI-агентов: markdown этой страницы — /docs-content/bots/events/polling.md индекс документации — /llms.txt

Получить события (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 — личный ключ

Terminal
curl "https://vibecode.bitrix24.tech/v1/bots/42/events?limit=50" \
  -H "X-Api-Key: YOUR_API_KEY"

curl — OAuth-приложение

Terminal
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 — личный ключ

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-приложение

javascript
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):

JSON
{
  "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, число в тексте — количество зафиксированных периодов пустоты, а не сделанных запросов):

JSON
{
  "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 — бот не найден:

JSON
{
  "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-цикл (готовый пример):

javascript
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()

Смотрите также