rails-webhooks について
rails-webhooksは、Rails開発者向けにWebhookの送受信、セキュリティ、運用の設計指針をまとめたガイドです。永続化された配信記録、障害の分類、サーキットブレーカーを重視します。
主な機能
- 01outboxパターンによる配信の永続化
- 02宛先障害とコード例外の区別
- 03失敗追跡と自動停止
- 04SSRF対策と署名検証
- 05宛先アダプターとテナント分離
- 06配信記録の整理と監視
活用シーン
- 01再開可能なアウトバウンドWebhookの設計
- 02監査記録を使った配信障害の調査
- 03SSRFからWebhook宛先を保護
- 04サーキットブレーカー付き管理画面の構築
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ marckohlbrugge/ 37signals-skills --skill rails-webhooksインストール後、Claude Code で/rails-webhooksを入力して読み込まれたか確認します。
詳細
Rails Webhooks
送信/受信 Webhook のアーキテクチャと信頼性に使用します。Fizzy のイベント駆動型 Webhook パイプラインを参考にしており、対比として Campfire のよりシンプルなボット Webhook を取り上げています。
配信モデル(outbox パターン)
- ドメインイベントはディスパッチジョブを 1 つキューに追加します。ディスパッチジョブはファンアウトし、Webhook ごとに、状態を表す enum(
pending in_progress completed errored—index_by(&:itself)による文字列ベース)を持つ永続化済みのDelivery行を 1 つ作成します。 - 各配信行は
after_create_commit :deliver_laterによって、それぞれの送信を自動でキューに追加します。先に永続化し、その後に配信するため、クラッシュや再試行が発生しても状態が保持されます。 ActiveJob::Continuableによって、ファンアウトをクラッシュセーフにします。一致する Webhook を対象にカーソルを進める(find_each(start: step.cursor)+step.advance!)ことで、バッチの途中でクラッシュしても最初からではなく、中断した位置から再開できます。- 監査やデバッグのため、リクエストのメタデータ(ヘッダー、ペイロード)とレスポンス(ステータス、本文)を配信行に記録します。実行中のバイト数をカウントし、保存・ストリーミングするレスポンス本文は約 100KB に制限します。
- 専用の
webhooksキューを使用し、応答の遅い宛先が他の処理を妨げないようにします。
失敗の分類(重要な違い)
- 宛先側で発生する想定内の失敗(タイムアウト、TLS、DNS、接続拒否、HTTP 4xx/5xx):例外を捕捉し、記号形式のエラー(
response: { error: :connection_timeout })を付けて配信をcompletedにします。配信処理は実行され、宛先側で失敗したということです。ジョブを再試行しないでください。 - 想定外の例外(こちらのバグ):
errored!にしてから、ActiveJob の再試行が行われるように例外を再送出します。 - この区別により、再試行の挙動、ダッシュボード、滞留状況の追跡を正確に保てます。
滞留を防ぐサーキットブレーカー
- Webhook ごとに連続失敗数と
first_failure_atを追跡し、最低期間内に N 回失敗したら自動で無効化します(Fizzy の場合:1 時間以上の期間に 10 回失敗)。 - 成功時にカウンターをリセットします。
- 手動で再有効化するエンドポイント(
resource :activation)とともに、UI に非アクティブ状態を表示します。
セキュリティの基本対策
- Webhook URL は信頼できない入力として扱い、SSRF に対する包括的な保護を適用します(IP を解決・検証し、プライベートレンジをブロックし、IP を固定し、リダイレクトのたびに再検証します。rails-security-multitenancy を参照)。
- 作成時だけでなく、送信時にも宛先を再検証します。
- 宛先 URL は作成後に変更できません。更新で許可するのは名前とサブスクリプションのみです。宛先を変更する場合は、新しい Webhook(および新しいシークレット)が必要です。
- ペイロードに署名します。HMAC 署名ヘッダーとタイムスタンプヘッダーを使用します。
- モデル層で購読可能なイベントをホワイトリスト化します:
normalizes :subscribed_actions, with: ->(v) { Array.wrap(v).map(&:to_s).uniq & PERMITTED_ACTIONS }。 - Webhook 管理エンドポイントには管理者レベルの認証を必須にします。
インテグレーションアダプター
- 1 つの配信パイプラインで複数の宛先タイプに対応できます。URL パターン(
for_slack?、for_campfire?)で判別し、宛先ごとにコンテンツタイプ、ペイロード形式(JSON/フォーム/HTML)、レンダリングテンプレートを切り替えます。インテグレーションごとに配信コードを分岐させないでください。 - ペイロード内の URL は、テナントに適した
script_nameを使ってレンダリングし、リンクが正しく機能するようにします。
受信 Webhook
- 最初に署名を検証し(
construct_event形式)、その後、ペイロードの内容や順序を信頼せず、プロバイダーの API から正式な状態を再取得します。 - チャットボット形式のコールバックでは、コンテンツタイプに応じて実行できる応答を制限します(
text/plain/text/htmlのみを返信にします)。また、ループを防ぐため、ボット自身のメッセージによってボットが起動しないようにします。
テナントの安全性
- Webhook レコードと配信クエリをテナントの範囲内に限定します。
- イベントのファンアウトによってテナント間でデータが漏えいしないようにします。
運用上の衛生管理
- 保持ポリシーに従って古い配信レコードを定期的に削除します(例:4 時間ごとに、古いレコードを対象とするスコープに対して
delete_allを実行)。 - Webhook 管理 UI に配信状況と履歴を表示します。
- 成功率、再試行、レイテンシーに関する有用なログとメトリクスを出力します。
危険信号
- 永続化された監査証跡のない、送信したら終わりの配信。
- 宛先の障害をコードエラーと同じ方法で再試行すること。
- 変更可能な Webhook の宛先 URL。
- サーキットブレーカーがなく、停止したエンドポイントに延々とリクエストを送り続けること。
- 宛先からのレスポンスを上限なく読み込むこと。
- 配信の作成/検索でテナントのスコープが設定されていないこと。
- 大量のイベントに対するバックプレッシャーやキューの分離がないこと。
コメント
Markdown 対応