Webhookを徹底解説:署名付きペイロードとリトライ戦略
Webhookを送信するサードパーティサービスを統合し、テストではすべてうまくいっているとします。しかし本番環境では、時折イベントが失われたり、重複配信が発生したり、さらに悪いことに悪意のある攻撃者が偽のペイロードをエンドポイントに送信したりすることに気づくでしょう。Webhookは概念は単純ですが、実際にはトリッキーです。この記事では、署名付きペイロードでWebhookを安全にし、堅牢なリトライ戦略を実装してイベント駆動型の統合を信頼できるようにする方法を説明します。
Webhookとは何か、なぜ重要なのか
Webhookは、特定のイベントによってトリガーされるユーザー定義のHTTPコールバックです。イベント(支払い成功や新規ユーザー登録など)が発生すると、ソースサービスはあなたが提供したURLにHTTP POSTリクエストを送信します。このプッシュベースのモデルはポーリングよりも効率的で、レイテンシとサーバー負荷を軽減します。しかし、真正性の検証と配信失敗の処理という課題が生じます。
署名付きペイロードでWebhookを保護する
検証がなければ、誰でもWebhookエンドポイントにリクエストを送信できます。署名付きペイロードは、リクエストが信頼できる送信元から来たことを証明する暗号署名を含めることでこれを解決します。
HMAC署名の仕組み
ほとんどのプロバイダーは、共有シークレットを使用したHMAC(Hash-based Message Authentication Code)を使用します。送信者はシークレットを使用してペイロードのハッシュを計算し、ヘッダー(例:X-Signature)に含めます。サーバーはハッシュを再計算して比較します。一致すれば、リクエストは本物です。
// 例:Node.jsでWebhook署名を検証する
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const hmac = crypto.createHmac('sha256', secret);
hmac.update(payload);
const digest = hmac.digest('hex');
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(digest));
}
重要:タイミング攻撃を防ぐために、常にタイミングセーフな比較を使用してください。また、署名は解析されたJSONオブジェクトではなく、生のリクエストボディに対して検証してください。解析によってペイロードが変更される可能性があるためです。
署名検証のベストプラクティス
- HTTPSを使用する:盗聴を防ぐために、常にTLS経由でWebhookを提供してください。
- シークレットをローテーションする:署名シークレットを定期的にローテーションし、ローテーション中は複数のアクティブなシークレットをサポートしてください。
- タイムスタンプを検証する:リプレイ攻撃を防ぐために署名にタイムスタンプを含めます。数分以上古いリクエストは拒否してください。
- 失敗をログに記録する:署名検証の失敗を監視して、攻撃や設定ミスを検出してください。
信頼性の高いリトライの実装
署名付きペイロードを使用しても、ネットワークの問題や一時的な障害によりWebhook配信が失敗する可能性があります。堅牢なリトライ戦略により、システムに過負荷をかけずにイベントが最終的に配信されることが保証されます。
ジッター付き指数バックオフ
すぐにリトライすると輻輳が悪化する可能性があります。代わりに指数バックオフを使用します。各リトライ間の待機時間を長くします。多くのWebhookが同時に失敗したときのサンダリングハード問題を避けるために、ジッター(ランダムな変動)を追加します。
// 例:指数バックオフとジッターを使用したリトライスケジュール
function getRetryDelay(attempt) {
const base = 1000; // 1秒
const max = 30000; // 30秒
const delay = Math.min(base * Math.pow(2, attempt), max);
return delay + Math.random() * 1000; // 最大1秒のジッターを追加
}
冪等性:重複の処理
リトライにより重複配信が発生する可能性があります。Webhookハンドラーは冪等でなければなりません。同じイベントを複数回処理しても、1回処理した場合と同じ効果になる必要があります。ペイロードのイベントIDを使用して処理済みイベントを追跡します。
- 処理済みのイベントIDをTTL付きでデータベースに保存します。
- 処理前に、イベントIDが既に存在するか確認します。存在する場合は、すぐに成功レスポンスを返します。
- 操作を冪等に設計します(例:挿入ではなくアップサートを使用)。
迅速に応答して確認する
Webhookエンドポイントは、できるだけ早く(理想的には数秒以内に)2xxステータスコードで応答する必要があります。処理に時間がかかる場合は、イベントを非同期処理用にキューに入れ、202 Acceptedを返します。これにより、タイムアウトや不要なリトライを防ぎます。
比較:Webhook vs. ポーリング
| 側面 | Webhook | ポーリング |
|---|---|---|
| レイテンシ | ほぼリアルタイム | 間隔に依存 |
| サーバー負荷 | 低い(プッシュのみ) | 高い(常時リクエスト) |
| 複雑さ | 高い(リトライ、セキュリティが必要) | 低い |
| 信頼性 | リトライロジックが必要 | 本質的に信頼性が高い |
ステップバイステップ:安全なWebhookレシーバーの構築
- 有効な証明書を持つHTTPSエンドポイントを設定します。
- 署名ヘッダー(例:
X-Signature)とタイムスタンプを抽出します。 - 共有シークレットと生のボディを使用して署名を検証します。
- タイムスタンプを確認して古いリクエスト(例:5分以上前)を拒否します。
- ペイロードを解析し、重複するイベントIDをチェックします。
- 必要に応じてイベントを非同期で処理し、200 OKで応答します。
- 監査とデバッグのためにイベントをログに記録します。
FAQ
署名付きWebhookペイロードとは何ですか?
署名付きペイロードには暗号署名(通常はHMAC)が含まれており、受信者はリクエストが信頼できるソースから来たもので、改ざんされていないことを検証できます。
失敗したWebhookを何回リトライすべきですか?
ほとんどのシステムは指数バックオフで3〜5回リトライします。その後、イベントは通常失敗としてマークされ、手動介入やデッドレターキューの処理が必要になる場合があります。
HTTPSなしでWebhookを使用できますか?
強くお勧めしません。HTTPSがないと、ペイロードと署名が傍受または変更される可能性があります。Webhookエンドポイントには常にTLSを使用してください。
Webhookペイロードやログをデバッグする際には、JSONを正しくフォーマットすることが不可欠です。JSON Formatterを使用して、JSONペイロードをすばやく整形して検証してください。