For AI agents: markdown of this page — /docs-content-en/bots/events/webhook.md documentation index — /llms.txt
Event delivered by webhook
What the body of an event Bitrix24 sends straight to the bot looks like, which fields it carries, and how the handler verifies that the request is genuine.
When such an event arrives
The delivery mode is set when the bot is registered — POST /v1/bots with eventMode: "webhook" and the webhookUrl address. Bitrix24 sends every event as a POST request directly to that address; the Vibecode platform takes no part in delivery.
Polling stays empty for such a bot: GET /v1/bots/:botId/events returns an empty events array. While that array is empty, the response also carries a hint saying that events go to webhookUrl. Alongside it comes nextPollAfterMs, which is absent from the response to a request with withUserEvents=true.
Address requirements, the response timeout, the absence of redelivery, and a comparison of the two modes — Troubleshooting.
Request body
The body format is application/x-www-form-urlencoded; nesting is expressed with square brackets. Below, a "new message to the bot" event is split across lines for readability; in the request it is a single line with no breaks.
event=ONIMBOTV2MESSAGEADD
&data[bot][id]=42
&data[message][id]=1501
&data[message][text]=Hello%2C+bot
&data[message][params][FILE_ID][0]=15423
&data[chat][id]=123
&data[chat][dialogId]=chat123
&data[chat][type]=chat
&data[user][id]=1
&auth[application_token]=customFAKE-bot-token-0123456789abcdef01
&auth[domain]=your-portal.bitrix24.com
&auth[member_id]=a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
The parsed body is an object with three top-level keys:
{
"event": "ONIMBOTV2MESSAGEADD",
"data": {
"bot": { "id": "42" },
"message": {
"id": "1501",
"text": "Hello, bot",
"params": { "FILE_ID": ["15423"] }
},
"chat": { "id": "123", "dialogId": "chat123", "type": "chat" },
"user": { "id": "1" }
},
"auth": {
"application_token": "customFAKE-bot-token-0123456789abcdef01",
"domain": "your-portal.bitrix24.com",
"member_id": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
}
}
Body fields
| Field | Description |
|---|---|
event |
Event type code. The same set of codes as in polling — Bot events |
data |
Event data. Mirrors the data object of the same event in polling; values arrive as strings |
data.bot.id |
Bot ID. The same value goes into the path when replying — POST /v1/bots/:botId/messages |
data.chat.dialogId |
Dialog ID to reply to: chatXXX for a group chat, the user ID for a private one |
data.message.id |
Message ID — for quoting via replyId |
data.message.params.FILE_ID |
Array of attached file IDs. To download — GET /files/:fileId |
auth.application_token |
Value used to verify authenticity — see the section below |
auth.domain |
Address of the Bitrix24 account the event came from |
auth.member_id |
Bitrix24 account identifier |
All field values arrive as strings — numbers and booleans too, which follows from the body format. So compare data.message.id after converting it to a number, not directly.
Authenticity verification
The webhookUrl address is reachable from the internet, so the handler must tell an event from your Bitrix24 account apart from an outside request. The marker is the auth[application_token] field, and the value to expect depends on which key registered the bot through POST /v1/bots.
| Registration key | Expected auth[application_token] |
|---|---|
Personal key vibe_api_… |
The string custom followed by the bot token |
| Key bound to an OAuth application | That application's application_token — the same one the application verifies subscription events against |
The rest of this section covers the first case. For the second, compare against the application's application_token; the bot token plays no part in that check.
The platform issues the bot token itself at registration and never shows it in responses. To get a value to compare against, set your own token — PATCH /v1/bots/:botId with the fields.botToken field: 32 to 40 characters from the [A-Za-z0-9_-] alphabet. A value outside these bounds is rejected with 400 BOT_TOKEN_INVALID, and the bot token stays unchanged.
The same value arrives either at the top level in auth or nested in data[bot][auth] — check both places. If it does not match, respond 403 and do not process the event.
The bot token is a secret: it also authorizes incoming events. Store it next to the API key and do not publish it in client-side code.
Handler
Below is the application's own code, not a call to the Vibecode API, so there is no cURL example here. The example is written for a bot registered with a personal key, so the expected value is built from the bot token. If the bot was registered with an OAuth application's key, put that application's application_token into EXPECTED_TOKEN instead.
import express from 'express'
const BOT_TOKEN = process.env.BOT_TOKEN // token set via PATCH fields.botToken
const EXPECTED_TOKEN = 'custom' + BOT_TOKEN
const VIBE = 'https://vibecode.bitrix24.com/v1'
const app = express()
app.use(express.urlencoded({ extended: true }))
app.post('/bot-events', (req, res) => {
const incoming =
req.body.auth?.application_token ?? req.body.data?.bot?.auth?.application_token
if (incoming !== EXPECTED_TOKEN) return res.sendStatus(403)
// The response goes out before processing: Bitrix24 waits a limited time for it,
// and the event has no redelivery.
res.sendStatus(200)
handleEvent(req.body).catch((err) => console.error('Handler error:', err.message))
})
async function handleEvent({ event, data }) {
if (event !== 'ONIMBOTV2MESSAGEADD') return
// The bot's own reply comes back — without this check the handler loops.
if (String(data.message?.authorId) === String(data.bot?.id)) return
await fetch(`${VIBE}/bots/${data.bot.id}/messages`, {
method: 'POST',
headers: { 'X-Api-Key': process.env.VIBE_API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({
dialogId: data.chat.dialogId,
fields: { message: `Received: ${data.message.text}` },
}),
})
}
app.listen(3000)
Routing is driven by the event field; the set of types is the same as in polling. Full filtering of incoming messages, including how to skip system ones, and the data structure of each type — Bot events.