跳至主要内容

接收 Webhooks

概述

Remnawave 可以為許多事件發送 webhooks。

配置

.env 配置

WEBHOOK_ENABLED=true
WEBHOOK_URL=https://your-server.com/webhook
WEBHOOK_SECRET_HEADER=your-secret-header
變數描述
WEBHOOK_ENABLED啟用 webhooks。
WEBHOOK_URL發送 webhook 的 URL。(必須以 https:// 或 http:// 開頭)。可以指定多個以逗號分隔的 URL(不帶空格)
WEBHOOK_SECRET_HEADER此標頭將用於簽署 webhook 有效載荷。(只允許 aA-zZ、0-9)

標頭

Remnawave 將隨 webhook 有效載荷發送以下標頭:

  • X-Remnawave-Signature - webhook 有效載荷的簽名。(使用 WEBHOOK_SECRET_HEADER 簽署)
  • X-Remnawave-Timestamp - webhook 有效載荷的時間戳。

有效載荷

有效載荷將是一個 JSON 物件。

示例 webhook 有效載荷
{
"scope": "service",
"event": "service.panel_started",
"timestamp": "2026-01-07T11:57:29.426Z",
"data": {
"panelVersion": "2.5.0"
}
}

屬性:

  • scope - webhook 有效載荷的範圍。(自 v2.5.0 起)

    • user - 使用者事件
    • user_hwid_devices - 使用者 HWID 裝置事件
    • node - 節點事件
    • service - 服務事件
    • crm - 基礎設施計費事件
    • torrent_blocker - 種子封鎖事件
    • errors - 錯誤事件
  • event - 發生的事件。

  • timestamp - ISO 8601 格式的 webhook 有效載荷時間戳。

  • data - 與事件相關的資料。

提示

每個範圍的詳細有效載荷模式可在 OpenAPI 文件中找到。

👉 參考下面每個範圍部分的 Model Link

範圍:user

OpenAPI 模型:RemnawaveWebhookUserEventsDto
模型連結: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 - 使用者未連接(僅當 .envNOT_CONNECTED_USERS_NOTIFICATIONS_ENABLED 為 true 時激活)
  • user.expiration - 用戶到期通知(僅當 .envEXPIRATION_NOTIFICATIONS_ENABLED 為 true 時啟用。)

Remnawave Typescript SDK 類型:

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

meta 物件

大多數用戶事件的 metanull通知類事件會填充此欄位,提供觸發 webhook 的上下文。

欄位類型適用事件說明
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_connecteduser.expiration 外的所有事件,meta 均為 null。當 meta 存在時,僅設置當前事件對應的欄位,另一欄位保持 null

範圍:user_hwid_devices

OpenAPI 模型:RemnawaveWebhookUserHwidDevicesEventsDto​
模型連結: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 模型:RemnawaveWebhookNodeEventsDto​
模型連結: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 模型:RemnawaveWebhookServiceEventsDto
模型連結: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 模型:RemnawaveWebhookCrmEventsDto
模型連結: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 - 頻寬使用閾值已達到最大通知次數

驗證 Webhook

Remnawave 將使用 WEBHOOK_SECRET_HEADER 簽署 webhook 有效載荷並發送到 WEBHOOK_URL

您可以通過檢查簽名來驗證 webhook 有效載荷。

Webhook 驗證
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)
}