Skip to content

Вебхуки

Вебхуки сообщают о событиях школы сразу, как только они произошли: новый ученик, оплата, завершённый урок, зачисление в группу, новый лид. Без них тот же результат даёт только периодический опрос GET /students или GET /payments с фильтром updated_since — рабочий вариант, но с задержкой в размер интервала опроса и с лишними запросами, когда событий не было. Вебхук устраняет и то, и другое: BigBen CRM сам отправляет POST-запрос на ваш URL в момент события.

Как подключить

Подписки на вебхуки настраиваются внутри CRM. Публичный API-ключ (bb_<prefix>_<secret>) для этого не подходит — управление подписками живёт в другом контуре авторизации. Заводит подписку сотрудник школы с ролью владельца или администратора:

CRM → Настройки → Интеграции → карточка «Вебхуки» → «Настроить».

В панели школа:

  1. указывает URL приёмника — обычный HTTPS-эндпоинт на вашей стороне;
  2. отмечает события, на которые подписывается (раздел Каталог событий);
  3. получает секрет подписки — строку для проверки подписи. Секрет показывается один раз, в момент создания подписки, и восстановить его позже нельзя — только выпустить новую подписку;
  4. может отправить тестовое событие и посмотреть журнал последних 50 доставок (статус, HTTP-код ответа, число попыток) прямо в этой панели.

Внимание

Если вы разрабатываете сайт или сервис школы, но сами не имеете доступа к её CRM — секрет и URL вам должен передать администратор школы. Публичный API-ключ для этого не подходит, им нельзя ни создать подписку, ни прочитать секрет.

Каталог событий

СобытиеКогда срабатывает
student.createdВ школе зарегистрирован новый ученик
lead.createdСоздан новый лид (в том числе через POST /leads, см. Сценарии использования)
group.enrolledУченика зачислили в группу
lesson.completedПреподаватель отметил посещение урока учеником (статус «был»)
payment.receivedПроведена оплата

Каждое событие приходит POST-запросом с телом:

json
{
  "event": "student.created",
  "account_id": 12345,
  "payload": { "...": "..." },
  "timestamp": "2026-08-14T10:15:00+00:00"
}

account_id — школа-владелец подписки, timestamp — момент отправки в ISO8601. Содержимое payload зависит от события.

student.created

json
{
  "event": "student.created",
  "account_id": 12345,
  "payload": {
    "student_id": 88123,
    "student_name": "Иванова Мария",
    "filial_id": 3
  },
  "timestamp": "2026-08-14T10:15:00+00:00"
}

lead.created

json
{
  "event": "lead.created",
  "account_id": 12345,
  "payload": {
    "lead_id": 55210,
    "name": "Пётр Сидоров",
    "phone": "+79261234567",
    "source": "api"
  },
  "timestamp": "2026-08-14T10:16:00+00:00"
}

group.enrolled

json
{
  "event": "group.enrolled",
  "account_id": 12345,
  "payload": {
    "enrollment_id": 91004,
    "student_id": 88123,
    "group_id": 410,
    "group_name": "Английский B1, вт/чт 18:00",
    "filial_id": 3
  },
  "timestamp": "2026-08-14T10:20:00+00:00"
}

lesson.completed

json
{
  "event": "lesson.completed",
  "account_id": 12345,
  "payload": {
    "student_id": 88123,
    "lesson_id": 30217,
    "group_id": 410,
    "teacher_id": 512,
    "visit_status": 1,
    "filial_id": 3
  },
  "timestamp": "2026-08-14T18:05:00+00:00"
}

payment.received

json
{
  "event": "payment.received",
  "account_id": 12345,
  "payload": {
    "student_id": 88123,
    "payment_id": 77001,
    "amount": 450000,
    "payment_amount": 450000,
    "payment_type": "card",
    "group_id": 410,
    "filial_id": 3
  },
  "timestamp": "2026-08-14T11:00:00+00:00"
}

amount и payment_amount — сумма в исходном виде, как она хранится в CRM (в примере выше это копейки в записи оплаты). Не полагайтесь на конкретный порядок величины без сверки с суммой, которую видит школа в интерфейсе — для точной суммы в копейках используйте GET /payments (поле amount_kopecks, см. Справочник API).

Проверка подписи

Каждый запрос приходит с заголовком X-BigBen-Signature — hex-строка HMAC-SHA256 от тела запроса (ровно тех байт, что пришли в теле, без повторной сериализации JSON), посчитанная на секрете вашей подписки. Проверяйте подпись до обработки события и сравнивайте результат функцией постоянного времени — обычное сравнение строк === через побайтовое сравнение раскрывает секрет через тайминг-атаку.

PHP

php
<?php

function verifyBigBenSignature(string $rawBody, string $signatureHeader, string $secret): bool
{
    $expected = hash_hmac('sha256', $rawBody, $secret);

    return hash_equals($expected, $signatureHeader);
}

$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_BIGBEN_SIGNATURE'] ?? '';
$secret = getenv('BIGBEN_WEBHOOK_SECRET');

if (!verifyBigBenSignature($rawBody, $signature, $secret)) {
    http_response_code(401);
    exit;
}

$event = json_decode($rawBody, true);
// обработка $event['event'], $event['account_id'], $event['payload']

http_response_code(200);

Node.js

js
const crypto = require('crypto')
const express = require('express')

const app = express()

// важно: нужны СЫРЫЕ байты тела, не распарсенный JSON — подпись считается по ним
app.use(express.raw({ type: 'application/json' }))

function verifyBigBenSignature(rawBody, signatureHeader, secret) {
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex')
  const expectedBuf = Buffer.from(expected, 'utf8')
  const signatureBuf = Buffer.from(signatureHeader || '', 'utf8')

  if (expectedBuf.length !== signatureBuf.length) {
    return false
  }

  return crypto.timingSafeEqual(expectedBuf, signatureBuf)
}

app.post('/webhooks/bigben', (req, res) => {
  const signature = req.header('X-BigBen-Signature')
  const secret = process.env.BIGBEN_WEBHOOK_SECRET

  if (!verifyBigBenSignature(req.body, signature, secret)) {
    return res.sendStatus(401)
  }

  const event = JSON.parse(req.body.toString('utf8'))
  // обработка event.event, event.account_id, event.payload

  res.sendStatus(200)
})

app.listen(3000)

Требования к эндпоинту приёмника

  • Отвечайте быстро и 2xx-кодом. BigBen CRM ждёт ответ до 10 секунд; любой код вне диапазона 2xx или таймаут считаются неудачей и запускают ретрай. Если обработка события медленная (отправка писем, синхронизация с внешней системой) — сохраните событие и ответьте 200 сразу, обработку вынесите в фоновую очередь на своей стороне.
  • Обрабатывайте события идемпотентно. Ретраи и, реже, дублирующие доставки возможны — держите на своей стороне ключ дедупликации, например по паре (event, payload.student_id/payload.lead_id/…, timestamp) или по внутреннему id из payload, и игнорируйте повтор, если событие уже обработано.
  • Проверяйте подпись на каждый запрос, включая повторные доставки. Секретность URL приёмника защитой не считается: адрес утекает в логи прокси и историю браузера.

Ретраи и что происходит после неудачи

Если приёмник ответил не 2xx или не ответил вовсе, BigBen CRM повторяет доставку того же события до 4 раз всего, с паузами перед повторными попытками:

ПопыткаПауза перед ней
2-я1 минута
3-я5 минут
4-я30 минут
2 часа (после 4-й, если бы была 5-я)

Внимание

Порядок пауз — 1 мин / 5 мин / 30 мин / 2 ч перед попытками №2–4 соответственно. Если 4-я попытка тоже не удалась, доставка помечается провалившейся, ретраев больше не будет.

После 4-й неудачной попытки подписка помечается проблемной, и все сотрудники школы с полным доступом получают уведомление в CRM о том, что доставка по URL не проходит. Сама подписка не отключается автоматически — она продолжает получать новые события, пока школа не отключит её вручную.

Отладка

  • В панели «Вебхуки» (CRM → Настройки → Интеграции) школа видит журнал последних 50 доставок на подписку: событие, статус, HTTP-код ответа, число попыток.
  • Там же можно отправить тестовое событие (webhook.test) на текущий URL и сразу увидеть результат — удобно проверить, что эндпоинт вообще отвечает 2xx, до того как ждать реального события.
  • Если приёмник стабильно недоступен, проще всего временно отключить подписку в панели (без удаления — история доставок сохраняется) и включить обратно, когда эндпоинт снова готов принимать запросы.

Коды ошибок публичного API — на отдельной странице: Коды ошибок. Лимиты на сам публичный API (не на вебхуки — они не считаются в бюджет запросов ключа) — Лимиты запросов.