> ## Documentation Index
> Fetch the complete documentation index at: https://faq.exode.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Вебхуки

> Как настроить вебхуки школы: доступные события, формат запроса, проверка подписи, повторные попытки и автоматическое отключение эндпоинта.

Вебхуки автоматически уведомляют ваши внешние сервисы о событиях в школе: регистрации, оплате (только в коммерческой школе), записи на курс, завершении курса. Когда событие происходит, платформа отправляет POST-запрос с данными события на адрес, который вы указали. Так школу связывают с CRM, ботами, таблицами и собственными сервисами без постоянного опроса API.

## Где настроить

Вебхуки находятся в разделе **Панель управления → Моя школа → Для разработчиков → Вебхуки** (в корпоративной школе — **Панель управления → Компания → Для разработчиков → Вебхуки**). Работать с ними может сотрудник с правом **Управление настройками школы** (в корпоративной школе — **Управление настройками компании**).

* В школе может быть не больше 5 вебхуков. Удалённые вебхуки в лимит не входят.
* Каждый вебхук — это один адрес и набор событий, на которые он подписан.
* Вебхуки настраиваются только в кабинете. Через API школы ими управлять нельзя.

## Как создать вебхук

**1. Нажмите «Создать вебхук»**

Откройте раздел **Вебхуки** (в корпоративной школе — **Вебхуки компании**) и нажмите **Создать вебхук**.

**2. Укажите адрес**

В поле **Ссылка** введите адрес вашего сервиса, например [https://my-service.com/api/exode](https://my-service.com/api/exode). Длина адреса — до 255 символов. Используйте HTTPS.

**3. Добавьте заметку**

В поле **Служебная заметка** напишите, для чего этот вебхук, например «Интеграция с AmoCRM». Заметка видна только в списке вебхуков, длина — до 255 символов.

**4. Выберите события**

Отметьте события в списке **Выбранные события**. Счётчик справа показывает, сколько событий выбрано.

**5. Проверьте доставку**

Нажмите на стрелку рядом с событием, чтобы отправить тестовое событие на указанный адрес. Убедитесь, что ваш сервис принял запрос.

**6. Сохраните вебхук**

Нажмите **Создать**. При создании платформа генерирует секретный ключ для проверки подписи.

В списке вебхуков у каждой строки есть переключатель **Активен** / **Не активен** и меню действий: **Редактировать**, **Копировать подпись** и **Удалить вебхук**.

## События

Для подписки доступны 13 событий (в корпоративной школе — 11):

| Событие в кабинете | Идентификатор | Когда срабатывает |
| - | - | - |
| Пользователь зарегистрировался | `UserSignedUp` | Новый пользователь сам зарегистрировался в школе |
| Пользователь заполнил данные | `UserAcquainted` | Пользователь заполнил и отправил форму знакомства |
| Пользователь подключил Telegram | `UserTgConnected` | Пользователь привязал или сменил Telegram |
| Пользователь создан через LMS | `UserCreatedViaLms` | Сотрудник школы создал пользователя вручную |
| Запись на бесплатный курс | `ProductEnrolledToFree` | Пользователь записался на бесплатный курс |
| Запись на курс через LMS | `ProductEnrolledViaLms` | Сотрудник школы выдал доступ вручную |
| Запись пользователя на курс через оплату | `ProductEnrolledViaPayment` | Доступ выдан после успешной оплаты (только в коммерческой школе) |
| Запись по ссылке-приглашению | `ProductEnrolledByInviteLink` | Пользователь записался по ссылке-приглашению |
| Платеж завершён | `PaymentCompleted` | Платёж успешно оплачен (только в коммерческой школе) |
| Прогресс курса изменён | `CourseProgressChanged` | У ученика изменился статус прогресса по уроку |
| Практика успешно пройдена (проверена) | `CourseLessonPracticeCompleted` | Практика перешла в статус «Пройдена» |
| Курс завершен | `CourseCompleted` | Ученик завершил все доступные модули и уроки курса |
| Сертификат выдан | `CertificateIssued` | Ученику выдан сертификат |

В школах для корпоративного обучения событий **Платеж завершён** и **Запись пользователя на курс через оплату** нет.

> [!NOTE]
>
> Подписаться можно только на события из таблицы. Событий о входе и выходе из системы, регистрации по реферальной ссылке, отправке практики на проверку, автопроверке практики, возврате средств и окончании подписки нет.

## Формат запроса

Платформа отправляет POST-запрос с телом в формате JSON. У тела одинаковая структура для всех событий:

| Поле | Что содержит |
| - | - |
| `event` | Идентификатор события из таблицы выше |
| `timestamp` | Момент события в формате ISO 8601, время UTC |
| `idempotencyKey` | Уникальный ключ события. Он одинаковый для всех ваших вебхуков и не меняется при повторных попытках |
| `data` | Данные события: пользователь, курс, платёж, доступ — состав зависит от события |

```json theme={null}
{
  "data": { "...": "зависит от события" },
  "event": "PaymentCompleted",
  "timestamp": "2026-10-15T09:30:12.512Z",
  "idempotencyKey": "7qqQL3bE0o8R8d3X"
}
```

В `data` попадают только поля из публичного описания событий, лишние поля вырезаются. Полный состав `data` для каждого события описан в [документации API](https://docs.exode.biz/ru/exode-api/webhooks/about).

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

В каждом запросе есть заголовок `signature`. Это HMAC-SHA256 от сырого тела запроса, посчитанный на секретном ключе вебхука и записанный в шестнадцатеричном виде.

**1. Скопируйте секретный ключ**

В меню вебхука выберите **Копировать подпись** — в буфер попадёт секретный ключ из 64 символов. Та же ссылка есть в форме редактирования над полем **Ссылка**. Сохраните ключ в настройках своего сервиса.

**2. Посчитайте подпись на своей стороне**

Посчитайте HMAC-SHA256 от тела запроса в том виде, в каком оно пришло. Не разбирайте JSON и не собирайте его заново: другой порядок полей или пробелы дадут другую подпись.

**3. Сравните подписи**

Если подписи не совпадают, отклоните запрос.

```javascript theme={null}
import crypto from 'crypto';
import express from 'express';

const app = express();

app.post('/webhooks/exode', express.raw({ type: 'application/json' }), (req, res) => {
    const rawBody = req.body.toString('utf8');
    const received = req.header('signature') || '';

    const expected = crypto
        .createHmac('sha256', process.env.EXODE_WEBHOOK_SECRET)
        .update(rawBody)
        .digest('hex');

    const isValid = received.length === expected.length
        && crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));

    if (!isValid) {
        return res.status(401).send('invalid signature');
    }

    const payload = JSON.parse(rawBody);

    /* Обработайте событие и запомните payload.idempotencyKey */

    res.sendStatus(200);
});
```

> [!WARNING]
>
> Секретный ключ — конфиденциальные данные. Храните его только на сервере и не публикуйте в кастомном коде школы или в открытых репозиториях.

## Доставка и повторные попытки

| Параметр | Значение |
| - | - |
| Метод | POST, тело в JSON |
| Время на ответ | 15 секунд |
| Успешный ответ | HTTP-коды 200, 201 и 202 |
| Число попыток | До 5: первая отправка и до 4 повторов |
| Паузы между попытками | Около 11, 33, 77 и 165 минут. Последняя попытка — примерно через 4 часа 46 минут после первой |
| Порядок доставки | Не гарантируется |

Любой другой ответ, превышение 15 секунд или сетевая ошибка считаются неудачей. После 5 неудачных попыток событие больше не отправляется. Если вебхук выключили, пока шли повторы, оставшиеся попытки отменяются.

> [!TIP]
>
> Отвечайте кодом 200 сразу после проверки подписи, а тяжёлую обработку выполняйте в фоне. Используйте `idempotencyKey`, чтобы не обработать одно событие дважды: при повторной попытке ключ тот же.

## Автоматическое отключение

Если вебхук не принял ни одного события 14 дней подряд, платформа выключает его. Владелец школы получает уведомление и письмо **Webhook отключён** с адресом вебхука.

Выключенный вебхук не получает новые события. Чтобы снова получать события, исправьте адрес или сервис и включите вебхук переключателем в списке. После ручного включения отсчёт 14 дней начинается заново.

## Тестовые события

Тестовое событие отправляется кнопкой-стрелкой рядом с событием в форме вебхука. Кнопка есть только у событий, доступных для выбора.

* Тест отправляется на адрес из поля **Ссылка**, даже если вебхук ещё не сохранён.
* В теле — пример данных события в том же формате, что и у настоящих событий.
* Тест отправляется один раз, без повторных попыток.
* Тест из формы сохранённого вебхука подписывается его секретным ключом. Тест из формы нового вебхука подписывается временным ключом, поэтому проверить его подпись не получится.
* Можно отправить не больше 100 тестов за 10 минут.

## Частые вопросы

> **Почему мой сервис отвечает 204, а события приходят повторно?**
>
> Успешными считаются только ответы 200, 201 и 202. Ответ 204 платформа считает неудачей и повторяет отправку. Отвечайте кодом 200.

> **Почему подпись не совпадает?**
>
> Чаще всего подпись считают от заново собранного JSON, а не от сырого тела запроса. Считайте HMAC-SHA256 от тела в том виде, в каком оно пришло. Также проверьте, что используете ключ из **Копировать подпись** именно этого вебхука и что тест отправлен из формы сохранённого вебхука.

> **Сколько раз платформа повторяет отправку?**
>
> Всего до 5 попыток: первая и 4 повтора с паузами около 11, 33, 77 и 165 минут. После этого событие считается недоставленным.

> **Почему вебхук выключился сам?**
>
> Вебхук выключается, если 14 дней подряд не принял ни одного события. Владелец школы получает об этом уведомление и письмо. Исправьте адрес или работу сервиса и включите вебхук переключателем.

> **Можно ли создать больше 5 вебхуков?**
>
> Нет, в школе может быть не больше 5 вебхуков. Удалите неиспользуемые или подпишите один вебхук на несколько событий. Если лимита не хватает, напишите в поддержку.

> **Приходят ли события в том порядке, в котором произошли?**
>
> Нет, порядок не гарантируется: события отправляются параллельно, а неудачные попытки повторяются позже. Ориентируйтесь на поле `timestamp`.

> **Почему в списке нет событий о входе в систему или возврате средств?**
>
> Такие события не отправляются, поэтому подписаться на них нельзя. Для подписки доступны 13 событий из таблицы выше (в корпоративной школе — 11).

> **Придёт ли вебхук, если я подпишу два адреса на одно событие?**
>
> Да. Событие уйдёт на каждый активный вебхук, который на него подписан, с одинаковым `idempotencyKey`.

> **Где посмотреть, что именно приходит в данных события?**
>
> Отправьте тестовое событие на свой адрес и посмотрите тело запроса. Полное описание данных каждого события — в [документации API](https://docs.exode.biz/ru/exode-api/webhooks/about).

***

*Обновлено: 2026-10-17 07:07 UTC*


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.