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

Event delivered by webhook

New OAuth bots support fetch and webhook. The platform verifies bot ownership using the same OAuth application and an exact match on the bot ID and code, without sending botToken to the OAuth API. Delivery to /api/bot/webhook requires a recent, verified INSTALL or placement event from the bound application; otherwise registration returns 409 BOT_APP_EVENT_ENROLLMENT_REQUIRED. A previously issued application token does not replace this verification. Events received before initial registration completes are retained in a bounded encrypted inbox. Bot-token changes for new application-owned bots are refused (409 BOT_OAUTH_POLICY_INVALID); unmarked legacy bots are not automatically converted. A standalone OAuth key without a bound application can use fetch or its own receiver, but is not trusted by the platform receiver.

The platform receiver is available to Agent and ManagedBot bots with a verified owner. An application bot without a verified owner receives 409 BOT_APP_EVENT_CONSUMER_REQUIRED before any data is written to Bitrix24. It can use fetch or its own callback. Events for an already published bot after a Bitrix24 account address change may remain in encrypted quarantine until the recovered identity is verified and published. Delivery requires an unchanged callback binding, continued eligibility of the application, author and owner membership, and verified authority to deliver the recovered events. If that basis changes or cannot be verified, held events are permanently discarded while bot identity recovery continues. Bounded retention does not guarantee delivery. Each recovery can retain at most 256 events and 4 MiB of ciphertext for 72 hours. A duplicate event does not extend retention. On overflow, the receiver returns 503 without acknowledging receipt.

This page describes the body of an event Bitrix24 sends directly to a bot, the fields it contains, and how the handler verifies the request's authenticity.

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 events as POST requests to webhookUrl. Delivery to your own handler goes directly to that handler without involving Vibecode. When the destination is the platform receiver /api/bot/webhook, the platform authenticates and accepts events for onward delivery.

Polling returns no events for a bot in this mode: 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. The response also includes nextPollAfterMs, except when withUserEvents=true.

See Troubleshooting for address requirements, the response timeout, the lack of guaranteed redelivery, and a comparison of the two modes.

Request body

The body format is application/x-www-form-urlencoded; nesting is expressed with square brackets. The examples below show only the fields used on this page. 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:

JSON
{
  "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
Fieldevent DescriptionEvent type code. The same set of codes as in polling — Bot events
Fielddata DescriptionEvent data. Mirrors the data object of the same event in polling; values arrive as strings
Fielddata.bot.id DescriptionBot ID. The same value goes into the path when replying — POST /v1/bots/:botId/messages
Fielddata.chat.dialogId DescriptionDialog ID to reply to: chatXXX for a group chat, the user ID for a private one
Fielddata.message.id DescriptionMessage ID — for quoting via replyId
Fielddata.message.params.FILE_ID DescriptionArray of attached file IDs. To download — GET /files/:fileId
Fieldauth.application_token DescriptionValue used to verify authenticity — see the section below
Fieldauth.domain DescriptionAddress of the Bitrix24 account the event came from
Fieldauth.member_id DescriptionBitrix24 account identifier

All field values, including numbers and booleans, arrive as strings because of the body format. Convert data.message.id to a number before comparing it.

Authenticity verification

The webhookUrl address is reachable from the internet, so the handler must distinguish between events from your Bitrix24 account and other requests. Check auth[application_token]; its expected value depends on the bot registration contract used with POST /v1/bots.

Registration class Expected auth[application_token]
Registration classPersonal key vibe_api_… Expected auth[application_token]The string custom followed by the bot token
Registration classOAuth bot registered under the previous contract Expected auth[application_token]The string custom followed by the bot token
Registration classNew application-owned OAuth bot Expected auth[application_token]Top-level auth.application_token from a recent, verified application enrollment; not botToken

The custom-token instructions below apply to personal-key bots using an inbound webhook and OAuth bots registered under the previous contract. Existing legacy bots retain that contract; using an OAuth key does not convert them into new application-owned bots. For new application-owned bots, the receiver below verifies the top-level application token from trusted server-side configuration; nested bot.auth does not confirm enrollment. The platform's ownership check during registration separately verifies the bot ID and code.

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.

For a personal-key bot using an inbound webhook or an OAuth bot registered under the previous contract, the same custom-token value arrives either at the top level in auth or nested in data[bot][auth] — check both places. If it does not match, respond with 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

Personal key or previous-contract OAuth bot

This example uses the custom bot token for a personal-key bot with an inbound webhook or an OAuth bot registered under the previous contract. For a new application-owned OAuth bot, use the separate example below.

javascript
import express from 'express'

const BOT_TOKEN = process.env.BOT_TOKEN // token set via PATCH fields.botToken
if (!BOT_TOKEN) {
  throw new Error('Set BOT_TOKEN')
}
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)

  // Respond before processing: Bitrix24 waits only a limited time for a response
  // and does not guarantee 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)

New application-owned OAuth bot

This minimal receiver verifies the request's authenticity. Run it separately from the custom bot-token example. Set APPLICATION_TOKEN to the registered application token from a verified application enrollment and keep it in trusted server-side configuration. Never take it from the incoming request. The receiver compares only the top-level auth.application_token with that configured value. The application is the stable identity: numeric data.bot.id is event data used for routing, not an authentication pin. The simplified event body above does not need data.bot.code.

After 409 BOT_ID_REPLACED, use error.details.botId for subsequent Bot API calls. The receiver continues accepting events authenticated by the same configured application token when the remote bot ID changes; no receiver configuration update is required before quarantine release. Never replace the trusted application token with a value learned from an incoming event.

javascript
import express from 'express'

const APPLICATION_TOKEN = process.env.APPLICATION_TOKEN
if (!APPLICATION_TOKEN) {
  throw new Error('Set APPLICATION_TOKEN')
}

const app = express()
app.use(express.urlencoded({ extended: true }))

app.post('/bot-events', (req, res) => {
  if (req.body?.auth?.application_token !== APPLICATION_TOKEN) return res.sendStatus(403)

  res.sendStatus(200)
})

app.listen(3000)

Routing is driven by the event field; the set of types is the same as in polling. See Bot events for full details on filtering incoming messages, including how to skip system messages, and the data structure for each event type.

See also