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:
{
"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 typepersonalandsupervisorreceive all chat messages, including their own replies — without this check the handler will loop forever. A bot of typebotreceives only direct messages and@mentionsand does not get its own messages back. It is safe to keep the check for all bot types.
Event dispatcher:
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).
{
"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.textis empty or contains only a note ("Voice message"). - Take the attachment list from the
message.params.FILE_IDarray: it arrives immediately in the event payload. GET /v1/chats/:dialogId/messagesmay return this message 1–2 seconds after the event. Do not wait for it with repeated message list requests.
Reading attachments from the event:
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
personalandsupervisor, 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_DENIEDeven with the correct scope. - The
diskscope on the key is necessary but not sufficient — it opens the/v1/files/:idendpoint, 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.
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.
{
"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.
{
"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.
{
"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.
{
"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.
{
"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.
{
"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.