メインコンテンツまでスキップ

Webhook の受信

概要

Remnawave は多くのイベントに対して webhook を送信できます。

設定

.env 設定

WEBHOOK_ENABLED=true
WEBHOOK_URL=https://your-server.com/webhook
WEBHOOK_SECRET_HEADER=your-secret-header
変数説明
WEBHOOK_ENABLEDwebhook を有効にする。
WEBHOOK_URLwebhook の送信先 URL。(https:// または http:// で始まる必要があります)。カンマ区切り(スペースなし)で複数の URL を指定可能
WEBHOOK_SECRET_HEADERwebhook ペイロードの署名に使用されるヘッダー。(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 形式のタイムスタンプ。

  • 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 - ユーザー有効期限通知(.envEXPIRATION_NOTIFICATIONS_ENABLED が true のときのみ有効です。)
import { TRemnawaveWebhookUserEvent, RemnawaveWebhookUserEvents } from '@remnawave/backend-contract'

meta オブジェクト

ほとんどのユーザーイベントは meta: null を持ちます。このフィールドは通知スタイルのイベントのみに設定され、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 以外のすべてのイベントでは、metanull です。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 に送信します。

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"
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)
}