Перейти к основному содержимому

Получение вебхуков

Обзор

Remnawave может отправлять вебхуки для многих событий.

Конфигурация

Конфигурация .env

WEBHOOK_ENABLED=true
WEBHOOK_URL=https://your-server.com/webhook
WEBHOOK_SECRET_HEADER=your-secret-header
ПеременнаяОписание
WEBHOOK_ENABLEDВключить вебхуки.
WEBHOOK_URLURL для отправки вебхука. (должен начинаться с https:// или http://). Можно указать несколько URL, разделённых запятыми (без пробелов)
WEBHOOK_SECRET_HEADERЭтот заголовок будет использоваться для подписи полезной нагрузки вебхука. (допускаются только aA-zZ, 0-9)

Заголовки

Remnawave будет отправлять следующие заголовки с полезной нагрузкой вебхука:

  • X-Remnawave-Signature - Подпись полезной нагрузки вебхука. (подписывается с помощью WEBHOOK_SECRET_HEADER)
  • X-Remnawave-Timestamp - Метка времени полезной нагрузки вебхука.

Полезная нагрузка

Полезная нагрузка будет JSON-объектом.

Пример полезной нагрузки вебхука
{
"scope": "service",
"event": "service.panel_started",
"timestamp": "2026-01-07T11:57:29.426Z",
"data": {
"panelVersion": "2.5.0"
}
}

Свойства:

  • scope - Область вебхука. (Начиная с v2.5.0)

    • user - События пользователя
    • user_hwid_devices - События устройств HWID пользователя
    • node - События узла
    • service - События сервиса
    • crm - События инфра-биллинга
    • torrent_blocker - События блокировщика торрентов
    • errors - События ошибок
  • event - Произошедшее событие.

  • timestamp - Метка времени в формате ISO 8601.

  • data - Данные, связанные с событием.

подсказка

Подробная схема полезной нагрузки для каждой области доступна в документации OpenAPI.

👉 Обратитесь к Model Link в каждом разделе области ниже.

Область: user

OpenAPI Model: RemnawaveWebhookUserEventsDto
Model Link: https://rw.gy/api/#model/RemnawaveWebhookUserEventsDto

Доступные события (свойство event):

  • user.created - Пользователь создан
  • user.modified - Пользователь изменён
  • user.deleted - Пользователь удалён
  • user.revoked - Пользователь отозван
  • user.disabled - Пользователь отключён
  • user.enabled - Пользователь включён
  • user.limited - Пользователь ограничен
  • user.expired - Срок действия пользователя истёк
  • user.traffic_reset - Трафик пользователя сброшен
  • user.expires_in_72_hours - Срок действия пользователя истекает через 72 часа (удалено в v.2.8.0, используйте user.expiration)
  • user.expires_in_48_hours - Срок действия пользователя истекает через 48 часов (удалено в v.2.8.0, используйте user.expiration)
  • user.expires_in_24_hours - Срок действия пользователя истекает через 24 часа (удалено в v.2.8.0, используйте user.expiration)
  • user.expired_24_hours_ago - Срок действия пользователя истёк 24 часа назад (удалено в v.2.8.0, используйте user.expiration)
  • user.first_connected - Пользователь впервые подключился
  • user.bandwidth_usage_threshold_reached - Достигнут порог использования полосы пропускания
  • user.not_connected - Пользователь не подключён (активно только когда NOT_CONNECTED_USERS_NOTIFICATIONS_ENABLED равно true в .env)
  • user.expiration - Уведомления об истечении срока действия пользователя (активно только когда EXPIRATION_NOTIFICATIONS_ENABLED равно true в .env.)

Типы Remnawave Typescript SDK:

import { TRemnawaveWebhookUserEvent, RemnawaveWebhookUserEvents } from '@remnawave/backend-contract'

Объект meta

Большинство пользовательских событий содержат meta: null. Поле заполняется только для событий-уведомлений, где оно предоставляет контекст, вызвавший вебхук.

ПолеТипПрисутствует дляОписание
notConnectedAfterHoursnumber | nulluser.not_connectedЧасов офлайн. Соответствует порогу из NOT_CONNECTED_USERS_NOTIFICATIONS_AFTER_HOURS, вызвавшему событие.
expirationnumber | nulluser.expirationЗнаковое смещение в часах относительно истечения срока действия (expireAt). Соответствует значению из EXPIRATION_NOTIFICATIONS.

Знак поля expiration

Значение expiration знаковое и кодирует направление относительно момента истечения:

  • Отрицательное — сработало до истечения: истекает через |N| часов (например, -72 → истекает через 72 часа).
  • Положительное — сработало после истечения: истёк N часов назад (например, 24 → истёк 24 часа назад).
примечание

Для всех событий, кроме user.not_connected и user.expiration, meta равно null. При наличии meta заполнено только поле для текущего события — другое поле остаётся null.

Область: user_hwid_devices

OpenAPI Model: RemnawaveWebhookUserHwidDevicesEventsDto​
Model Link: https://rw.gy/api/#model/RemnawaveWebhookUserHwidDevicesEventsDto

Доступные события:

  • user_hwid_devices.added - Устройство HWID пользователя добавлено
  • user_hwid_devices.deleted - Устройство HWID пользователя удалено
import {
TRemnawaveWebhookUserHwidDevicesEvent,
RemnawaveWebhookUserHwidDevicesEvents
} from '@remnawave/backend-contract'

Область: node

OpenAPI Model: RemnawaveWebhookNodeEventsDto​
Model Link: https://rw.gy/api/#model/RemnawaveWebhookNodeEventsDto

Доступные события:

  • node.created - Узел создан
  • node.modified - Узел изменён
  • node.disabled - Узел отключён
  • node.enabled - Узел включён
  • node.deleted - Узел удалён
  • node.connection_lost - Соединение с узлом потеряно
  • node.connection_restored - Соединение с узлом восстановлено
  • node.traffic_notify - Уведомление о трафике узла
import { TRemnawaveWebhookNodeEvent, RemnawaveWebhookNodeEvents } from '@remnawave/backend-contract'

Область: service

OpenAPI Model: RemnawaveWebhookServiceEventsDto
Model Link: https://rw.gy/api/#model/RemnawaveWebhookServiceEventsDto

Доступные события:

  • service.panel_started - Панель запущена
  • service.login_attempt_failed - Попытка входа не удалась
  • service.login_attempt_success - Попытка входа успешна
  • service.subpage_config_changed - Конфигурация подстраницы изменена
import {
TRemnawaveWebhookServiceEvent,
RemnawaveWebhookServiceEvents
} from '@remnawave/backend-contract'

Область: crm

OpenAPI Model: RemnawaveWebhookCrmEventsDto
Model Link: https://rw.gy/api/#model/RemnawaveWebhookCrmEventsDto

Доступные события:

  • crm.infra_billing_node_payment_in_7_days - Оплата узла инфра-биллинга через 7 дней
  • crm.infra_billing_node_payment_in_48hrs - Оплата узла инфра-биллинга через 48 часов
  • crm.infra_billing_node_payment_in_24hrs - Оплата узла инфра-биллинга через 24 часа
  • crm.infra_billing_node_payment_due_today - Оплата узла инфра-биллинга сегодня
  • crm.infra_billing_node_payment_overdue_24hrs - Просрочка оплаты узла 24 часа
  • crm.infra_billing_node_payment_overdue_48hrs - Просрочка оплаты узла 48 часов
  • crm.infra_billing_node_payment_overdue_7_days - Просрочка оплаты узла 7 дней
import { TRemnawaveWebhookCrmEvent, RemnawaveWebhookCrmEvents } from '@remnawave/backend-contract'

Область: torrent_blocker

Начиная с Remnawave Panel v2.7.0.

OpenAPI Model: RemnawaveWebhookTorrentBlockerEventsDto Model Link: https://rw.gy/api/#model/RemnawaveWebhookTorrentBlockerEventsDto

Доступные события:

  • torrent_blocker.report - Отчёт блокировщика торрентов
import {
TRemnawaveWebhookTorrentBlockerEvent,
RemnawaveWebhookTorrentBlockerEvents
} from '@remnawave/backend-contract'

Область: errors

OpenAPI Model: RemnawaveWebhookErrorsEventsDto Model Link: https://rw.gy/api/#model/RemnawaveWebhookErrorsEventsDto

Доступные события:

  • errors.bandwidth_usage_threshold_reached_max_notifications - Достигнуто максимальное число уведомлений о пороге полосы пропускания

Проверка вебхука

Remnawave подпишет полезную нагрузку вебхука с помощью WEBHOOK_SECRET_HEADER и отправит её на WEBHOOK_URL.

Вы можете проверить полезную нагрузку вебхука, проверив подпись.

Проверка вебхука
export interface WebhookHeaders {
'x-remnawave-signature': string
'x-remnawave-timestamp': string
}

validateWebhook(data: {
body: unknown
headers: WebhookHeaders
}): boolean {
if (!this.webhookSecret) return false

const signature = createHmac('sha256', this.webhookSecret)
.update(JSON.stringify(data.body))
.digest('hex')

return signature === data.headers['x-remnawave-signature']
}

Примеры для разных языков

Python

Пример кода Python
def validate_webhook(body, signature):
webhook_secret_panel = "your_secret_token"
"""Validate webhook signature"""
if isinstance(body, str):
original_body = body
logging.warning("Body is string, parsing for logging...")
try:
parsed_body = json.loads(body)
except json.JSONDecodeError as e:
logging.warning("Failed to parse body: %s", e)
return False
else:
original_body = json.dumps(body, separators=(',', ':'))
parsed_body = body

computed_signature = hmac.new(
webhook_secret_panel.encode('utf-8'),
original_body.encode('utf-8'),
hashlib.sha256
).hexdigest()

return hmac.compare_digest(computed_signature, signature)

Go

Пример кода Go
package main

import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
)

var webhookSecret = "your-secret-header"

type WebhookPayload struct {
Event string `json:"event"`
Data json.RawMessage `json:"data"`
Timestamp string `json:"timestamp"`
}

func validateWebhook(body []byte, signature string) bool {
mac := hmac.New(sha256.New, []byte(webhookSecret))
mac.Write(body)
expectedMAC := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(signature), []byte(expectedMAC))
}

func webhookHandler(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Error reading request body", http.StatusBadRequest)
return
}
signature := r.Header.Get("X-Remnawave-Signature")
if !validateWebhook(body, signature) {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("Webhook received"))
}

func main() {
http.HandleFunc("/webhook", webhookHandler)
fmt.Println("Server running at http://localhost:3000")
http.ListenAndServe(":3000", nil)
}