Recebendo webhooks
Visão geral
O Remnawave pode enviar webhooks para muitos eventos.
Configuração
Configuração .env
WEBHOOK_ENABLED=true
WEBHOOK_URL=https://your-server.com/webhook
WEBHOOK_SECRET_HEADER=your-secret-header
| Variável | Descrição |
|---|---|
WEBHOOK_ENABLED | Habilitar webhooks. |
WEBHOOK_URL | A URL para enviar o webhook. (deve começar com https:// ou http://). É possível especificar múltiplas URLs separadas por vírgulas (sem espaços) |
WEBHOOK_SECRET_HEADER | Este cabeçalho será usado para assinar o payload do webhook. (apenas aA-zZ, 0-9 são permitidos) |
Cabeçalhos
O Remnawave enviará os seguintes cabeçalhos com o payload do webhook:
X-Remnawave-Signature- A assinatura do payload do webhook. (assinado com WEBHOOK_SECRET_HEADER)X-Remnawave-Timestamp- O timestamp do payload do webhook.
Payload
O payload será um objeto JSON.
{
"scope": "service",
"event": "service.panel_started",
"timestamp": "2026-01-07T11:57:29.426Z",
"data": {
"panelVersion": "2.5.0"
}
}
Propriedades:
-
scope- O escopo do payload do webhook. (Desde v2.5.0)user- Eventos de usuáriouser_hwid_devices- Eventos de dispositivos HWID do usuárionode- Eventos de nóservice- Eventos de serviçocrm- Eventos de faturamento de infraestruturatorrent_blocker- Eventos de bloqueador de torrentserrors- Eventos de erros
-
event- O evento que ocorreu. -
timestamp- O timestamp no formato ISO 8601. -
data- Os dados associados ao evento.
O esquema de payload detalhado para cada escopo está disponível na documentação OpenAPI.
👉 Consulte Model Link em cada seção de escopo abaixo.
Escopo: user
OpenAPI Model: RemnawaveWebhookUserEventsDto
Model Link: https://rw.gy/api/#model/RemnawaveWebhookUserEventsDto
Eventos disponíveis (propriedade event):
user.created- Usuário criadouser.modified- Usuário modificadouser.deleted- Usuário deletadouser.revoked- Usuário revogadouser.disabled- Usuário desabilitadouser.enabled- Usuário habilitadouser.limited- Usuário limitadouser.expired- Usuário expiradouser.traffic_reset- Tráfego do usuário resetadouser.expires_in_72_hours- Usuário expira em 72 horas (removido na v.2.8.0, useuser.expirationem vez disso)user.expires_in_48_hours- Usuário expira em 48 horas (removido na v.2.8.0, useuser.expirationem vez disso)user.expires_in_24_hours- Usuário expira em 24 horas (removido na v.2.8.0, useuser.expirationem vez disso)user.expired_24_hours_ago- Usuário expirou há 24 horas (removido na v.2.8.0, useuser.expirationem vez disso)user.first_connected- Usuário conectado pela primeira vezuser.bandwidth_usage_threshold_reached- Limite de uso de largura de banda atingidouser.not_connected- Usuário não conectado (ativo apenas quandoNOT_CONNECTED_USERS_NOTIFICATIONS_ENABLEDé true no.env)user.expiration- Notificações de expiração de usuário (Ativo somente quandoEXPIRATION_NOTIFICATIONS_ENABLEDé true em.env.)
import { TRemnawaveWebhookUserEvent, RemnawaveWebhookUserEvents } from '@remnawave/backend-contract'
Objeto meta
A maioria dos eventos de usuário tem meta: null. Este campo é preenchido apenas para eventos de notificação, fornecendo o contexto que acionou o webhook.
| Campo | Tipo | Presente para | Descrição |
|---|---|---|---|
notConnectedAfterHours | number | null | user.not_connected | Horas que o usuário ficou offline. Corresponde ao limite de NOT_CONNECTED_USERS_NOTIFICATIONS_AFTER_HOURS que disparou o evento. |
expiration | number | null | user.expiration | Deslocamento em horas com sinal em relação à expiração do usuário (expireAt). Corresponde a um valor de EXPIRATION_NOTIFICATIONS. |
Sinal do campo expiration
O valor de expiration tem sinal e codifica a direção em relação ao momento de expiração:
- Negativo — disparado antes da expiração: expira em
|N|horas (ex.:-72→ expira em 72 horas). - Positivo — disparado após a expiração: expirou há
Nhoras (ex.:24→ expirou há 24 horas).
Para todos os eventos exceto user.not_connected e user.expiration, meta é null. Quando meta está presente, apenas o campo do evento atual é definido — o outro campo permanece null.
Escopo: user_hwid_devices
Eventos disponíveis:
user_hwid_devices.added- Dispositivo HWID do usuário adicionadouser_hwid_devices.deleted- Dispositivo HWID do usuário deletado
Escopo: node
Eventos disponíveis:
node.created- Nó criadonode.modified- Nó modificadonode.disabled- Nó desabilitadonode.enabled- Nó habilitadonode.deleted- Nó deletadonode.connection_lost- Conexão do nó perdidanode.connection_restored- Conexão do nó restauradanode.traffic_notify- Notificação de tráfego do nó
Escopo: service
Eventos disponíveis:
service.panel_started- Painel iniciadoservice.login_attempt_failed- Tentativa de login falhouservice.login_attempt_success- Tentativa de login bem-sucedidaservice.subpage_config_changed- Configuração de subpágina alterada
Escopo: crm
Eventos disponíveis:
crm.infra_billing_node_payment_in_7_days- Pagamento de nó de faturamento de infra em 7 diascrm.infra_billing_node_payment_in_48hrs- Pagamento de nó em 48 horascrm.infra_billing_node_payment_in_24hrs- Pagamento de nó em 24 horascrm.infra_billing_node_payment_due_today- Pagamento de nó vence hojecrm.infra_billing_node_payment_overdue_24hrs- Pagamento de nó atrasado 24 horascrm.infra_billing_node_payment_overdue_48hrs- Pagamento de nó atrasado 48 horascrm.infra_billing_node_payment_overdue_7_days- Pagamento de nó atrasado 7 dias
Escopo: torrent_blocker
A partir do Remnawave Panel v2.7.0.
torrent_blocker.report- Relatório do bloqueador de torrents
Escopo: errors
errors.bandwidth_usage_threshold_reached_max_notifications- Limiar de largura de banda atingiu notificações máximas
Verificar webhook
O Remnawave assinará o payload do webhook com WEBHOOK_SECRET_HEADER e o enviará para 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']
}
Exemplos para diferentes linguagens
Python
Código de exemplo 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 exemplo 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)
}