For AI agents: markdown of this page — /docs-content-en/bots/events/bot-events.md documentation index — /llms.txt

Bot events (ONIMBOTV2*)

Arrive automatically for all bots with eventMode: "fetch". Received via polling.

Every event has the structure:

JSON
{
  "eventId": 29,
  "type": "ONIMBOTV2JOINCHAT",
  "date": "2026-04-13T17:12:00+00:00",
  "data": {
    "dialogId": "chat3553",
    "bot": { ... },
    "chat": { ... },
    "user": { ... },
    "language": "en"
  }
}
Field Type Description
eventId number Event ID — pass it as offset in the next polling request
type string Event type code
date string Date and time (ISO 8601)
data object Event data (structure depends on the type)

Event list

Type Description
ONIMBOTV2MESSAGEADD New message to the bot
ONIMBOTV2MESSAGEUPDATE Message edited
ONIMBOTV2MESSAGEDELETE Message deleted
ONIMBOTV2JOINCHAT Bot added to a chat
ONIMBOTV2COMMANDADD A slash command was invoked
ONIMBOTV2REACTIONCHANGE Reaction on a bot message
ONIMBOTV2DELETE Bot removed from the Bitrix24 account
ONIMBOTV2CONTEXTGET Dialog context requested

How to handle events

The polling loop is described on the Get events page. Once you receive the events array, route each event by its type field. You must reply to the bot in the event.data.dialogId dialog (for groups — chatXXX, for direct messages — the user id, the same as event.data.chat.dialogId).

Before processing a message, filter out the noise:

  • message.isSystem === true — a system message (joining a chat, settings change). Skip it.
  • message.authorId === event.data.bot.id — the bot's own message. Bots of type personal and supervisor receive all chat messages, including their own replies — without this check the handler will loop forever. A bot of type bot receives only direct messages and @mentions and does not get its own messages back, but the check does no harm.

Event dispatcher:

javascript
async function handleEvent(event) {
  const { type, data } = event
  const dialogId = data.dialogId

  switch (type) {
    case 'ONIMBOTV2MESSAGEADD': {
      const m = data.message
      if (m.isSystem || m.authorId === data.bot.id) return // skip system and own messages
      // Reply: POST /v1/bots/:botId/messages with { dialogId, fields: { message } }
      break
    }
    case 'ONIMBOTV2COMMANDADD':
      // Reply to the command: POST /v1/bots/:botId/commands/:commandId/answer
      // commandId = data.command.id, messageId = data.message.id
      break
    case 'ONIMBOTV2JOINCHAT':
      // Bot added to a chat — you can send a greeting to dialogId
      break
    case 'ONIMBOTV2REACTIONCHANGE':
      // data.reaction (reaction code) + data.action ('add' | 'delete')
      break
    case 'ONIMBOTV2CONTEXTGET':
      // Dialog opened via a link with context — data in data.context
      break
    case 'ONIMBOTV2MESSAGEUPDATE':
      // Message edited — data.message with the new text
      break
    case 'ONIMBOTV2MESSAGEDELETE':
      // Message deleted — data.messageId (a number)
      break
    case 'ONIMBOTV2DELETE':
      // Bot removed from the Bitrix24 account — release resources, stop polling
      break
  }
}

A reply to a dialog is always POST /v1/bots/:botId/messages with dialogId = event.data.dialogId.


ONIMBOTV2MESSAGEADD

New message to the bot (direct or an @mention in a group chat).

JSON
{
  "eventId": 35,
  "type": "ONIMBOTV2MESSAGEADD",
  "date": "2026-04-13T17:15:00+00:00",
  "data": {
    "dialogId": "chat123",
    "bot": {
      "id": 42,
      "code": "support_bot",
      "type": "bot",
      "isHidden": false,
      "isReactionsEnabled": true,
      "eventMode": "fetch"
    },
    "message": {
      "id": 1501,
      "chatId": 123,
      "authorId": 1,
      "date": "2026-04-13T17:15:00+00:00",
      "text": "Hi, bot! Help me with task #42",
      "isSystem": false,
      "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "forward": null,
      "params": {
        "FILE_ID": ["15423"]
      },
      "viewedByOthers": false
    },
    "chat": {
      "id": 123,
      "dialogId": "chat123",
      "type": "chat",
      "name": "Work chat",
      "owner": 1,
      "color": "#64a513",
      "entityType": "",
      "entityId": "",
      "permissions": {
        "manageUsersAdd": "member",
        "manageUsersDelete": "manager",
        "manageSettings": "owner",
        "manageMessages": "member",
        "canPost": "member"
      }
    },
    "user": {
      "id": 1,
      "active": true,
      "name": "John Brown",
      "firstName": "John",
      "lastName": "Brown",
      "workPosition": "Manager",
      "color": "#1eb4aa",
      "gender": "M",
      "extranet": false,
      "bot": false,
      "status": "online",
      "departments": [1, 5],
      "type": "employee"
    },
    "language": "en"
  }
}

data fields:

Field Type Description
dialogId string Dialog ID for the reply (chatXXX or the user id)
message.id number Message ID — for quoting via replyId
message.text string Message text
message.authorId number Author ID. Compare with bot.id to skip your own messages
message.isSystem boolean true for system messages — skip them
message.params.FILE_ID string[] IDs of attached files. Download: GET /files/:fileId
message.forward object | null Forwarded message object or null
bot object Bot object (id, code, type, eventMode)
chat.entityType string Binding type: LINES (Open Channels), CRM, empty string (regular chat)
chat.permissions object Chat permissions (manageUsersAdd, manageSettings, etc.)
user object Message author (id, name, departments, status, etc.)
language string User interface language

Handling voice and file messages

Voice messages and file attachments arrive in ONIMBOTV2MESSAGEADD with the following specifics:

  • A voice message has message.text empty or containing only a note ("Voice message").
  • The message.params.FILE_ID array is the authoritative source of attachment information; it appears in the event payload immediately.
  • GET /v1/chats/:dialogId/messages does not guarantee that the same message returns right away — the Bitrix24 disk index sometimes catches up to the event with a 1–2 second delay. Do not wait for it via polling.

Correct pattern:

javascript
const FILE_FETCH_TIMEOUT_MS = 10_000

async function handleMessageAdd(event) {
  const { message } = event.data
  const fileIds = message.params?.FILE_ID ?? []

  // Regular text message
  if (fileIds.length === 0) {
    return handleText(message)
  }

  // There is an attachment — read metadata via the disk API
  for (const fileId of fileIds) {
    let meta = null
    for (let attempt = 0; attempt < 2; attempt++) {
      // encodeURIComponent guards against future regressions if fileId ever
      // turns out not to be purely numeric (for example, B24 changes the ID format or
      // this pattern is extended to a user-provided ID from another source).
      const url = `https://vibecode.bitrix24.com/v1/files/${encodeURIComponent(fileId)}`
      // AbortController protects the handler from hanging if B24 or our proxy
      // respond slowly. Without a timeout one "stuck" event can block the poll loop.
      const controller = new AbortController()
      const timer = setTimeout(() => controller.abort(), FILE_FETCH_TIMEOUT_MS)
      let res
      try {
        res = await fetch(url, {
          headers: { 'X-Api-Key': process.env.VIBE_KEY },
          signal: controller.signal,
        })
      } catch (err) {
        clearTimeout(timer)
        if (err.name === 'AbortError') {
          console.warn(`File ${fileId} fetch timed out after ${FILE_FETCH_TIMEOUT_MS}ms, retrying`)
          continue
        }
        throw err
      }
      clearTimeout(timer)
      if (res.ok) {
        meta = await res.json()
        break
      }
      // 403 BITRIX_ACCESS_DENIED or 404 right after the event is typical:
      // the disk has not indexed the file yet. Wait 1.5 seconds and retry.
      if (res.status === 403 || res.status === 404) {
        await sleep(1500)
        continue
      }
      throw new Error(`Failed to read file ${fileId}: HTTP ${res.status}`)
    }
    if (!meta) {
      // The file is still unavailable — log it and continue without the attachment
      console.warn(`File ${fileId} not available after retry, skipping`)
      continue
    }
    await processAttachment(meta.data)
  }
}

const sleep = (ms) => new Promise((r) => setTimeout(r, ms))

Required scopes on the API key: imbot (receiving events) + disk (calling /v1/files/:id). Without the disk scope, a GET /v1/files/:id request returns SCOPE_DENIED 403. The im scope is additionally needed if you call im.* methods directly (not required for this pattern).

Permission model in Bitrix24:

  • The bot user (on whose behalf the API key works) must be a chat participant in the chat where the message arrived. For group bots, participation is added automatically on installation. For bots of type personal and supervisor, setup in your Bitrix24 account is required (see Register a bot).
  • A file attachment is available only within the Bitrix24 file retention period, which depends on the Bitrix24 account plan. After a file is deleted, the request returns BITRIX_ACCESS_DENIED even with the correct scope.
  • The disk scope on the key is necessary but not sufficient — it opens the /v1/files/:id endpoint, but Bitrix24 checks permission for a specific file separately by the bot's chat membership.

Transcribing a voice message. After getting the fileId from message.params.FILE_ID, download the file bytes and send them for recognition. Full chain: event → fileId → download the content → transcribe.

javascript
async function transcribeVoice(fileId) {
  // 1. Download the file bytes (binary response)
  const fileRes = await fetch(
    `https://vibecode.bitrix24.com/v1/files/${encodeURIComponent(fileId)}/download`,
    { headers: { 'X-Api-Key': process.env.VIBE_KEY } },
  )
  const audio = await fileRes.blob()

  // 2. Send the audio for transcription (Whisper, billed by duration against the portal AI quota; scope `vibe:ai`)
  const form = new FormData()
  form.append('file', audio, 'voice.ogg')
  const trRes = await fetch('https://vibecode.bitrix24.com/v1/audio/transcriptions', {
    method: 'POST',
    headers: { 'X-Api-Key': process.env.VIBE_KEY },
    body: form,
  })
  const { text } = await trRes.json()
  return text
}

The recognition contract (formats, limits, error codes empty_file / AI_PROVIDER_TIMEOUT) — Audio transcription. The vibe:ai scope is added to the key automatically.


ONIMBOTV2MESSAGEUPDATE

Message edited. The data structure matches ONIMBOTV2MESSAGEADD: the message object contains the updated text and the same id, authorId, params.FILE_ID fields. Find the original message by message.id.


ONIMBOTV2MESSAGEDELETE

Message deleted. Instead of a message object it contains messageId (a number).

JSON
{
  "eventId": 37,
  "type": "ONIMBOTV2MESSAGEDELETE",
  "date": "2026-04-13T17:16:00+00:00",
  "data": {
    "dialogId": "chat123",
    "bot": { "id": 42, "code": "support_bot", "type": "bot" },
    "messageId": 1501,
    "chat": { "id": 123, "dialogId": "chat123", "type": "chat", "name": "Work chat" },
    "user": { "id": 1, "name": "John Brown" }
  }
}

ONIMBOTV2JOINCHAT

Bot added to a chat. user — who added the bot.

JSON
{
  "eventId": 29,
  "type": "ONIMBOTV2JOINCHAT",
  "date": "2026-04-13T17:12:00+00:00",
  "data": {
    "dialogId": "chat3553",
    "bot": { "id": 42, "code": "support_bot", "type": "bot", "eventMode": "fetch" },
    "chat": {
      "id": 3553,
      "dialogId": "chat3553",
      "type": "chat",
      "name": "Sales department",
      "owner": 42,
      "color": "#64a513",
      "permissions": { "manageUsersAdd": "member", "manageSettings": "owner" }
    },
    "user": { "id": 3, "name": "Maria Davis", "workPosition": "Head" },
    "language": "en"
  }
}

ONIMBOTV2COMMANDADD

A bot slash command was invoked. Contains an additional command object.

JSON
{
  "eventId": 40,
  "type": "ONIMBOTV2COMMANDADD",
  "date": "2026-04-13T17:18:00+00:00",
  "data": {
    "dialogId": "chat123",
    "bot": { "id": 42, "code": "support_bot", "type": "bot" },
    "message": { "id": 1510, "text": "/help tasks" },
    "chat": { "id": 123, "dialogId": "chat123" },
    "user": { "id": 1, "name": "John Brown" },
    "command": {
      "id": 7,
      "command": "help",
      "params": "tasks",
      "context": "textarea"
    }
  }
}

command fields:

Field Description
id Command ID
command Command text without /
params Parameters entered after the command
context Where it was invoked from: textarea (input field), keyboard (button), menu (context menu)

To reply, use POST /commands/:commandId/answer with messageId from message.id.


ONIMBOTV2REACTIONCHANGE

A reaction was added or removed on a bot message.

JSON
{
  "eventId": 42,
  "type": "ONIMBOTV2REACTIONCHANGE",
  "date": "2026-04-13T17:19:00+00:00",
  "data": {
    "dialogId": "chat123",
    "bot": { "id": 42, "code": "support_bot", "type": "bot" },
    "reaction": "like",
    "action": "add",
    "message": { "id": 1502, "text": "Hi! How can I help?" },
    "chat": { "id": 123, "dialogId": "chat123" },
    "user": { "id": 1, "name": "John Brown" }
  }
}
Field Description
reaction Reaction code (see reaction codes)
action "add" — added, "delete" — removed

ONIMBOTV2DELETE

Bot removed from the Bitrix24 account. Contains only the bot object — without chat and user.

JSON
{
  "eventId": 50,
  "type": "ONIMBOTV2DELETE",
  "date": "2026-04-13T17:25:00+00:00",
  "data": {
    "bot": { "id": 42, "code": "support_bot", "type": "bot" }
  }
}

ONIMBOTV2CONTEXTGET

A user opened a dialog with the bot via a link that carries embedded context. The context field receives the arbitrary data from that link — the bot can reply immediately taking it into account.

JSON
{
  "eventId": 45,
  "type": "ONIMBOTV2CONTEXTGET",
  "date": "2026-04-13T17:20:00+00:00",
  "data": {
    "dialogId": "5",
    "bot": { "id": 42, "code": "support_bot", "type": "bot" },
    "context": { "action": "openTask", "taskId": "456" },
    "chat": { "id": 789, "dialogId": "5", "type": "private" },
    "user": { "id": 5, "name": "Alex Hughes" }
  }
}
Field Description
context Arbitrary data from the link the dialog was opened with. Arrives as a string or an object. Bitrix24 passes values as strings — the number 456 arrives as "456"

The event arrives for a direct dialog with the bot, so dialogId is the id of the user who opened the link.

How context is passed to the bot. A dialog with the bot is opened via a link with the BOT_CONTEXT parameter — it carries arbitrary URL-encoded JSON:

https://<portal>/online/?IM_DIALOG=<dialogId>&BOT_CONTEXT=<URL-encoded JSON>

<portal> is the Bitrix24 account domain, <dialogId> is the identifier of the dialog with the bot. The BOT_CONTEXT value arrives to the bot in the context field of this event unchanged.


Common objects in events

All events (except ONIMBOTV2DELETE) contain bot, chat, user objects with the same structure. The full objects are shown in the ONIMBOTV2MESSAGEADD example above. The abbreviated examples show only the key fields.

See also