Réception de webhooks
Présentation
Remnawave peut envoyer des webhooks pour de nombreux événements.
Configuration
Configuration .env
WEBHOOK_ENABLED=true
WEBHOOK_URL=https://your-server.com/webhook
WEBHOOK_SECRET_HEADER=your-secret-header
| Variable | Description |
|---|---|
WEBHOOK_ENABLED | Activer les webhooks. |
WEBHOOK_URL | L'URL à laquelle envoyer le webhook. (doit commencer par https:// ou http://). Il est possible de spécifier plusieurs URLs séparées par des virgules (sans espaces) |
WEBHOOK_SECRET_HEADER | Cet en-tête sera utilisé pour signer la charge utile du webhook. (seuls aA-zZ, 0-9 sont autorisés) |
En-têtes
Remnawave enverra les en-têtes suivants avec la charge utile du webhook :
X-Remnawave-Signature- La signature de la charge utile du webhook. (signée avec WEBHOOK_SECRET_HEADER)X-Remnawave-Timestamp- L'horodatage de la charge utile du webhook.
Charge utile
La charge utile sera un objet JSON.
{
"scope": "service",
"event": "service.panel_started",
"timestamp": "2026-01-07T11:57:29.426Z",
"data": {
"panelVersion": "2.5.0"
}
}
Propriétés :
-
scope- La portée de la charge utile du webhook. (Depuis v2.5.0)user- Événements utilisateuruser_hwid_devices- Événements appareils HWID utilisateurnode- Événements nœudservice- Événements servicecrm- Événements facturation infrastructuretorrent_blocker- Événements bloqueur de torrentserrors- Événements erreurs
-
event- L'événement survenu. -
timestamp- L'horodatage en format ISO 8601. -
data- Les données associées à l'événement.
Le schéma de charge utile détaillé pour chaque portée est disponible dans la documentation OpenAPI.
👉 Consultez Model Link dans chaque section de portée ci-dessous.
Portée : user
OpenAPI Model : RemnawaveWebhookUserEventsDto
Model Link : https://rw.gy/api/#model/RemnawaveWebhookUserEventsDto
Événements disponibles (propriété event) :
user.created- Utilisateur crééuser.modified- Utilisateur modifiéuser.deleted- Utilisateur suppriméuser.revoked- Utilisateur révoquéuser.disabled- Utilisateur désactivéuser.enabled- Utilisateur activéuser.limited- Utilisateur limitéuser.expired- Utilisateur expiréuser.traffic_reset- Trafic utilisateur réinitialiséuser.expires_in_72_hours- Utilisateur expire dans 72 heures (supprimé dans v.2.8.0, utilisezuser.expirationà la place)user.expires_in_48_hours- Utilisateur expire dans 48 heures (supprimé dans v.2.8.0, utilisezuser.expirationà la place)user.expires_in_24_hours- Utilisateur expire dans 24 heures (supprimé dans v.2.8.0, utilisezuser.expirationà la place)user.expired_24_hours_ago- Utilisateur expiré il y a 24 heures (supprimé dans v.2.8.0, utilisezuser.expirationà la place)user.first_connected- Utilisateur connecté pour la première foisuser.bandwidth_usage_threshold_reached- Seuil d'utilisation de bande passante atteintuser.not_connected- Utilisateur non connecté (actif uniquement quandNOT_CONNECTED_USERS_NOTIFICATIONS_ENABLEDest true dans.env)user.expiration- Notifications d'expiration d'utilisateur (Actif uniquement lorsqueEXPIRATION_NOTIFICATIONS_ENABLEDest vrai dans.env.)
import { TRemnawaveWebhookUserEvent, RemnawaveWebhookUserEvents } from '@remnawave/backend-contract'
Objet meta
La plupart des événements utilisateur ont meta: null. Ce champ est renseigné uniquement pour les événements de type notification, où il fournit le contexte ayant déclenché le webhook.
| Champ | Type | Présent pour | Description |
|---|---|---|---|
notConnectedAfterHours | number | null | user.not_connected | Heures de déconnexion de l'utilisateur. Correspond au seuil de NOT_CONNECTED_USERS_NOTIFICATIONS_AFTER_HOURS ayant déclenché l'événement. |
expiration | number | null | user.expiration | Décalage signé en heures par rapport à l'expiration de l'utilisateur (expireAt). Correspond à une valeur de EXPIRATION_NOTIFICATIONS. |
Signe du champ expiration
La valeur expiration est signée et encode la direction par rapport au moment d'expiration :
- Négatif — déclenché avant l'expiration : expire dans
|N|heures (ex.-72→ expire dans 72 heures). - Positif — déclenché après l'expiration : expiré il y a
Nheures (ex.24→ expiré il y a 24 heures).
Pour tous les événements autres que user.not_connected et user.expiration, meta est null. Lorsque meta est présent, seul le champ de l'événement actuel est défini — l'autre champ reste null.
Portée : user_hwid_devices
Événements disponibles :
user_hwid_devices.added- Appareil HWID utilisateur ajoutéuser_hwid_devices.deleted- Appareil HWID utilisateur supprimé
Portée : node
Événements disponibles :
node.created- Nœud créénode.modified- Nœud modifiénode.disabled- Nœud désactivénode.enabled- Nœud activénode.deleted- Nœud suppriménode.connection_lost- Connexion nœud perduenode.connection_restored- Connexion nœud restauréenode.traffic_notify- Notification trafic nœud
Portée : service
Événements disponibles :
service.panel_started- Panel démarréservice.login_attempt_failed- Tentative de connexion échouéeservice.login_attempt_success- Tentative de connexion réussieservice.subpage_config_changed- Configuration sous-page modifiée
Portée : crm
Événements disponibles :
crm.infra_billing_node_payment_in_7_days- Paiement nœud facturation infra dans 7 jourscrm.infra_billing_node_payment_in_48hrs- Paiement nœud dans 48 heurescrm.infra_billing_node_payment_in_24hrs- Paiement nœud dans 24 heurescrm.infra_billing_node_payment_due_today- Paiement nœud dû aujourd'huicrm.infra_billing_node_payment_overdue_24hrs- Paiement nœud en retard 24 heurescrm.infra_billing_node_payment_overdue_48hrs- Paiement nœud en retard 48 heurescrm.infra_billing_node_payment_overdue_7_days- Paiement nœud en retard 7 jours
Portée : torrent_blocker
Depuis Remnawave Panel v2.7.0.
torrent_blocker.report- Rapport bloqueur de torrents
Portée : errors
errors.bandwidth_usage_threshold_reached_max_notifications- Seuil de bande passante atteint notifications maximales
Vérifier le webhook
Remnawave signera la charge utile du webhook avec WEBHOOK_SECRET_HEADER et l'enverra à 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']
}
Exemples pour différents langages
Python
Exemple de code Python
def validate_webhook(body, signature):
webhook_secret_panel = "your_secret_token"
if isinstance(body, str):
original_body = body
try:
parsed_body = json.loads(body)
except json.JSONDecodeError as 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
Exemple de code Go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"io/ioutil"
"net/http"
)
var webhookSecret = "your-secret-header"
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, _ := ioutil.ReadAll(r.Body)
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)
}