Вебхуки данных
Вебхуки позволяют Bito сообщать вам об изменениях данных клиента, чтобы не приходилось опрашивать API. Адрес назначения появляется двумя путями:
webhook_uriв интеграции — Bito подписывает его автоматически при каждой установке;webhook/subscribe— через Integration API, по каждому адресу отдельно, с выбранными вами событиями.
webhook_uri: автоматическая подписка
Если в интеграции указан webhook_uri, при каждой установке интеграции Bito подписывает этот адрес на события create, update и delete следующих коллекций: currencies, customers, trades, users, productCategories, products, unitsOfMeasure, saleOrders, invoices, transactions, purchases, purchaseReturns, purchaseOrders.
status_change этим путём не отправляется — если он нужен, подпишитесь отдельно через webhook/subscribe. Удаление интеграции удаляет эту подписку, повторная установка создаёт её заново.
Список доступных событий
POST /integration/api/v2/webhook/events возвращает всё, на что можно подписаться. Имена событий — <коллекция>.<действие>.
Response (сокращённо)
{
"code": 0,
"data": [
{
"collection_name": "saleOrders",
"actions": ["create", "update", "delete", "status_change"],
"events": [
"saleOrders.create",
"saleOrders.update",
"saleOrders.delete",
"saleOrders.status_change"
]
},
{
"collection_name": "customers",
"actions": ["create", "update", "delete"],
"events": ["customers.create", "customers.update", "customers.delete"]
}
],
"time": "2025-01-15T10:30:00.000Z"
}| Действие | Когда срабатывает |
|---|---|
create | Запись создана |
update | Запись изменена |
delete | Запись удалена |
status_change | Изменился статус записи. Доступно только для коллекций, у которых статус вообще есть — какие именно, показывает вызов events. |
Подписка
POST /integration/api/v2/webhook/subscribe. Вызов работает как upsert в пределах одного destination: повторная отправка заменяет список событий для этого адреса и не трогает остальные ваши адреса.
Request
POST /integration/api/v2/webhook/subscribe
api-key: mycompany:9ad41c0e7b3f28d5610ac94e7f1b26d8
Content-Type: application/json
{
"destination": "https://your-app.example.com/bito/webhook",
"events": ["saleOrders.create", "saleOrders.status_change", "customers.update"]
}Response
{
"code": 0,
"data": {
"destination": "https://your-app.example.com/bito/webhook",
"events": ["saleOrders.create", "saleOrders.status_change", "customers.update"],
"secret": "d7c1904ae5b3268f04ad91c7e63b58f2019ce74db28a1650f3c9e7a84b2d6015"
},
"time": "2025-01-15T10:30:00.000Z"
}| Эндпоинт | Тело | Кто может вызывать |
|---|---|---|
POST webhook/events | - | Любой авторизованный пользователь интеграции |
POST webhook/get-all | - | Любой авторизованный пользователь интеграции |
POST webhook/subscribe | { destination, events[] } | Владелец аккаунта или дополнительный владелец |
POST webhook/unsubscribe | { destination } | Владелец аккаунта или дополнительный владелец |
subscribe и unsubscribe проверяют, от чьего имени выполняется запрос: вызов от имени обычного сотрудника через заголовок user_id отклоняется.
Ограничения: не более 100 разных адресов на интеграцию, не более 500 событий в одном вызове subscribe; subscribe и unsubscribe — не чаще 1 запроса в секунду.
Приём доставки
Что присылает Bito
POST https://your-app.example.com/bito/webhook
account-id: 507f1f77bcf86cd799439011
X-Bito-Signature: t=1736937000,v1=6b1f0a...c94e
Content-Type: application/json
{"collection_name":"saleOrders","action":"status_change","id":"6712f0a9c4d1e87b3a0f9911","organization_id":"6712f0a9c4d1e87b3a0f7788"}В теле передаётся идентификатор изменившейся записи, а не само изменение. Получите актуальную запись по id через обычные эндпоинты чтения v2, в рамках аккаунта из заголовка account-id.
| Заголовок | Когда есть | Значение |
|---|---|---|
account-id | Всегда | Аккаунт, в котором произошло событие |
X-Bito-Signature | Всегда | Подпись — см. ниже |
api-key | Только у подписки webhook_uri | Токен, который Bito сгенерировал для этой подписки. Это не api_key вашей интеграции — не используйте его для вызовов API. Подписки, созданные через webhook/subscribe, заголовок api-key не отправляют. |
Bito ждёт ответ 10 секунд и не следует редиректам (3xx). Ответ, не пришедший за 10 секунд или отличный от 2xx, считается неудачной доставкой. Ставьте тяжёлую работу в очередь и сразу возвращайте 2xx.
Проверка подписи
X-Bito-Signature имеет вид t=<unix-секунды>,v1=<hex>, где v1 — это HMAC_SHA256(secret, "{t}.{raw_body}"). secret — это секрет вашего клиента (client_secret); subscribe и get-all возвращают именно это значение. Он один для всех аккаунтов и всех адресов, не меняется при повторной установке и меняется только при смене секрета.
Пример проверки
import { createHmac, timingSafeEqual } from 'crypto';
// `raw` MUST be the untouched request body bytes. Do NOT JSON.parse and
// re-stringify before verifying -- key order and number formatting will differ
// and the signature will never match.
export function verify(raw: string, header: string, secret: string): boolean {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const t = parts.t;
const v1 = parts.v1;
if (!t || !v1) return false;
// Reject anything older than 5 minutes to blunt replay.
if (Math.abs(Math.floor(Date.now() / 1000) - Number(t)) > 300) return false;
const expected = createHmac('sha256', secret).update(`${t}.${raw}`).digest('hex');
const a = Buffer.from(expected, 'hex');
const b = Buffer.from(v1, 'hex');
return a.length === b.length && timingSafeEqual(a, b);
}WARNING
Хешируйте сырые байты тела запроса. Это самая частая ошибка при работе с вебхуками Bito. Если вы сделаете JSON.parse и заново сериализуете тело перед вычислением HMAC, байты, которые вы хешируете, разойдутся с теми, что хешировал Bito, как только отличится порядок ключей, пробелы или формат чисел — и подпись никогда не совпадёт. Сначала проверьте подпись по нетронутому телу, и только потом разбирайте JSON.
Чего вебхуки не гарантируют
WARNING
- Одна попытка, без повторов. Нет ни повторной отправки, ни задержек, ни очереди недоставленных. Если ваш адрес недоступен, событие потеряно.
- Нет гарантии порядка. Доставки могут приходить не по порядку.
- Массовые операции не создают событий вообще. Массовый импорт и групповые изменения не отправляют вебхуки. Считайте вебхук поводом перечитать данные, а не самими данными, и периодически сверяйтесь полным чтением. Интеграция, полагающаяся на полноту вебхуков, незаметно разойдётся с реальностью.