Webhook の受信
概要
Remnawave は多くのイベントに対して webhook を送信できます。
設定
.env 設定
WEBHOOK_ENABLED=true
WEBHOOK_URL=https://your-server.com/webhook
WEBHOOK_SECRET_HEADER=your-secret-header
| 変数 | 説明 |
|---|---|
WEBHOOK_ENABLED | webhook を有効にする。 |
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 オブジェクトです。
{
"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 形式のタイムスタンプ。 -
data- イベントに関連するデータ。
各スコープの詳細なペイロードスキーマは OpenAPI ドキュメントで確認できます。
👉 以下の各スコープセクションの Model Link を参照してください。
スコープ: user
OpenAPI Model: RemnawaveWebhookUserEventsDto
Model Link: 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- ユーザーが接続していない(.env でNOT_CONNECTED_USERS_NOTIFICATIONS_ENABLEDが true の場合のみ有効)user.expiration- ユーザー有効期限通知(.envのEXPIRATION_NOTIFICATIONS_ENABLEDが true のときのみ有効です。)
import { TRemnawaveWebhookUserEvent, RemnawaveWebhookUserEvents } from '@remnawave/backend-contract'
meta オブジェクト
ほとんどのユーザーイベントは meta: null を持ちます。このフィールドは通知スタイルのイベントのみに設定され、webhookをトリガーしたコンテキストを提供します。
| フィールド | 型 | 対象イベント | 説明 |
|---|---|---|---|
notConnectedAfterHours | number | null | user.not_connected | ユーザーがオフラインだった時間(時間単位)。イベントを発火した NOT_CONNECTED_USERS_NOTIFICATIONS_AFTER_HOURS のしきい値と一致します。 |
expiration | number | null | user.expiration | ユーザーの有効期限(expireAt)を基準とした時間の符号付きオフセット。EXPIRATION_NOTIFICATIONS の値と一致します。 |
expiration フィールドの符号
expiration の値は符号付きで、有効期限に対する方向を示します:
- 負の値 — 有効期限前に発火:
|N|時間後に期限切れ (例:-72→ 72時間後に期限切れ)。 - 正の値 — 有効期限後に発火:
N時間前に期限切れ (例:24→ 24時間前に期限切れ)。
user.not_connected と user.expiration 以外のすべてのイベントでは、meta は null です。meta が存在する場合、現在のイベントのフィールドのみが設定され、もう一方のフィールドは null のままです。
スコープ: user_hwid_devices
利用可能なイベント:
user_hwid_devices.added- ユーザー HWID デバイスが追加されたuser_hwid_devices.deleted- ユーザー HWID デバイスが削除された
スコープ: node
利用可能なイベント:
node.created- ノードが作成されたnode.modified- ノードが変更されたnode.disabled- ノードが無効化されたnode.enabled- ノードが有効化されたnode.deleted- ノードが削除されたnode.connection_lost- ノードの接続が失われたnode.connection_restored- ノードの接続が回復したnode.traffic_notify- ノードのトラフィック通知
スコープ: service
利用可能なイベント:
service.panel_started- パネルが起動したservice.login_attempt_failed- ログイン試行が失敗したservice.login_attempt_success- ログイン試行が成功したservice.subpage_config_changed- サブページ設定が変更された
スコープ: crm
利用可能なイベント:
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 日超過
スコープ: torrent_blocker
Remnawave Panel v2.7.0 以降。
torrent_blocker.report- トレントブロッカーレポート
スコープ: errors
errors.bandwidth_usage_threshold_reached_max_notifications- 帯域幅使用量しきい値の最大通知に達した
Webhook の検証
Remnawave は WEBHOOK_SECRET_HEADER で webhook ペイロードに署名し、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']
}
異なる言語の例
Python
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
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)
}