[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"docs-bots\u002Fevents\u002Fbot-events":3,"docs-tabs-bots\u002Fevents\u002Fbot-events":6},{"content":4,"lastmod":5},"\n## Bot-события (ONIMBOTV2*)\n\nПриходят автоматически для всех ботов с `eventMode: \"fetch\"`. Получаются через [polling](\u002Fdocs\u002Fbots\u002Fevents\u002Fpolling).\n\nКаждое событие имеет структуру:\n\n```json\n{\n  \"eventId\": 29,\n  \"type\": \"ONIMBOTV2JOINCHAT\",\n  \"date\": \"2026-04-13T17:12:00+03:00\",\n  \"data\": {\n    \"dialogId\": \"chat3553\",\n    \"bot\": { ... },\n    \"chat\": { ... },\n    \"user\": { ... },\n    \"language\": \"ru\"\n  }\n}\n```\n\n| Поле | Тип | Описание |\n|------|-----|---------|\n| `eventId` | number | ID события — передайте как `offset` в следующем запросе polling |\n| `type` | string | Код типа события |\n| `date` | string | Дата и время (ISO 8601) |\n| `data` | object | Данные события (структура зависит от типа) |\n\n## Список событий\n\n| Тип | Описание |\n|-----|---------|\n| [ONIMBOTV2MESSAGEADD](#onimbotv2messageadd) | Новое сообщение боту |\n| [ONIMBOTV2MESSAGEUPDATE](#onimbotv2messageupdate) | Сообщение отредактировано |\n| [ONIMBOTV2MESSAGEDELETE](#onimbotv2messagedelete) | Сообщение удалено |\n| [ONIMBOTV2JOINCHAT](#onimbotv2joinchat) | Бот добавлен в чат |\n| [ONIMBOTV2COMMANDADD](#onimbotv2commandadd) | Вызвана slash-команда |\n| [ONIMBOTV2REACTIONCHANGE](#onimbotv2reactionchange) | Реакция на сообщение бота |\n| [ONIMBOTV2DELETE](#onimbotv2delete) | Бот удалён с портала |\n| [ONIMBOTV2CONTEXTGET](#onimbotv2contextget) | Запрошен контекст диалога |\n\n---\n\n## Как обрабатывать события\n\nЦикл опроса описан на странице [Получить события](\u002Fdocs\u002Fbots\u002Fevents\u002Fpolling). Получив массив `events`, маршрутизируйте каждое событие по полю `type`. Отвечать боту нужно в диалог `event.data.dialogId` (для групп — `chatXXX`, для личных — id пользователя, тот же, что `event.data.chat.dialogId`).\n\n**Перед обработкой сообщения отфильтруйте лишнее:**\n\n- `message.isSystem === true` — служебное сообщение (вход в чат, смена настроек). Пропускайте.\n- `message.authorId === event.data.bot.id` — собственное сообщение бота. Боты типа `personal` и `supervisor` получают все сообщения чата, включая свои ответы, — без этой проверки обработчик зациклится. Бот типа `bot` получает только личные сообщения и `@упоминания` и свои сообщения обратно не получает, но проверка не повредит.\n\n**Диспетчер событий:**\n\n```javascript\nasync function handleEvent(event) {\n  const { type, data } = event\n  const dialogId = data.dialogId\n\n  switch (type) {\n    case 'ONIMBOTV2MESSAGEADD': {\n      const m = data.message\n      if (m.isSystem || m.authorId === data.bot.id) return \u002F\u002F пропустить служебные и свои\n      \u002F\u002F Ответить: POST \u002Fv1\u002Fbots\u002F:botId\u002Fmessages с { dialogId, fields: { message } }\n      break\n    }\n    case 'ONIMBOTV2COMMANDADD':\n      \u002F\u002F Ответить на команду: POST \u002Fv1\u002Fbots\u002F:botId\u002Fcommands\u002F:commandId\u002Fanswer\n      \u002F\u002F commandId = data.command.id, messageId = data.message.id\n      break\n    case 'ONIMBOTV2JOINCHAT':\n      \u002F\u002F Бот добавлен в чат — можно отправить приветствие в dialogId\n      break\n    case 'ONIMBOTV2REACTIONCHANGE':\n      \u002F\u002F data.reaction (код реакции) + data.action ('add' | 'delete')\n      break\n    case 'ONIMBOTV2CONTEXTGET':\n      \u002F\u002F Диалог открыт по ссылке с контекстом — данные в data.context\n      break\n    case 'ONIMBOTV2MESSAGEUPDATE':\n      \u002F\u002F Сообщение отредактировано — data.message с новым текстом\n      break\n    case 'ONIMBOTV2MESSAGEDELETE':\n      \u002F\u002F Сообщение удалено — data.messageId (число)\n      break\n    case 'ONIMBOTV2DELETE':\n      \u002F\u002F Бот удалён с портала — освободите ресурсы, прекратите опрос\n      break\n  }\n}\n```\n\nОтвет в диалог — всегда [`POST \u002Fv1\u002Fbots\u002F:botId\u002Fmessages`](\u002Fdocs\u002Fbots\u002Fmessages\u002Fsend) с `dialogId = event.data.dialogId`.\n\n---\n\n## ONIMBOTV2MESSAGEADD\n\nНовое сообщение боту (личное или @упоминание в групповом чате).\n\n```json\n{\n  \"eventId\": 35,\n  \"type\": \"ONIMBOTV2MESSAGEADD\",\n  \"date\": \"2026-04-13T17:15:00+03:00\",\n  \"data\": {\n    \"dialogId\": \"chat123\",\n    \"bot\": {\n      \"id\": 42,\n      \"code\": \"support_bot\",\n      \"type\": \"bot\",\n      \"isHidden\": false,\n      \"isReactionsEnabled\": true,\n      \"eventMode\": \"fetch\"\n    },\n    \"message\": {\n      \"id\": 1501,\n      \"chatId\": 123,\n      \"authorId\": 1,\n      \"date\": \"2026-04-13T17:15:00+03:00\",\n      \"text\": \"Привет, бот! Подскажи по задаче #42\",\n      \"isSystem\": false,\n      \"uuid\": \"a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\n      \"forward\": null,\n      \"params\": {\n        \"FILE_ID\": [\"15423\"]\n      },\n      \"viewedByOthers\": false\n    },\n    \"chat\": {\n      \"id\": 123,\n      \"dialogId\": \"chat123\",\n      \"type\": \"chat\",\n      \"name\": \"Рабочий чат\",\n      \"owner\": 1,\n      \"color\": \"#64a513\",\n      \"entityType\": \"\",\n      \"entityId\": \"\",\n      \"permissions\": {\n        \"manageUsersAdd\": \"member\",\n        \"manageUsersDelete\": \"manager\",\n        \"manageSettings\": \"owner\",\n        \"manageMessages\": \"member\",\n        \"canPost\": \"member\"\n      }\n    },\n    \"user\": {\n      \"id\": 1,\n      \"active\": true,\n      \"name\": \"Иван Петров\",\n      \"firstName\": \"Иван\",\n      \"lastName\": \"Петров\",\n      \"workPosition\": \"Менеджер\",\n      \"color\": \"#1eb4aa\",\n      \"gender\": \"M\",\n      \"extranet\": false,\n      \"bot\": false,\n      \"status\": \"online\",\n      \"departments\": [1, 5],\n      \"type\": \"employee\"\n    },\n    \"language\": \"ru\"\n  }\n}\n```\n\n**Поля data:**\n\n| Поле | Тип | Описание |\n|------|-----|---------|\n| `dialogId` | string | ID диалога для ответа (`chatXXX` или id пользователя) |\n| `message.id` | number | ID сообщения — для цитирования через [replyId](\u002Fdocs\u002Fbots\u002Fmessages\u002Fsend) |\n| `message.text` | string | Текст сообщения |\n| `message.authorId` | number | ID автора. Сравните с `bot.id`, чтобы пропустить собственные сообщения |\n| `message.isSystem` | boolean | `true` для служебных сообщений — пропускайте их |\n| `message.params.FILE_ID` | string[] | ID прикреплённых файлов. Скачать: [GET \u002Ffiles\u002F:fileId](\u002Fdocs\u002Fbots\u002Ffiles\u002Fdownload) |\n| `message.forward` | object \\| null | Объект пересланного сообщения или `null` |\n| `bot` | object | Объект бота (`id`, `code`, `type`, `eventMode`) |\n| `chat.entityType` | string | Тип привязки: `LINES` (открытая линия), `CRM`, пустая строка (обычный чат) |\n| `chat.permissions` | object | Права в чате (`manageUsersAdd`, `manageSettings` и др.) |\n| `user` | object | Автор сообщения (`id`, `name`, `departments`, `status` и др.) |\n| `language` | string | Язык интерфейса пользователя |\n\n### Обработка голосовых и файловых сообщений\n\nГолосовые сообщения и файлы-вложения попадают в `ONIMBOTV2MESSAGEADD` со следующими особенностями:\n\n- У голосового сообщения `message.text` пуст или содержит только пояснение (`\"Голосовое сообщение\"`).\n- Массив `message.params.FILE_ID` — **авторитативный источник** информации о вложениях, появляется сразу в теле события.\n- `GET \u002Fv1\u002Fchats\u002F:dialogId\u002Fmessages` **не гарантирует**, что то же сообщение вернётся сразу — disk-индекс Битрикс24 иногда догоняет событие с задержкой в 1–2 секунды. Не ждите его через polling.\n\n**Корректный паттерн:**\n\n```javascript\nconst FILE_FETCH_TIMEOUT_MS = 10_000\n\nasync function handleMessageAdd(event) {\n  const { message } = event.data\n  const fileIds = message.params?.FILE_ID ?? []\n\n  \u002F\u002F Обычное текстовое сообщение\n  if (fileIds.length === 0) {\n    return handleText(message)\n  }\n\n  \u002F\u002F Есть вложение — читаем метаданные через disk API\n  for (const fileId of fileIds) {\n    let meta = null\n    for (let attempt = 0; attempt \u003C 2; attempt++) {\n      \u002F\u002F encodeURIComponent защищает от future-regressions если fileId когда-то\n      \u002F\u002F окажется не чисто-числовым (например, B24 поменяет формат ID или\n      \u002F\u002F этот паттерн расширят на user-provided ID из другого источника).\n      const url = `https:\u002F\u002Fvibecode.bitrix24.tech\u002Fv1\u002Ffiles\u002F${encodeURIComponent(fileId)}`\n      \u002F\u002F AbortController защищает handler от зависания, если B24 или наш proxy\n      \u002F\u002F отвечают медленно. Без timeout одно «залипшее» событие может заблокировать poll-loop.\n      const controller = new AbortController()\n      const timer = setTimeout(() => controller.abort(), FILE_FETCH_TIMEOUT_MS)\n      let res\n      try {\n        res = await fetch(url, {\n          headers: { 'X-Api-Key': process.env.VIBE_KEY },\n          signal: controller.signal,\n        })\n      } catch (err) {\n        clearTimeout(timer)\n        if (err.name === 'AbortError') {\n          console.warn(`File ${fileId} fetch timed out after ${FILE_FETCH_TIMEOUT_MS}ms, retrying`)\n          continue\n        }\n        throw err\n      }\n      clearTimeout(timer)\n      if (res.ok) {\n        meta = await res.json()\n        break\n      }\n      \u002F\u002F 403 BITRIX_ACCESS_DENIED или 404 сразу после события — типично:\n      \u002F\u002F disk ещё не проиндексировал файл. Ждём 1.5 секунды и ретраим.\n      if (res.status === 403 || res.status === 404) {\n        await sleep(1500)\n        continue\n      }\n      throw new Error(`Failed to read file ${fileId}: HTTP ${res.status}`)\n    }\n    if (!meta) {\n      \u002F\u002F Файл всё ещё недоступен — логируем и продолжаем без attachment\n      console.warn(`File ${fileId} not available after retry, skipping`)\n      continue\n    }\n    await processAttachment(meta.data)\n  }\n}\n\nconst sleep = (ms) => new Promise((r) => setTimeout(r, ms))\n```\n\n**Требуемые скоупы у API-ключа:** `imbot` (получение событий) + **`disk`** (вызов `\u002Fv1\u002Ffiles\u002F:id`). Без скоупа `disk` запрос `GET \u002Fv1\u002Ffiles\u002F:id` возвращает `SCOPE_DENIED` 403. Скоуп `im` нужен дополнительно, если вы вызываете методы `im.*` напрямую (для этого паттерна не требуется).\n\n**Модель прав в Битрикс24:**\n\n- Бот-пользователь (от чьего имени работает API-ключ) должен быть **участником чата**, в котором пришло сообщение. Для групповых ботов участие добавляется автоматически при установке. Для ботов типа `personal` и `supervisor` требуется настройка в интерфейсе портала (см. [Зарегистрировать бота](\u002Fdocs\u002Fbots\u002Fmanagement\u002Fcreate)).\n- Файл-вложение доступно только в пределах срока хранения файлов в Битрикс24, который зависит от тарифа портала. После удаления файла запрос возвращает `BITRIX_ACCESS_DENIED` даже с корректным скоупом.\n- Скоуп `disk` у ключа **необходим, но недостаточен** — он открывает эндпоинт `\u002Fv1\u002Ffiles\u002F:id`, но права на конкретный файл Битрикс24 проверяет отдельно по участию бота в чате.\n\n**Расшифровка голосового сообщения.** Получив `fileId` из `message.params.FILE_ID`, скачайте байты файла и отправьте их на распознавание. Полная цепочка: событие → `fileId` → скачать содержимое → расшифровать.\n\n```javascript\nasync function transcribeVoice(fileId) {\n  \u002F\u002F 1. Скачать байты файла (бинарный ответ)\n  const fileRes = await fetch(\n    `https:\u002F\u002Fvibecode.bitrix24.tech\u002Fv1\u002Ffiles\u002F${encodeURIComponent(fileId)}\u002Fdownload`,\n    { headers: { 'X-Api-Key': process.env.VIBE_KEY } },\n  )\n  const audio = await fileRes.blob()\n\n  \u002F\u002F 2. Отправить аудио на расшифровку (Whisper, тарифицируется по длительности в AI-квоте портала; скоуп `vibe:ai`)\n  const form = new FormData()\n  form.append('file', audio, 'voice.ogg')\n  const trRes = await fetch('https:\u002F\u002Fvibecode.bitrix24.tech\u002Fv1\u002Faudio\u002Ftranscriptions', {\n    method: 'POST',\n    headers: { 'X-Api-Key': process.env.VIBE_KEY },\n    body: form,\n  })\n  const { text } = await trRes.json()\n  return text\n}\n```\n\nКонтракт распознавания (форматы, лимиты, коды ошибок `empty_file` \u002F `AI_PROVIDER_TIMEOUT`) — [Расшифровка аудио](\u002Fdocs\u002Fai\u002Faudio). Скоуп `vibe:ai` добавляется к ключу автоматически.\n\n---\n\n## ONIMBOTV2MESSAGEUPDATE\n\nСообщение отредактировано. Структура `data` совпадает с `ONIMBOTV2MESSAGEADD`: объект `message` содержит обновлённый `text` и те же поля `id`, `authorId`, `params.FILE_ID`. Находите исходное сообщение по `message.id`.\n\n---\n\n## ONIMBOTV2MESSAGEDELETE\n\nСообщение удалено. Вместо объекта `message` содержит `messageId` (число).\n\n```json\n{\n  \"eventId\": 37,\n  \"type\": \"ONIMBOTV2MESSAGEDELETE\",\n  \"date\": \"2026-04-13T17:16:00+03:00\",\n  \"data\": {\n    \"dialogId\": \"chat123\",\n    \"bot\": { \"id\": 42, \"code\": \"support_bot\", \"type\": \"bot\" },\n    \"messageId\": 1501,\n    \"chat\": { \"id\": 123, \"dialogId\": \"chat123\", \"type\": \"chat\", \"name\": \"Рабочий чат\" },\n    \"user\": { \"id\": 1, \"name\": \"Иван Петров\" }\n  }\n}\n```\n\n---\n\n## ONIMBOTV2JOINCHAT\n\nБот добавлен в чат. `user` — кто добавил бота.\n\n```json\n{\n  \"eventId\": 29,\n  \"type\": \"ONIMBOTV2JOINCHAT\",\n  \"date\": \"2026-04-13T17:12:00+03:00\",\n  \"data\": {\n    \"dialogId\": \"chat3553\",\n    \"bot\": { \"id\": 42, \"code\": \"support_bot\", \"type\": \"bot\", \"eventMode\": \"fetch\" },\n    \"chat\": {\n      \"id\": 3553,\n      \"dialogId\": \"chat3553\",\n      \"type\": \"chat\",\n      \"name\": \"Отдел продаж\",\n      \"owner\": 42,\n      \"color\": \"#64a513\",\n      \"permissions\": { \"manageUsersAdd\": \"member\", \"manageSettings\": \"owner\" }\n    },\n    \"user\": { \"id\": 3, \"name\": \"Мария Сидорова\", \"workPosition\": \"Руководитель\" },\n    \"language\": \"ru\"\n  }\n}\n```\n\n---\n\n## ONIMBOTV2COMMANDADD\n\nВызвана slash-команда бота. Содержит дополнительный объект `command`.\n\n```json\n{\n  \"eventId\": 40,\n  \"type\": \"ONIMBOTV2COMMANDADD\",\n  \"date\": \"2026-04-13T17:18:00+03:00\",\n  \"data\": {\n    \"dialogId\": \"chat123\",\n    \"bot\": { \"id\": 42, \"code\": \"support_bot\", \"type\": \"bot\" },\n    \"message\": { \"id\": 1510, \"text\": \"\u002Fhelp задачи\" },\n    \"chat\": { \"id\": 123, \"dialogId\": \"chat123\" },\n    \"user\": { \"id\": 1, \"name\": \"Иван Петров\" },\n    \"command\": {\n      \"id\": 7,\n      \"command\": \"help\",\n      \"params\": \"задачи\",\n      \"context\": \"textarea\"\n    }\n  }\n}\n```\n\n**Поля command:**\n\n| Поле | Описание |\n|------|---------|\n| `id` | ID команды |\n| `command` | Текст команды без `\u002F` |\n| `params` | Параметры, введённые после команды |\n| `context` | Откуда вызвана: `textarea` (поле ввода), `keyboard` (кнопка), `menu` (контекстное меню) |\n\nДля ответа используйте [POST \u002Fcommands\u002F:commandId\u002Fanswer](\u002Fdocs\u002Fbots\u002Fcommands\u002Fanswer) с `messageId` из `message.id`.\n\n---\n\n## ONIMBOTV2REACTIONCHANGE\n\nРеакция добавлена или удалена на сообщение бота.\n\n```json\n{\n  \"eventId\": 42,\n  \"type\": \"ONIMBOTV2REACTIONCHANGE\",\n  \"date\": \"2026-04-13T17:19:00+03:00\",\n  \"data\": {\n    \"dialogId\": \"chat123\",\n    \"bot\": { \"id\": 42, \"code\": \"support_bot\", \"type\": \"bot\" },\n    \"reaction\": \"like\",\n    \"action\": \"add\",\n    \"message\": { \"id\": 1502, \"text\": \"Привет! Чем могу помочь?\" },\n    \"chat\": { \"id\": 123, \"dialogId\": \"chat123\" },\n    \"user\": { \"id\": 1, \"name\": \"Иван Петров\" }\n  }\n}\n```\n\n| Поле | Описание |\n|------|---------|\n| `reaction` | Код реакции (см. [коды реакций](\u002Fdocs\u002Fbots\u002Fui\u002Freactions)) |\n| `action` | `\"add\"` — добавлена, `\"delete\"` — удалена |\n\n---\n\n## ONIMBOTV2DELETE\n\nБот удалён с портала. Содержит только объект `bot` — без `chat` и `user`.\n\n```json\n{\n  \"eventId\": 50,\n  \"type\": \"ONIMBOTV2DELETE\",\n  \"date\": \"2026-04-13T17:25:00+03:00\",\n  \"data\": {\n    \"bot\": { \"id\": 42, \"code\": \"support_bot\", \"type\": \"bot\" }\n  }\n}\n```\n\n---\n\n## ONIMBOTV2CONTEXTGET\n\nПользователь открыл диалог с ботом по ссылке, в которую вложен контекст. В поле `context` приходят произвольные данные из этой ссылки — бот может сразу ответить с их учётом.\n\n```json\n{\n  \"eventId\": 45,\n  \"type\": \"ONIMBOTV2CONTEXTGET\",\n  \"date\": \"2026-04-13T17:20:00+03:00\",\n  \"data\": {\n    \"dialogId\": \"5\",\n    \"bot\": { \"id\": 42, \"code\": \"support_bot\", \"type\": \"bot\" },\n    \"context\": { \"action\": \"openTask\", \"taskId\": \"456\" },\n    \"chat\": { \"id\": 789, \"dialogId\": \"5\", \"type\": \"private\" },\n    \"user\": { \"id\": 5, \"name\": \"Алексей Козлов\" }\n  }\n}\n```\n\n| Поле | Описание |\n|------|---------|\n| `context` | Произвольные данные из ссылки, по которой открыли диалог. Приходит строкой или объектом. Битрикс24 передаёт значения строками — число `456` придёт как `\"456\"` |\n\nСобытие приходит для личного диалога с ботом, поэтому `dialogId` — это id пользователя, открывшего ссылку.\n\n**Как боту передают контекст.** Диалог с ботом открывают по ссылке с параметром `BOT_CONTEXT` — в него кладут произвольный JSON в URL-кодировке:\n\n```\nhttps:\u002F\u002F\u003Cportal>\u002Fonline\u002F?IM_DIALOG=\u003CdialogId>&BOT_CONTEXT=\u003CJSON в URL-кодировке>\n```\n\n`\u003Cportal>` — домен портала, `\u003CdialogId>` — идентификатор диалога с ботом. Значение `BOT_CONTEXT` приходит боту в поле `context` этого события без изменений.\n\n---\n\n## Общие объекты в событиях\n\nВсе события (кроме `ONIMBOTV2DELETE`) содержат объекты `bot`, `chat`, `user` с одинаковой структурой. Полные объекты показаны в примере `ONIMBOTV2MESSAGEADD` выше. В сокращённых примерах показаны только ключевые поля.\n\n## Смотрите также\n\n- [Получить события](\u002Fdocs\u002Fbots\u002Fevents\u002Fpolling)\n- [User-события](\u002Fdocs\u002Fbots\u002Fevents\u002Fuser-events)\n- [Отправить сообщение](\u002Fdocs\u002Fbots\u002Fmessages\u002Fsend)\n- [Ответить на команду](\u002Fdocs\u002Fbots\u002Fcommands\u002Fanswer)\n- [Коды реакций](\u002Fdocs\u002Fbots\u002Fui\u002Freactions)\n- [Бот-платформа](\u002Fdocs\u002Fbots)\n","2026-07-07",{}]