Webhook解説:署名付きペイロードとリトライ戦略
サードパーティサービスからイベントを受信するWebhookエンドポイントを構築したばかりだとします。テストではすべてが機能しますが、本番環境では一部のイベントが届かない、あるいはさらに悪いことに重複イベントを受信してデータの不整合が発生することに気づきます。これらはWebhookの典型的な落とし穴であり、2つの重要な側面、つまりペイロードの真正性と配信の信頼性に起因します。
WebhookはイベントによってトリガーされるHTTPコールバックであり、データをリアルタイムでアプリケーションにプッシュします。ポーリングとは異なり、効率的でほぼ即時です。しかし、課題ももたらします。リクエストが本当に期待される送信者から来たのかをどうやって知るのか、受信側が一時的にダウンしているときに失敗をどう処理するのか、といったことです。この記事では署名付きペイロードとリトライメカニズムを掘り下げ、回復力のあるWebhook統合を構築するための実践的なパターンを提供します。
Webhookに署名が必要な理由
Webhookエンドポイントは公開アクセス可能なURLです。検証がなければ、誰でも偽造リクエストをエンドポイントに送信でき、不正なアクションやデータ破損を引き起こす可能性があります。署名付きペイロードは、各リクエストに暗号署名を追加することでこれを解決し、送信者の身元を検証し、ペイロードが改ざんされていないことを確認できるようにします。
ほとんどのプロバイダーは共有シークレットを使用したHMAC(Hash-based Message Authentication Code)を使用しています。送信者はシークレットを使用してペイロードのハッシュを計算し、それをヘッダーに含めます。あなたは自分の側でハッシュを再計算して比較します。一致すれば、リクエストは正当です。
HMAC検証の実装
Node.jsでの典型的な実装を見ていきましょう。プロバイダーがX-Signatureヘッダーに署名を送信し、それがあなたのシークレットを使用した生のリクエストボディのHMAC-SHA256として計算されているとします。
const crypto = require('crypto');
const express = require('express');
const app = express();
// Use raw body parser to preserve exact payload
app.use(express.raw({ type: 'application/json' }));
app.post('/webhook', (req, res) => {
const signature = req.headers['x-signature'];
const secret = process.env.WEBHOOK_SECRET;
const expected = crypto
.createHmac('sha256', secret)
.update(req.body)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(401).send('Invalid signature');
}
// Process the event
const event = JSON.parse(req.body);
// ... handle event
res.status(200).send('OK');
});
重要なポイント:
- 検証には常に生のリクエストボディを使用し、解析済みのJSONは使用しないでください。解析すると空白やキーの順序が変更され、ハッシュが壊れる可能性があります。
- タイミング攻撃を防ぐために、タイミングセーフな比較を使用してください。
- シークレットを安全に保ち、定期的にローテーションし、ローテーション中は複数のアクティブなシークレットをサポートしてください。
リトライ戦略の設計
署名があっても、ネットワークの問題、サーバーのダウンタイム、タイムアウトにより配信が失敗する可能性があります。堅牢なリトライ戦略は、システムや送信者に過負荷をかけることなく、最終的な配信を保証します。
1. 迅速に確認し、非同期で処理する
Webhookエンドポイントは、できれば数秒以内にできるだけ早く2xxステータスで応答する必要があります。処理に時間がかかる場合は、イベントをバックグラウンド処理のためにキューに入れます。これによりタイムアウトを防ぎ、送信者が配信を成功と見なすことができます。
2. ジッター付き指数バックオフの実装
配信が失敗した場合(非2xx応答またはタイムアウト)、送信者は遅延を増やしながらリトライする必要があります。指数バックオフ(例:1秒、2秒、4秒、8秒...)はエンドポイントへの集中攻撃を防ぎます。ランダムなジッターを追加すると、多くのWebhookが同時に失敗したときのサンダリングハード問題を回避できます。
リトライスケジュールの例:
- 試行1:即時
- 試行2:30秒後
- 試行3:2分後
- 試行4:10分後
- 試行5:1時間後
最大リトライ回数(例:5回)に達した後、送信者は停止し、あなたに通知する必要があります(例:メールまたはダッシュボード経由)。
3. 冪等性で重複を処理する
リトライは重複配信を引き起こす可能性があります。エンドポイントは冪等である必要があります。つまり、同じイベントを複数回処理しても、1回処理した場合と同じ効果を持つ必要があります。ペイロードから一意のイベントIDを使用して保存します。処理前に、IDが既に確認されているかどうかをチェックし、確認されている場合は再処理せずに200を返します。
一般的なアプローチ:
- イベントIDに対するデータベースの一意制約。
- 最近処理されたIDのためのTTL付きRedisセット。
- ビジネスロジックにおける冪等性キー。
比較:署名付きペイロード vs その他のセキュリティ対策
| 方法 | 長所 | 短所 |
|---|---|---|
| HMAC署名 | 真正性と完全性を検証。ステートレス | 共有シークレットの管理が必要 |
| IP許可リスト | 実装が簡単 | IPは変更可能。動的環境には不向き |
| Basic認証 | 追加が簡単 | 毎リクエストで認証情報を送信。安全性が低い |
| ヘッダー内のJWT | クレームと有効期限を含められる | トークン発行/検証のオーバーヘッド |
HMAC署名は、ステートレスで効率的であり、正しく実装すれば強力なセキュリティを提供するため、事実上の標準となっています。
Webhookコンシューマーのベストプラクティス
- すべてのリクエストで署名を検証する(開発環境でも)。
- 迅速に応答し、重い処理はキューにオフロードする。
- すべてをログに記録する(ヘッダー、ペイロード、処理結果)。デバッグに役立つ。
- 失敗を監視し、繰り返しエラーに対してアラートを設定する。
- HTTPSを使用して転送中のペイロードを保護する。
- シークレットを定期的にローテーションし、ローテーション中は複数のアクティブなシークレットをサポートする。
Webhookプロデューサーのベストプラクティス
- 各コンシューマーに固有のシークレットでペイロードに署名する。
- 一意のイベントIDを含めることで冪等性を支援する。
- バックオフ付きでリトライし、試行回数に上限を設ける。
- ダッシュボードを提供し、コンシューマーが配信ログを表示し、イベントを再生できるようにする。
- 署名スキームを明確に文書化する。
Webhook実装のテスト
ngrokなどのツールを使用してローカルサーバーを公開し、Webhook配信をシミュレートします。署名検証と冪等性ロジックの単体テストを作成します。負荷テストには、署名付きリクエストを大規模に送信できるツールを検討してください。
デバッグ時には、生のペイロードとヘッダーを検査します。JSONフォーマッターはペイロード構造を視覚化するのに役立ちます。当社のJSON Formatterを試して、Webhookペイロードをすばやく整形および検証してください。
FAQ
署名付きWebhookペイロードとは何ですか?
署名付きペイロードには暗号署名(通常はHMAC)が含まれており、受信者が送信者の身元を検証し、ペイロードが改ざんされていないことを確認できます。共有シークレットとリクエストボディを使用して計算されます。
Webhookは何回リトライする必要がありますか?
普遍的な数はありませんが、一般的な慣行は指数バックオフ付きで3〜5回のリトライです。その後、イベントは失敗と見なされ、コンシューマーに通知して調査または手動再生を促す必要があります。
Webhook署名にHMACの代わりにJWTを使用できますか?
はい、JWTは機能しますが、複雑さが増します(トークン発行、有効期限)。HMACはよりシンプルで、ほとんどのユースケースに十分です。既存のインフラストラクチャと要件に基づいて選択してください。
Webhookペイロードを検査およびデバッグする準備はできましたか?無料のJSON Formatterを使用して、JSONを即座にフォーマットおよび検証してください。