Webhooks empfangen
Übersicht
Remnawave kann Webhooks für viele Ereignisse senden.
Konfiguration
.env-Konfiguration
WEBHOOK_ENABLED=true
WEBHOOK_URL=https://your-server.com/webhook
WEBHOOK_SECRET_HEADER=your-secret-header
| Variable | Beschreibung |
|---|---|
WEBHOOK_ENABLED | Webhooks aktivieren. |
WEBHOOK_URL | Die URL, an die der Webhook gesendet wird. (muss mit https:// oder http:// beginnen). Mehrere URLs können durch Kommas getrennt angegeben werden (ohne Leerzeichen) |
WEBHOOK_SECRET_HEADER | Dieser Header wird verwendet, um die Webhook-Nutzlast zu signieren. (nur aA-zZ, 0-9 erlaubt) |
Header
Remnawave sendet folgende Header mit der Webhook-Nutzlast:
X-Remnawave-Signature- Die Signatur der Webhook-Nutzlast. (signiert mit WEBHOOK_SECRET_HEADER)X-Remnawave-Timestamp- Der Zeitstempel der Webhook-Nutzlast.
Nutzlast
Die Nutzlast ist ein JSON-Objekt.
{
"scope": "service",
"event": "service.panel_started",
"timestamp": "2026-01-07T11:57:29.426Z",
"data": {
"panelVersion": "2.5.0"
}
}
Eigenschaften:
-
scope- Der Geltungsbereich der Webhook-Nutzlast. (Seit v2.5.0)user- Benutzerereignisseuser_hwid_devices- Benutzer-HWID-Geräteereignissenode- Knotenereignisseservice- Dienstereignissecrm- Infrastruktur-Abrechnungsereignissetorrent_blocker- Torrent-Blocker-Ereignisseerrors- Fehlerereignisse
-
event- Das eingetretene Ereignis. -
timestamp- Der Zeitstempel im ISO 8601-Format. -
data- Die mit dem Ereignis verbundenen Daten.
Das detaillierte Nutzlastschema für jeden Bereich ist in der OpenAPI-Dokumentation verfügbar.
👉 Beachten Sie Model Link in jedem Bereichsabschnitt unten.
Bereich: user
OpenAPI Model: RemnawaveWebhookUserEventsDto
Model Link: https://rw.gy/api/#model/RemnawaveWebhookUserEventsDto
Verfügbare Ereignisse (Eigenschaft event):
user.created- Benutzer erstelltuser.modified- Benutzer geändertuser.deleted- Benutzer gelöschtuser.revoked- Benutzer widerrufenuser.disabled- Benutzer deaktiviertuser.enabled- Benutzer aktiviertuser.limited- Benutzer limitiertuser.expired- Benutzer abgelaufenuser.traffic_reset- Benutzertraffic zurückgesetztuser.expires_in_72_hours- Benutzer läuft in 72 Stunden ab (entfernt in v.2.8.0, verwende stattdessenuser.expiration)user.expires_in_48_hours- Benutzer läuft in 48 Stunden ab (entfernt in v.2.8.0, verwende stattdessenuser.expiration)user.expires_in_24_hours- Benutzer läuft in 24 Stunden ab (entfernt in v.2.8.0, verwende stattdessenuser.expiration)user.expired_24_hours_ago- Benutzer ist vor 24 Stunden abgelaufen (entfernt in v.2.8.0, verwende stattdessenuser.expiration)user.first_connected- Benutzer zum ersten Mal verbundenuser.bandwidth_usage_threshold_reached- Bandbreitennutzungsschwelle erreichtuser.not_connected- Benutzer nicht verbunden (aktiv nur wennNOT_CONNECTED_USERS_NOTIFICATIONS_ENABLEDin.envtrue ist)user.expiration- Benutzerablauf-Benachrichtigungen (Aktiv nur wennEXPIRATION_NOTIFICATIONS_ENABLEDin.envtrue ist.)
Remnawave Typescript SDK-Typen:
import { TRemnawaveWebhookUserEvent, RemnawaveWebhookUserEvents } from '@remnawave/backend-contract'
meta-Objekt
Die meisten Benutzerereignisse haben meta: null. Das Feld wird nur bei Benachrichtigungs-Ereignissen befüllt und liefert den Kontext, der den Webhook ausgelöst hat.
| Feld | Typ | Vorhanden für | Beschreibung |
|---|---|---|---|
notConnectedAfterHours | number | null | user.not_connected | Stunden, die der Benutzer offline war. Entspricht dem Schwellenwert aus NOT_CONNECTED_USERS_NOTIFICATIONS_AFTER_HOURS, der das Ereignis auslöste. |
expiration | number | null | user.expiration | Vorzeichenbehafteter Stunden-Offset relativ zum Ablauf des Benutzers (expireAt). Entspricht einem Wert aus EXPIRATION_NOTIFICATIONS. |
Vorzeichen des expiration-Felds
Der Wert expiration ist vorzeichenbehaftet und kodiert die Richtung relativ zum Ablaufzeitpunkt:
- Negativ — ausgelöst vor Ablauf: läuft in
|N|Stunden ab (z.B.-72→ läuft in 72 Stunden ab). - Positiv — ausgelöst nach Ablauf: ist seit
NStunden abgelaufen (z.B.24→ ist seit 24 Stunden abgelaufen).
Für alle anderen Ereignisse außer user.not_connected und user.expiration ist meta gleich null. Wenn meta vorhanden ist, ist nur das Feld für das aktuelle Ereignis gesetzt — das andere Feld bleibt null.
Bereich: user_hwid_devices
OpenAPI Model: RemnawaveWebhookUserHwidDevicesEventsDto
Model Link: https://rw.gy/api/#model/RemnawaveWebhookUserHwidDevicesEventsDto
Verfügbare Ereignisse:
user_hwid_devices.added- Benutzer-HWID-Gerät hinzugefügtuser_hwid_devices.deleted- Benutzer-HWID-Gerät gelöscht
import {
TRemnawaveWebhookUserHwidDevicesEvent,
RemnawaveWebhookUserHwidDevicesEvents
} from '@remnawave/backend-contract'
Bereich: node
OpenAPI Model: RemnawaveWebhookNodeEventsDto
Model Link: https://rw.gy/api/#model/RemnawaveWebhookNodeEventsDto
Verfügbare Ereignisse:
node.created- Knoten erstelltnode.modified- Knoten geändertnode.disabled- Knoten deaktiviertnode.enabled- Knoten aktiviertnode.deleted- Knoten gelöschtnode.connection_lost- Knotenverbindung verlorennode.connection_restored- Knotenverbindung wiederhergestelltnode.traffic_notify- Knotentraffic-Benachrichtigung
import { TRemnawaveWebhookNodeEvent, RemnawaveWebhookNodeEvents } from '@remnawave/backend-contract'
Bereich: service
OpenAPI Model: RemnawaveWebhookServiceEventsDto
Model Link: https://rw.gy/api/#model/RemnawaveWebhookServiceEventsDto
Verfügbare Ereignisse:
service.panel_started- Panel gestartetservice.login_attempt_failed- Anmeldeversuch fehlgeschlagenservice.login_attempt_success- Anmeldeversuch erfolgreichservice.subpage_config_changed- Unterseiten-Konfiguration geändert
import {
TRemnawaveWebhookServiceEvent,
RemnawaveWebhookServiceEvents
} from '@remnawave/backend-contract'
Bereich: crm
OpenAPI Model: RemnawaveWebhookCrmEventsDto
Model Link: https://rw.gy/api/#model/RemnawaveWebhookCrmEventsDto
Verfügbare Ereignisse:
crm.infra_billing_node_payment_in_7_days- Infrastruktur-Abrechnungsknoten Zahlung in 7 Tagencrm.infra_billing_node_payment_in_48hrs- Infrastruktur-Abrechnungsknoten Zahlung in 48 Stundencrm.infra_billing_node_payment_in_24hrs- Infrastruktur-Abrechnungsknoten Zahlung in 24 Stundencrm.infra_billing_node_payment_due_today- Infrastruktur-Abrechnungsknoten Zahlung heute fälligcrm.infra_billing_node_payment_overdue_24hrs- Infrastruktur-Abrechnungsknoten Zahlung 24 Stunden überfälligcrm.infra_billing_node_payment_overdue_48hrs- Infrastruktur-Abrechnungsknoten Zahlung 48 Stunden überfälligcrm.infra_billing_node_payment_overdue_7_days- Infrastruktur-Abrechnungsknoten Zahlung 7 Tage überfällig
import { TRemnawaveWebhookCrmEvent, RemnawaveWebhookCrmEvents } from '@remnawave/backend-contract'
Bereich: torrent_blocker
Ab Remnawave Panel v2.7.0.
OpenAPI Model: RemnawaveWebhookTorrentBlockerEventsDto
Model Link: https://rw.gy/api/#model/RemnawaveWebhookTorrentBlockerEventsDto
Verfügbare Ereignisse:
torrent_blocker.report- Torrent-Blocker-Bericht
import {
TRemnawaveWebhookTorrentBlockerEvent,
RemnawaveWebhookTorrentBlockerEvents
} from '@remnawave/backend-contract'
Bereich: errors
OpenAPI Model: RemnawaveWebhookErrorsEventsDto
Model Link: https://rw.gy/api/#model/RemnawaveWebhookErrorsEventsDto
Verfügbare Ereignisse:
errors.bandwidth_usage_threshold_reached_max_notifications- Maximale Benachrichtigungen für Bandbreitenschwelle erreicht
Webhook verifizieren
Remnawave signiert die Webhook-Nutzlast mit dem WEBHOOK_SECRET_HEADER und sendet sie an die WEBHOOK_URL.
Sie können die Webhook-Nutzlast durch Überprüfung der Signatur validieren.
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']
}
Beispiele für verschiedene Sprachen
Python
Python-Beispielcode
def validate_webhook(body, signature):
webhook_secret_panel = "your_secret_token"
"""Validate webhook signature"""
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
Go-Beispielcode
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, 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)
}