Webhook解説:署名付きペイロードとリトライ
Webhookに署名付きペイロードとリトライが必要な理由
サードパーティサービスを統合し、エンドポイントにWebhookが送信されるようにしたばかりだとします。テストではすべてが機能しますが、本番環境ではイベントの欠落、重複配信、さらに悪いことに、誰かがWebhook URLへのリクエストを偽装していることに気づきます。適切なセキュリティと信頼性の対策がなければ、Webhookはバグや脆弱性の原因になり得ます。
Webhookは、イベントが発生したときにアプリケーションに通知するHTTPコールバックです。概念はシンプルですが、信頼境界とネットワークの信頼性の問題を横断するため、実際には注意が必要です。このガイドでは、2つの重要な側面を扱います:署名付きペイロードによる真正性の検証と、リトライによる配信の保証です。すぐに適用できる実践的な実装を見ていきましょう。
署名付きペイロードの理解
署名付きペイロードは、送信者がWebhookが自分から送信され、改ざんされていないことを証明する方法です。送信者は共有シークレットを使用してリクエストボディ(場合によってはヘッダー)の暗号署名を計算し、その署名をヘッダーに含めます。エンドポイントは署名を再計算して比較します。
最も一般的なアルゴリズムはHMAC-SHA256です。高速で安全、そして広くサポートされています。仕組みは次のとおりです:
- 送信者と受信者は秘密鍵を共有します(Webhook設定で構成)。
- 各Webhookについて、送信者はHMAC-SHA256を使用してペイロードを秘密鍵でハッシュ化し、署名を作成します。
- 署名は
X-Signatureのようなヘッダーで送信されます。 - 受信者は同じHMACを計算し、タイミング攻撃を防ぐために定数時間比較を使用して比較します。
Node.jsでのHMAC検証の実装
具体的な例を見てみましょう。送信者がX-Signatureに16進文字列として署名を含めるとします。Expressルートで検証する方法は次のとおりです:
const crypto = require('crypto');
const express = require('express');
const app = express();
// 署名検証には生のボディを使用
app.use('/webhook', 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');
}
// イベントを処理
const event = JSON.parse(req.body);
console.log('Received event:', event.type);
res.status(200).send('OK');
});
app.listen(3000);
重要:署名の計算には常に生のリクエストボディを使用し、解析済みのJSONは使用しないでください。解析と再文字列化により空白や順序が変わり、署名が壊れる可能性があります。
タイムスタンプとリプレイ保護
署名だけではリプレイ攻撃を防げません。攻撃者は有効な署名付きリクエストをキャプチャし、後で再送信する可能性があります。これを軽減するには、署名付きペイロードまたはヘッダーにタイムスタンプを含めます。たとえば、Unix時間を含むX-Timestampヘッダーを追加します。受信者はタイムスタンプが許容範囲(例:5分)内であることを確認し、古いリクエストを拒否します。
また、改ざんを防ぐためにタイムスタンプを署名計算に含めます。送信者はtimestamp + '.' + bodyに対してHMACを計算します。受信者も同様に計算して比較します。
信頼性の高い配信のためのリトライ実装
Webhookはパブリックインターネットを経由するため、障害は避けられません:ネットワークタイムアウト、サーバーエラー(5xx)、またはエンドポイントが一時的にダウンしている場合などです。堅牢なWebhookシステムは失敗した配信をリトライする必要があります。
リトライ戦略
一般的なリトライ戦略には次のものがあります:
- 固定遅延: 固定間隔(例:1分)後にリトライ。シンプルですが、多数の失敗が発生すると過負荷になる可能性があります。
- 指数バックオフ: 各試行後に待機時間を長くします(例:1秒、2秒、4秒、8秒...)。最も一般的で推奨されるアプローチです。
- ジッター: 遅延にランダム性を追加して、多数のWebhookが同時に失敗したときのサンダリングハード問題を回避します。
典型的なポリシーは、10秒から始めて指数バックオフとジッターで最大5回リトライするというものです。その後、配信を失敗としてマークし、場合によってはアラートを出します。
冪等性:重複の処理
リトライは重複配信を引き起こす可能性があります。エンドポイントは冪等である必要があります。つまり、同じイベントを2回処理しても、顧客への二重請求などの副作用を引き起こしてはなりません。ペイロードに一意のイベントID(例:event.id)を含め、処理済みIDを妥当な期間保存します。処理前に、IDが既に確認されているかチェックします。
あるいは、操作を自然に冪等になるように設計します(例:挿入ではなくupsertを使用)。
迅速な応答
Webhookエンドポイントは迅速に受信を確認し(数秒以内)、イベントを非同期で処理する必要があります。処理に時間がかかりすぎると、送信者がタイムアウトしてリトライし、重複が発生する可能性があります。キューを使用します:署名を検証した直後に200 OKで応答し、その後イベントをバックグラウンド処理用にエンキューします。
Webhookセキュリティ対策の比較
| 対策 | 目的 | 実装の複雑さ |
|---|---|---|
| HMAC署名 | 真正性と整合性の検証 | 低 |
| タイムスタンプ検証 | リプレイ攻撃の防止 | 低 |
| 冪等性キー | 重複処理の回避 | 中 |
| IP許可リスト | 既知の送信者IPに制限 | 低(ただし脆弱) |
| HTTPS | 転送中のデータを暗号化 | 低 |
IP許可リストは層を追加できますが、送信者IPが変更される可能性があるため信頼性はありません。主に署名に依存してください。
Webhookエンドポイントのテスト
ngrokなどのツールを使用してローカルサーバーを公開し、プロバイダーのダッシュボードからテストWebhookをトリガーします。署名が正しく検証されること、および失敗をシミュレートして(例:最初の数回の試行で500を返す)リトライが機能することを確認します。
デバッグ時には、JSONペイロードを検査する必要があるかもしれません。JSON Formatterは、Webhookペイロードを迅速に整形して検証するのに役立ちます。
FAQ
署名付きWebhookペイロードとは何ですか?
署名付きペイロードには暗号署名(通常はHMAC)が含まれ、受信者はリクエストが期待される送信者から来たものであり、改ざんされていないことを検証できます。共有シークレットを使用して計算されます。
失敗したWebhookを何回リトライすべきですか?
普遍的な数はありませんが、指数バックオフで3〜5回のリトライが一般的です。その後、失敗をログに記録し、アラートを検討します。目標は、無限のリトライなしで一時的な問題を処理することです。
Webhook署名にJWTを使用できますか?
はい、一部のプロバイダーはJWT(JSON Web Tokens)を使用してWebhookに署名します。トークンにはクレームが含まれ、秘密鍵または秘密鍵で署名されます。検証は同様です:署名を検証し、有効期限などのクレームを確認します。
安全なWebhookを実装する準備はできましたか?強力なシークレットを生成し、HMAC検証を追加し、冪等性を備えたリトライを設定することから始めましょう。開発中の迅速なペイロード検査には、JSON Formatterを試してWebhook JSONを整形・検証してください。