Recibir webhooks
Descripción general
Remnawave puede enviar webhooks para muchos eventos.
Configuración
Configuración .env
WEBHOOK_ENABLED=true
WEBHOOK_URL=https://your-server.com/webhook
WEBHOOK_SECRET_HEADER=your-secret-header
| Variable | Descripción |
|---|---|
WEBHOOK_ENABLED | Habilitar webhooks. |
WEBHOOK_URL | La URL a la que se enviará el webhook. (debe comenzar con https:// o http://). Es posible especificar múltiples URLs separadas por comas (sin espacios) |
WEBHOOK_SECRET_HEADER | Este encabezado se utilizará para firmar la carga del webhook. (solo se permiten aA-zZ, 0-9) |
Encabezados
Remnawave enviará los siguientes encabezados con la carga del webhook:
X-Remnawave-Signature- La firma de la carga del webhook. (firmada con WEBHOOK_SECRET_HEADER)X-Remnawave-Timestamp- La marca de tiempo de la carga del webhook.
Carga
La carga será un objeto JSON.
{
"scope": "service",
"event": "service.panel_started",
"timestamp": "2026-01-07T11:57:29.426Z",
"data": {
"panelVersion": "2.5.0"
}
}
Propiedades:
-
scope- El alcance de la carga del webhook. (Desde v2.5.0)user- Eventos de usuariouser_hwid_devices- Eventos de dispositivos HWID de usuarionode- Eventos de nodoservice- Eventos de serviciocrm- Eventos de facturación de infraestructuratorrent_blocker- Eventos de bloqueador de torrentserrors- Eventos de errores
-
event- El evento que ocurrió. -
timestamp- La marca de tiempo en formato ISO 8601. -
data- Los datos asociados con el evento.
El esquema de carga detallado para cada alcance está disponible en la documentación OpenAPI.
👉 Consulte Model Link en cada sección de alcance a continuación.
Alcance: user
OpenAPI Model: RemnawaveWebhookUserEventsDto
Model Link: https://rw.gy/api/#model/RemnawaveWebhookUserEventsDto
Eventos disponibles (propiedad event):
user.created- Usuario creadouser.modified- Usuario modificadouser.deleted- Usuario eliminadouser.revoked- Usuario revocadouser.disabled- Usuario deshabilitadouser.enabled- Usuario habilitadouser.limited- Usuario limitadouser.expired- Usuario expiradouser.traffic_reset- Tráfico de usuario restablecidouser.expires_in_72_hours- Usuario expira en 72 horas (eliminado en v.2.8.0, usauser.expirationen su lugar)user.expires_in_48_hours- Usuario expira en 48 horas (eliminado en v.2.8.0, usauser.expirationen su lugar)user.expires_in_24_hours- Usuario expira en 24 horas (eliminado en v.2.8.0, usauser.expirationen su lugar)user.expired_24_hours_ago- Usuario expiró hace 24 horas (eliminado en v.2.8.0, usauser.expirationen su lugar)user.first_connected- Usuario conectado por primera vezuser.bandwidth_usage_threshold_reached- Umbral de uso de ancho de banda alcanzadouser.not_connected- Usuario no conectado (activo solo cuandoNOT_CONNECTED_USERS_NOTIFICATIONS_ENABLEDes true en.env)user.expiration- Notificaciones de expiración de usuario (Activo solo cuandoEXPIRATION_NOTIFICATIONS_ENABLEDes true en.env.)
Tipos de Remnawave Typescript SDK:
import { TRemnawaveWebhookUserEvent, RemnawaveWebhookUserEvents } from '@remnawave/backend-contract'
Objeto meta
La mayoría de los eventos de usuario tienen meta: null. Este campo se rellena solo para eventos de tipo notificación, donde proporciona el contexto que desencadenó el webhook.
| Campo | Tipo | Presente para | Descripción |
|---|---|---|---|
notConnectedAfterHours | number | null | user.not_connected | Horas que el usuario ha estado desconectado. Coincide con el umbral de NOT_CONNECTED_USERS_NOTIFICATIONS_AFTER_HOURS que disparó el evento. |
expiration | number | null | user.expiration | Desfase en horas con signo relativo al vencimiento del usuario (expireAt). Coincide con un valor de EXPIRATION_NOTIFICATIONS. |
Signo del campo expiration
El valor de expiration tiene signo y codifica la dirección relativa al momento de vencimiento:
- Negativo — disparado antes del vencimiento: vence en
|N|horas (p. ej.,-72→ vence en 72 horas). - Positivo — disparado después del vencimiento: venció hace
Nhoras (p. ej.,24→ venció hace 24 horas).
Para todos los eventos excepto user.not_connected y user.expiration, meta es null. Cuando meta está presente, solo se establece el campo para el evento actual; el otro campo permanece null.
Alcance: user_hwid_devices
Eventos disponibles:
user_hwid_devices.added- Dispositivo HWID de usuario agregadouser_hwid_devices.deleted- Dispositivo HWID de usuario eliminado
Alcance: node
Eventos disponibles:
node.created- Nodo creadonode.modified- Nodo modificadonode.disabled- Nodo deshabilitadonode.enabled- Nodo habilitadonode.deleted- Nodo eliminadonode.connection_lost- Conexión de nodo perdidanode.connection_restored- Conexión de nodo restauradanode.traffic_notify- Notificación de tráfico de nodo
Alcance: service
Eventos disponibles:
service.panel_started- Panel iniciadoservice.login_attempt_failed- Intento de inicio de sesión fallidoservice.login_attempt_success- Intento de inicio de sesión exitososervice.subpage_config_changed- Configuración de subpágina cambiada
Alcance: crm
Eventos disponibles:
crm.infra_billing_node_payment_in_7_days- Pago de nodo de facturación de infraestructura en 7 díascrm.infra_billing_node_payment_in_48hrs- Pago de nodo en 48 horascrm.infra_billing_node_payment_in_24hrs- Pago de nodo en 24 horascrm.infra_billing_node_payment_due_today- Pago de nodo vence hoycrm.infra_billing_node_payment_overdue_24hrs- Pago de nodo vencido 24 horascrm.infra_billing_node_payment_overdue_48hrs- Pago de nodo vencido 48 horascrm.infra_billing_node_payment_overdue_7_days- Pago de nodo vencido 7 días
Alcance: torrent_blocker
Desde Remnawave Panel v2.7.0.
torrent_blocker.report- Informe del bloqueador de torrents
Alcance: errors
errors.bandwidth_usage_threshold_reached_max_notifications- Umbral de uso de ancho de banda alcanzó máximo de notificaciones
Verificar webhook
Remnawave firmará la carga del webhook con el WEBHOOK_SECRET_HEADER y la enviará a la 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']
}
Ejemplos para diferentes lenguajes
Python
Código de muestra 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
Código de muestra 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)
}