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

Bot events (ONIMBOTV2*)

These events arrive automatically for all bots with eventMode: "fetch". They are fetched by polling.

Every event has the following structure:

JSON
{
  "eventId": 35,
  "type": "ONIMBOTV2MESSAGEADD",
  "date": "2026-04-13T17:15:00+00:00",
  "data": {
    "bot": { ... },
    "message": { ... },
    "chat": { ... },
    "user": { ... },
    "language": "en"
  }
}
Field Type Description
FieldeventId Typenumber DescriptionEvent ID — pass it as offset in the next poll request
Fieldtype Typestring DescriptionEvent type code
Fielddate Typestring DescriptionDate and time (ISO 8601)
Fielddata Typeobject DescriptionEvent data. The structure depends on the type

Event list

Type Description
TypeONIMBOTV2MESSAGEADD DescriptionNew message to the bot
TypeONIMBOTV2MESSAGEUPDATE DescriptionMessage edited
TypeONIMBOTV2MESSAGEDELETE DescriptionMessage deleted
TypeONIMBOTV2JOINCHAT DescriptionBot added to a chat
TypeONIMBOTV2COMMANDADD DescriptionBot command invoked
TypeONIMBOTV2REACTIONCHANGE DescriptionReaction on a bot message
TypeONIMBOTV2DELETE DescriptionBot removed from the Bitrix24 account
TypeONIMBOTV2CONTEXTGET DescriptionDialog context requested

How to handle events

The polling loop is described on the Get events (polling) page. Once you receive the events array, route each event by its type field. The bot must reply in the event.data.chat.dialogId dialog: chatXXX for groups, the user ID for direct messages. The chat.dialogId field is present in every event except ONIMBOTV2DELETE. A top-level dialogId in data arrives only in ONIMBOTV2JOINCHAT and ONIMBOTV2CONTEXTGET, so it cannot be used to reply to a message, a command or a reaction.

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. It is safe to keep the check for all bot types.

Event dispatcher:

javascript
async function handleEvent(event) {
  const { type, data } = event
  const dialogId = data.chat?.dialogId // only ONIMBOTV2DELETE has no chat object

  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.chat.dialogId.


ONIMBOTV2MESSAGEADD

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

JSON
{
  "eventId": 35,
  "type": "ONIMBOTV2MESSAGEADD",
  "date": "2026-04-13T17:15:00+00:00",
  "data": {
    "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"],
        "REPLY_ID": "1498"
      },
      "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
Fieldchat.dialogId Typestring DescriptionDialog ID for the reply: chatXXX for a group chat, the user ID for a direct chat
Fieldmessage.id Typenumber DescriptionMessage ID — for quoting via replyId
Fieldmessage.text Typestring DescriptionMessage text
Fieldmessage.authorId Typenumber DescriptionAuthor ID. Compare with bot.id to skip the bot's own messages
Fieldmessage.isSystem Typeboolean Descriptiontrue for system messages — skip them
Fieldmessage.params.FILE_ID Typestring[] DescriptionIDs of attached files. Download: GET /files/:fileId
Fieldmessage.params.REPLY_ID Typestring DescriptionReply to a message: the ID of the quoted message. Read the original: GET /messages/:messageId for personal/supervisor bots, otherwise GET /chats/:dialogId/messages
Fieldmessage.forward Typeobject | null DescriptionForwarded message object or null
Fieldbot Typeobject DescriptionBot object (id, code, type, eventMode)
Fieldchat.entityType Typestring DescriptionBinding type: LINES (Open Channels), CRM, empty string (regular chat)
Fieldchat.permissions Typeobject DescriptionChat permissions (manageUsersAdd, manageSettings, etc.)
Fielduser Typeobject DescriptionMessage author (id, name, departments, status, etc.)
Fieldlanguage Typestring DescriptionThe user's interface language

Handling voice and file messages

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

  • For a voice message, message.text is empty or contains only a note ("Voice message").
  • Take the attachment list from the message.params.FILE_ID array: it arrives immediately in the event payload.
  • GET /v1/chats/:dialogId/messages may return this message 1–2 seconds after the event. Do not wait for it with repeated message list requests.

Reading attachments from the event:

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 escapes fileId if it ever contains characters other than digits.
      const url = `https://vibecode.bitrix24.com/v1/files/${encodeURIComponent(fileId)}`
      // AbortController limits the wait for a response: without a timeout, one stuck
      // event stops the whole polling 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: the file is not yet
      // available on Drive. Wait 1.5 seconds and repeat the request.
      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 not needed for this example.

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, membership is granted 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 access to a specific file separately, based on 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 transcription. 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 your Bitrix24 account's 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 transcription contract (formats, limits, error codes empty_file / AI_PROVIDER_TIMEOUT) is described in Audio transcription. The key needs the vibe:ai scope. It is pre-selected in the key creation form.


ONIMBOTV2MESSAGEUPDATE

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


ONIMBOTV2MESSAGEDELETE

Message deleted. Instead of a message object it contains a numeric messageId.

JSON
{
  "eventId": 37,
  "type": "ONIMBOTV2MESSAGEDELETE",
  "date": "2026-04-13T17:16:00+00:00",
  "data": {
    "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 is the 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 of department" },
    "language": "en"
  }
}

ONIMBOTV2COMMANDADD

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

JSON
{
  "eventId": 40,
  "type": "ONIMBOTV2COMMANDADD",
  "date": "2026-04-13T17:18:00+00:00",
  "data": {
    "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
Fieldid DescriptionCommand ID
Fieldcommand DescriptionCommand text without /
Fieldparams DescriptionParameters entered after the command
Fieldcontext DescriptionWhere 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 on a bot message was added or removed.

JSON
{
  "eventId": 42,
  "type": "ONIMBOTV2REACTIONCHANGE",
  "date": "2026-04-13T17:19:00+00:00",
  "data": {
    "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
Fieldreaction DescriptionReaction code (see reaction codes)
Fieldaction Description"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 with embedded context. The context field contains arbitrary data from that link — the bot can take it into account and reply right away.

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
Fieldcontext DescriptionArbitrary 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 your Bitrix24 account domain, <dialogId> is the identifier of the dialog with the bot. The BOT_CONTEXT value reaches the bot unchanged in the context field of this event.


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