Рисмикдокументация
Кабинет

Вебхуки

Каждое событие приходит на ваш адрес запросом POST с JSON:

POST /hooks/whiteboard HTTP/1.1
Content-Type: application/json
User-Agent: WhiteboardStudio-Webhooks/1.0
X-Whiteboard-Event: lesson.ended
X-Whiteboard-Delivery: 1842
X-Whiteboard-Signature: t=1760432400,v1=5f2c…

{"id":1842,"type":"lesson.ended","created_at":"2026-10-14T09:45:00+00:00","data":{"lesson_id":"6f1c…","external_id":"course-7/lesson-3","started_at":"…","ended_at":"…","minutes":45,"mode":"full"}}

На любой ответ 2xx мы считаем событие доставленным. Отвечайте сразу, а обрабатывайте событие у себя в очереди.

События

typedataКогда
lesson.startedlesson_id, external_id, started_at, ended_at (null), minutes (0)в уроке двое или больше, начали считаться минуты
lesson.endedто же + ended_at, minutes — минуты этого занятия, mode — с видео или без5 минут в уроке никого не было
participant.joinedlesson_id, external_id, role, nameчеловек вошёл в урок. Приходит один раз за 6 часов на человека и урок
balance.lowbalance_seconds, balance_minutesосталось меньше часа
balance.exhaustedто жеминуты закончились
webhook.testmessageвы вызвали POST /webhook/test

id растёт и не повторяется, по нему отбрасывайте повторы. Порядок доставки не гарантирован: упорядочивайте по created_at.

Подпись

X-Whiteboard-Signature: t=<unix-время>,v1=<hex>, где v1 = HMAC-SHA256(secret, "<t>." + тело запроса). Секрет whsec_… возвращает PUT /webhook.

Проверяйте подпись по сырому телу запроса, до разбора JSON. Отклоняйте запросы старше 5 минут.

Node:

import { parseWebhook } from '@whiteboard-studio/partner'

app.post('/hooks/whiteboard', express.raw({ type: 'application/json' }), (req, res) => {
  const event = parseWebhook({ secret: process.env.WB_WEBHOOK_SECRET!, header: req.get('X-Whiteboard-Signature'), body: req.body })
  if (!event) return res.sendStatus(400)
  queue.push(event)
  res.sendStatus(204)
})

Python (Django):

from whiteboard_partner import parse_webhook

def whiteboard_hook(request):
    event = parse_webhook(settings.WB_WEBHOOK_SECRET, request.headers.get("X-Whiteboard-Signature"), request.body)
    if event is None:
        return HttpResponse(status=400)
    queue.enqueue(event)
    return HttpResponse(status=204)

PHP:

$body = file_get_contents('php://input');
parse_str(str_replace(',', '&', $_SERVER['HTTP_X_WHITEBOARD_SIGNATURE'] ?? ''), $sig);
$expected = hash_hmac('sha256', $sig['t'] . '.' . $body, getenv('WB_WEBHOOK_SECRET'));
if (abs(time() - (int)$sig['t']) > 300 || !hash_equals($expected, $sig['v1'] ?? '')) { http_response_code(400); exit; }

Повторы

Если ответ не 2xx или сервер не ответил за 10 секунд, мы повторяем отправку через 30 с, 2 мин, 10 мин, 30 мин, 1 ч, 3 ч, 6 ч и 12 ч. Всего 9 попыток примерно за сутки, после этого событие считается недоставленным.

Перенаправления (3xx) мы не выполняем: укажите конечный адрес.

Требования к адресу

  • Только https:// и только публичный адрес. Мы проверяем все IP-адреса имени и отправляем запрос именно на проверенный адрес. Адреса внутренних сетей не принимаются.
  • Сертификат должен быть действительным для имени из адреса.