For AI agents: markdown of this page — /docs-content-en/infra/event-subscriptions/handler.md documentation index — /llms.txt
Application-side handler
Receiving a delivered event in the application on a Black Hole server. The platform delivers each event as a POST request to the application subdomain at the appPath path in application/x-www-form-urlencoded format — this is the standard Bitrix24 event body. The handler reads event and data, checks auth[application_token] against its own application_token, and responds 2xx. The platform retries delivery on a non-2xx response and wakes a sleeping server.
// Express handler: Bitrix24 form fields arrive in req.body
import express from 'express'
const APP_TOKEN = process.env.APPLICATION_TOKEN // application_token of the app
const app = express()
app.use(express.urlencoded({ extended: true }))
app.post('/api/webhooks/b24', (req, res) => {
// Verify the app token — events without a valid token are rejected
if (req.body.auth?.application_token !== APP_TOKEN) {
return res.status(403).json({ ok: false })
}
const event = req.body.event // e.g. "ONTASKADD"
const taskId = req.body.data?.FIELDS?.ID // Entity ID from the event
// A repeated event must not be processed twice — the platform may deliver it again
console.log(`Event ${event}, task #${taskId}`)
// Respond 2xx, otherwise the platform will retry delivery
res.status(200).json({ ok: true })
})
Event body
Example of the body that arrives in the handler:
event=ONTASKADD&data[FIELDS][ID]=20152&ts=1780843200&auth[access_token]=<Bitrix24 token>&auth[refresh_token]=<Bitrix24 refresh token>&auth[application_token]=<app application_token>&auth[domain]=your-portal.bitrix24.com&auth[member_id]=...
The body carries the Bitrix24 user tokens. The handler checks auth[application_token] to reject foreign events. auth[access_token] is a Bitrix24 token for direct Bitrix24 REST calls. It does not work as a V1 API key directly, but it can be exchanged for a session token — this is the second authorization method below.
The body has no auth[access_token] or auth[refresh_token] in three cases:
- The key that owns the server, or the authorization key of the subscribed app, does not change Bitrix24 data.
- Either key is deleted or revoked, or the app has no active authorization key.
- The app address
appUrlpoints outside the platform — neither to a Black Hole subdomain nor to a custom domain connected to a server. The platform never sends the tokens to an external address, whatever the key modes.
auth[application_token], auth[user_id] and the event data still arrive. The second authorization method is not available to such a handler — call the V1 API with a personal key using the first method, and the calls run on behalf of the key owner.
When the address points to a platform server, an event without the tokens is not delivered if it was raised by a user whom that server's access policy does not admit. An event sent to an external address is not filtered by the access policy — check auth[user_id] in the handler yourself.
To receive the tokens again, switch both keys to the Read and write mode — Key access mode. An external address does not receive them even then.
Authorizing V1 API calls from the handler
The handler often calls the V1 API in response to an event — reading a related entity or creating a new one. The platform delivers the event itself and does not add authorization headers: the request carries only Content-Type and the event body. The handler authorizes its calls itself. There are two working methods — the choice depends on whose key the calls should run under.
Method 1. Personal key `vibe_api_`
The shortest path: a personal key in the X-Api-Key header, no session needed. Calls run on behalf of the key owner. Suitable when they do not have to run under the same application as the subscription.
app.post('/api/webhooks/b24', async (req, res) => {
if (req.body.auth?.application_token !== APP_TOKEN) {
return res.status(403).json({ ok: false })
}
const taskId = req.body.data?.FIELDS?.ID
// V1 API call — personal key in the X-Api-Key header
const task = await fetch(
`https://vibecode.bitrix24.com/v1/tasks/${taskId}`,
{ headers: { 'X-Api-Key': process.env.VIBE_API_KEY } },
).then((r) => r.json())
console.log('Task from event:', task.data)
res.status(200).json({ ok: true })
})
A personal vibe_api_ key is created in your account — the creation procedure is described in Keys and authorization. Bitrix24 scopes are attached to the key at issue time: mark the data groups the handler accesses (for the tasks example — tasks).
Method 2. Authorization key `vibe_app_` under the same app
If calls must run under the same authorization key as the subscription, that is, on behalf of the Bitrix24 account user, the handler needs a session. The event body carries this user's tokens: auth[access_token], auth[member_id], auth[domain], and auth[refresh_token]. They are exchanged for a vibe_session_ session token via POST /v1/oauth/placement-session with the body { app_key, access_token, member_id, domain, refresh_token }. Pass the resulting session in Authorization: Bearer alongside X-Api-Key:
curl https://vibecode.bitrix24.com/v1/tasks/20152 \
-H "X-Api-Key: YOUR_APP_KEY" \
-H "Authorization: Bearer USER_SESSION_TOKEN"
How to obtain a session from event tokens — the Application on your own server section in Keys and authorization. The session lives for 24 hours, with no renewal.
Common authorization mistakes
Three header combinations that will not work, and the response code for each:
| What was passed | Response | Reason |
|---|---|---|
Authorization key vibe_app_ in X-Api-Key without Bearer |
401 TOKEN_MISSING |
The key itself has no portal tokens — you need a session in Authorization: Bearer alongside X-Api-Key (Method 2) |
Authorization key vibe_app_ in Authorization: Bearer |
401 WRONG_AUTH_SCHEME |
Bearer carries a vibe_session_ session token, not the app key |
The auth[access_token] token from the event in Authorization: Bearer |
401 MISSING_API_KEY |
This is a Bitrix24 token, not a V1 key. In Bearer the V1 API accepts only vibe_ tokens — a Bitrix24 token is first exchanged for a session (Method 2) |
A detailed breakdown of TOKEN_MISSING — Bot platform troubleshooting.
Known specifics
- Delivery goes through the tunnel — the application needs no public URL. The platform registers the handler on its side, Bitrix24 sends the event there, and the platform delivers it to the application through the Black Hole tunnel with an
auth[application_token]check. The application needs no public address of its own. - A sleeping server wakes up for delivery. If the server is asleep at the moment of the event, the platform wakes it and delivers the event after the tunnel connects. So the first event after an idle period arrives with a wake-up delay.
- Delivery is reliable, but not infinite. Failed deliveries are retried with a growing delay. Once all attempts are exhausted, the delivery moves to
FAILED, the subscription is markedDEGRADED, and the owner receives a notification. Delivery statuses are visible inGET /v1/infra/servers/:id/event-subscriptions. - A repeated delivery must not duplicate the effect. The same event may arrive again. Process it so that a second receipt with the same
data[FIELDS][ID]does not do the work twice.