Вебхуки
Вебхуки позволяют панели самой сообщать внешним системам о том, что произошло:
создан пользователь, упала нода, закончился трафик. Панель отправляет запрос
POST с JSON-телом на ваш адрес, поэтому принимать его может скрипт, CRM,
система мониторинга или чат-бот.
Включение
Вебхуки выключены по умолчанию. Настройки задаются в .env панели:
| Переменная | По умолчанию | Описание |
|---|---|---|
WEBHOOK_ENABLED | false | включает отправку вебхуков |
WEBHOOK_URL | — | адрес получателя; несколько адресов — через запятую |
WEBHOOK_SECRET_HEADER | — | секрет подписи payload, ровно 64 символа [a-zA-Z0-9] |
WEBHOOK_ENABLED=true
WEBHOOK_URL=https://example.com/xlada/webhook
WEBHOOK_SECRET_HEADER=vsmu67Kmg6R8FjIOF1WUY8LWBHie4scdEqrfsKmyf4IAf8dY3nFS0wwYHkhh6ZvQ
При WEBHOOK_ENABLED=true обе переменные обязательны. WEBHOOK_URL должен
начинаться с http:// или https://, иначе контейнер не стартует. Секрет
подписи храните только в .env — по нему получатель проверяет подлинность
запросов.
Какие события отправлять, решает файл
/var/lib/remnawave/configs/notifications/notifications-config.yml внутри
контейнера. По умолчанию в образе включены все события по обоим каналам.
Событие, которого нет в файле, считается выключенным. Чтобы правки
сохранялись, смонтируйте свой файл поверх каталога
/var/lib/remnawave/configs/notifications.
events:
user.created:
telegram: true
webhook: true
user.expired:
telegram: false
webhook: true
# дополнительный адрес только для этого события
additionalWebhookUrls:
- https://example.com/xlada/critical
Подпись payload
С каждым запросом панель отправляет служебные заголовки:
| Заголовок | Значение |
|---|---|
X-XLADA-Signature | HMAC-SHA256 от тела запроса, в hex |
X-XLADA-Timestamp | время события в ISO 8601 |
Content-Type | application/json |
User-Agent | XLADA |
Подпись считается как HMAC-SHA256(секрет, тело_запроса) и сравнивается с
заголовком. Сравнивать нужно именно исходное тело, а не разобранный JSON.
import { createHmac, timingSafeEqual } from 'node:crypto';
function isValidSignature(rawBody, secret, signature) {
const expected = createHmac('sha256', secret).update(rawBody).digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(signature ?? '');
return a.length === b.length && timingSafeEqual(a, b);
}
Формат payload
В теле всегда есть четыре поля: scope, event, timestamp и data.
У событий пользователя дополнительно есть meta.
{
"scope": "service",
"event": "service.panel_started",
"timestamp": "2026-01-07T11:57:29.426Z",
"data": {
"panelVersion": "1.1.1"
}
}
Поле meta заполняется только у событий-напоминаний:
| Поле | Событие | Значение |
|---|---|---|
notConnectedAfterHours | user.not_connected | сколько часов пользователь не подключался |
expiration | user.expiration | часы относительно expireAt: минус — до истечения, плюс — после |
У всех остальных событий meta равно null.
Список событий
Область (scope) | События |
|---|---|
user | user.created, user.modified, user.deleted, user.revoked, user.disabled, user.enabled, user.limited, user.expired, user.traffic_reset, user.first_connected, user.bandwidth_usage_threshold_reached, user.not_connected, user.expiration |
user_hwid_devices | user_hwid_devices.added, user_hwid_devices.deleted |
node | node.created, node.modified, node.disabled, node.enabled, node.deleted, node.connection_lost, node.connection_restored, node.traffic_notify |
service | service.panel_started, service.login_attempt_failed, service.login_attempt_success, service.subpage_config_changed, service.api_token_created, service.api_token_deleted |
errors | errors.bandwidth_usage_threshold_reached_max_notifications |
crm | crm.infra_billing_node_payment_in_7_days, crm.infra_billing_node_payment_in_48hrs, crm.infra_billing_node_payment_in_24hrs, crm.infra_billing_node_payment_due_today, crm.infra_billing_node_payment_overdue_24hrs, crm.infra_billing_node_payment_overdue_48hrs, crm.infra_billing_node_payment_overdue_7_days |
torrent_blocker | torrent_blocker.report |
События user.not_connected и user.expiration приходят только при включённых
NOT_CONNECTED_USERS_NOTIFICATIONS_ENABLED и EXPIRATION_NOTIFICATIONS_ENABLED.
События user_hwid_devices.* появляются, только когда включён лимит устройств,
и уходят исключительно в вебхуки — в Telegram они не дублируются.
Повторные попытки
Доставка идёт через очередь: на каждый адрес создаётся отдельная задача с ограничением в 100 одновременных отправок. Если получатель не ответил, запрос повторяется 3 раза с паузой 5 секунд — всего до четырёх попыток. После этого ошибка пишется в лог панели, а задача остаётся в очереди с ограниченным хранением.
В notifications-config.yml можно задать дополнительные адреса для конкретного
события (additionalWebhookUrls). Они добавляются к адресам из WEBHOOK_URL,
а не заменяют их.
Отдавайте на вебхук 2xx сразу, а обработку выносите в фон. Пока получатель
не ответит, панель держит соединение, а медленные ответы копят очередь.
Что дальше
- Уведомления — Telegram, пороги трафика и напоминания.
- Переменные окружения — все переключатели панели.
- API Specification — схемы данных для каждого события.
- Безопасность панели — как защитить приёмник вебхуков.