Webhook解説:署名付きペイロードとリトライ戦略
決済ゲートウェイやCI/CDサービスを統合し、リアルタイムのイベントを受信する必要が出てきました。エンドポイントを設定したものの、すぐに2つの重要な疑問に直面します。リクエストが本当にそのサービスから来たのかをどう確認するか?そして、イベント発生時にサーバーがダウンしていたらどうなるか?この記事では、信頼性の高いWebhookの基盤である署名付きペイロードとリトライ戦略を説明することで、両方の疑問に答えます。
Webhookとは何か?
Webhookはユーザー定義のHTTPコールバックです。ソースシステムでイベント(新しいコミット、決済成功など)が発生すると、指定したURLにHTTP POSTリクエストを送信します。ポーリングとは異なり、Webhookはほぼリアルタイムでデータをプッシュするため、レイテンシとサーバー負荷を軽減します。しかし、このプッシュモデルには課題があります。真正性の検証と配信失敗への対処です。
署名付きペイロードが重要な理由
誰でもあなたのエンドポイントにHTTPリクエストを送信できます。検証がなければ、攻撃者がイベントを偽造し、不正な操作を引き起こす可能性があります。署名付きペイロードは、共有秘密鍵を使用してリクエストボディに対して計算された暗号署名を含めることでこれを解決します。受信側は署名を再計算して比較し、ペイロードが改ざんされておらず、期待される送信者から来たことを確認します。
HMAC署名の仕組み
ほとんどのサービスはHMAC-SHA256を使用します。送信者は秘密鍵でペイロードをハッシュ化して署名を作成し、ヘッダー(例:X-Signature)に含めます。受信者も同様に計算し、タイミング攻撃を防ぐために定数時間関数を使用して比較します。
ステップバイステップ:署名付きペイロードの検証
- 署名を抽出する(例:
X-Signatureヘッダーから)。 - 生のリクエストボディを取得する(文字列またはバッファとして)。フォーマットの変更は署名を壊すため、最初にパースしないでください。
- 期待される署名を計算する(秘密鍵を使用したHMAC-SHA256)。
- 署名を比較する(定数時間比較関数を使用)。
- 一致しない場合はHTTP 401でリクエストを拒否する。
Expressを使用したNode.jsの例を次に示します:
const crypto = require('crypto');
const express = require('express');
const app = express();
app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
const signature = req.headers['x-signature'];
const secret = process.env.WEBHOOK_SECRET;
const payload = req.body; // raw buffer
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.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(payload);
// ... handle event
res.status(200).send('OK');
});
注意: 検証には常に生のボディを使用してください。JSONをパースするミドルウェアはペイロードを変更し、署名の不一致を引き起こします。
リトライ戦略の設計
署名付きペイロードを使用しても、ネットワークの問題やサーバーのダウンタイムにより配信が失敗する可能性があります。堅牢なリトライ戦略により、イベントが失われないようにします。主な考慮事項:
1. 迅速に確認応答する
署名を検証し、イベントを処理のためにキューに入れたらすぐに2xxステータスで応答します。これにより、送信側がタイムアウトして不必要にリトライするのを防ぎます。
2. 冪等性
リトライにより同じイベントが複数回配信される可能性があります。冪等性キー(多くの場合、ペイロードまたはヘッダーに含まれる)を使用して重複を排除します。処理済みのイベントIDを保存し、重複を無視します。
3. ジッター付き指数バックオフ
エンドポイントがエラー(非2xx)を返した場合、送信側は遅延を増やしながらリトライする必要があります。指数バックオフ(例:1秒、2秒、4秒、8秒)は障害時の負荷を軽減します。ランダムなジッターを追加すると、サンダリングハード問題を防ぎます。
一般的なリトライスケジュール:
| 試行回数 | 遅延 |
|---|---|
| 1 | 即時 |
| 2 | 1分 |
| 3 | 5分 |
| 4 | 30分 |
| 5 | 2時間 |
4. デッドレターキュー
最大リトライ回数に達した後、イベントを手動検査用のデッドレターキューに移動します。これにより、無限のリトライとデータ損失を防ぎます。
5. 監視とアラート
配信成功率、レイテンシ、失敗理由を追跡します。失敗の急増に対するアラートを設定し、問題を早期に検出します。
よくある落とし穴とベストプラクティス
- 常に署名を検証する—受信データを盲目的に信頼しないでください。
- HTTPSを使用する—転送中のペイロードを保護します。
- 秘密鍵を定期的にローテーションする—ローテーション中は複数のアクティブな秘密鍵をサポートします。
- Webhookイベントをログに記録する(機密データはマスクする)—デバッグ用。
- サンプルペイロードでテストする—Webhook配信をシミュレートするツールを使用します。
FAQ
Webhookにおける署名付きペイロードとは?
署名付きペイロードには暗号署名(通常はHMAC)が含まれ、受信者がリクエストの真正性と整合性を検証できるようにします。
Webhookは何回リトライすべきですか?
固定の回数はありません。一般的な慣行では、指数バックオフで3〜10回のリトライが行われます。その後、イベントはデッドレターキューに送られることがよくあります。
署名なしでWebhookを使用できますか?
可能ですが、強く推奨されません。署名がないと、エンドポイントは偽造リクエストやデータ侵害に対して脆弱になります。
Webhookペイロードをテストする準備はできましたか?JSON Formatterを使用して、JSONペイロードを迅速に検査および検証してください。