Webhook解説:署名付きペイロードとリトライロジック
APIを構築し、イベントが発生したときに他のサービスに通知する必要が出てきました。ポーリングは非効率なので、Webhookに目を向けるでしょう。しかし、すぐにWebhookが失敗したり、なりすまされたり、重複処理を引き起こしたりする可能性があることに気づきます。この記事では、署名付きペイロードでWebhookを安全にし、堅牢なリトライロジックを実装する方法を説明します。
Webhookとは?
Webhookはユーザー定義のHTTPコールバックです。システムでイベントが発生すると(例:支払いが成功した)、サブスクライバーが提供したURLにHTTP POSTリクエストを送信します。ペイロードには通常、JSONまたはフォーム形式でイベントデータが含まれます。
Webhookは、常時ポーリングすることなくサービス間のリアルタイム通信を可能にします。決済ゲートウェイ、CI/CDツール、SaaSプラットフォームで広く利用されています。
なぜWebhookを保護するのか?
セキュリティがない場合、Webhook URLを知っている誰でも偽のリクエストを送信でき、データの破損や詐欺につながります。また、攻撃者が古いリクエストをリプレイする可能性もあります。共有シークレットでペイロードに署名することで、真正性と完全性を確保できます。
HMACによるペイロードの署名
HMAC(Hash-based Message Authentication Code)は、Webhookに署名する一般的な方法です。送信者と受信者は秘密鍵を共有します。送信者は秘密鍵を使用してペイロードのHMACを計算し、ヘッダー(例:X-Signature)に含めます。受信者はHMACを再計算して比較します。
ステップバイステップ:署名付きWebhookの送信
- ペイロード(JSON)を作成します。
- 秘密鍵を使用してペイロードのHMAC-SHA256を計算します。
- 16進ダイジェストをヘッダーに含めます(例:
X-Signature: sha256=...)。 - POSTリクエストを送信します。
// Node.js example using crypto
const crypto = require('crypto');
const secret = 'your_shared_secret';
const payload = JSON.stringify({ event: 'payment.success', amount: 100 });
const signature = crypto.createHmac('sha256', secret).update(payload).digest('hex');
// Send POST with header X-Signature: sha256=signature
署名の検証
受信側では、署名ヘッダーを抽出し、生のリクエストボディのHMACを計算し、タイミング攻撃を防ぐために定数時間比較を使用して比較します。
// Express.js example
app.post('/webhook', (req, res) => {
const signature = req.headers['x-signature'];
const computed = crypto.createHmac('sha256', secret).update(req.rawBody).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(computed))) {
return res.status(401).send('Invalid signature');
}
// Process payload
res.status(200).send('OK');
});
注意:パースされたJSONではなく、生のボディを使用してください。パースするとペイロードが変更される可能性があります。
リプレイ攻撃の防止
署名付きペイロードにタイムスタンプを含め、数分より古いリクエストを拒否します。また、一意のイベントIDを含め、処理済みのIDを保存して重複を避けます。
信頼性の高い配信のためのリトライロジック
Webhookエンドポイントはダウンしたり遅くなったりする可能性があります。指数バックオフとジッターを使用したリトライを実装します。最大リトライ回数(例:5回)と各リクエストのタイムアウトを設定します。
リトライ戦略
- 指数バックオフ: 1秒、2秒、4秒、8秒など待機します。
- ジッター: ランダム性を追加してサンダリングハードを回避します。
- 冪等性: 冪等キーを使用して、重複配信が重複アクションを引き起こさないようにします。
リトライスケジュールの例:1回目は5秒後、2回目は30秒後、3回目は2分後、4回目は10分後、5回目は1時間後。
障害の処理
すべてのリトライが失敗した場合、失敗をログに記録し、サブスクライバーに通知します(例:メールまたはダッシュボード経由)。Webhookを手動で再送信する方法を提供します。
比較:署名付きペイロード vs. ベーシック認証
| 方法 | セキュリティ | 複雑さ | リプレイ保護 |
|---|---|---|---|
| HMAC署名付きペイロード | 高 | 中程度 | あり(タイムスタンプ付き) |
| ベーシック認証 | 低 | 低 | なし |
| ヘッダーのAPIキー | 中 | 低 | なし |
ベストプラクティス
- Webhookエンドポイントには常にHTTPSを使用します。
- 秘密鍵を定期的にローテーションします。
- 処理前にペイロードスキーマを検証します。
- 迅速に応答し(2xx以内)、非同期で処理します。
- Webhook配信メトリクス(成功率、レイテンシ)を監視します。
FAQ
Webhook署名を検証するにはどうすればよいですか?
秘密鍵を使用して生のリクエストボディのHMACを計算し、定数時間比較を使用して署名ヘッダーと比較します。
Webhookが失敗した場合はどうすればよいですか?
指数バックオフとジッターを使用したリトライを実装します。すべてのリトライが失敗した場合、失敗をログに記録し、手動再送信オプションを提供します。
署名なしでWebhookを使用できますか?
推奨されません。署名がないと送信者を検証できず、システムがなりすましやリプレイ攻撃に対して脆弱になります。
Webhookペイロードをテストする際には、JSONレスポンスを検査する必要があるかもしれません。JSONデータをすばやく整形して検証するには、JSON Formatterをご利用ください。