Webhookは、サードパーティサービスからリアルタイムの更新を受け取るための最も強力な方法の一つです。Stripe、GitHub、Shopify、Twilioからの単一のHTTP POSTは、顧客への請求、リポジトリの更新、注文の発送、確認SMSの送信など、アプリケーションの重要なビジネスロジックをトリガーできます。
しかし、すべてのWebhookリクエストは公共のインターネット経由で届きます。つまり、あなたのWebhook URLを推測したり発見したりした人は誰でも、完全に正当に見える悪意のあるペイロードを送信できるということです。適切な認証がなければ、あなたのアプリケーションは本物のイベントと偽造されたイベントを区別する方法がありません。
そこで登場するのがWebhookシグネチャ検証です。これは、受信するすべてのWebhookリクエストが、期待するサービスから本当に送信されたものであり、転送中に改ざんされていないことを保証するシンプルで標準化されたメカニズムです。
この包括的なガイドでは、Webhookシグネチャ検証がどのように機能するのか、そして一般的な言語でそれを正しく実装する方法を正確に学びます。また、避けるべき一般的な間違いや、すべてを迅速かつ確実にエンドツーエンドでテストする方法も紹介します。
ボタン
Webhookシグネチャ検証とは?
Webhookシグネチャ検証とは、受信したWebhookリクエストが実際に期待するサービスから送信されたものであり、改ざんされていないことを確認するプロセスです。
ほとんどのプロバイダーは、SHA-256またはSHA-512を使用したHMAC(Hash-based Message Authentication Code)を使用しています。サービスは以下を計算します。
signature = HMAC-SHA256(secret_key, payload)
その後、シグネチャをヘッダー(通常はX-Signature、Signature、またはX-Hub-Signature-256)で送信します。
あなたのサーバーは次の手順を踏みます。
- ペイロードを未加工のバイトとして受信します(重要!)
- 保存されているシークレットを使用してHMACを再計算します
- 計算されたシグネチャと受信したシグネチャを比較します
それらが完全に一致する場合、Webhookを処理します。そうでなければ、HTTP 401または403を返します。
HMAC-SHA256が業界標準である理由
プロバイダーがHMAC-SHA256を選ぶのには明確な理由があります。
- 高速: 控えめなハードウェアでも非常に高速です。
- セキュア: SHA-256は2025年現在も破られていません。
- シンプル: 秘密鍵が1つだけで、公開鍵/秘密鍵のペアを管理する必要がありません。
- 標準化: あらゆる言語のライブラリが正しく実装しています。
GitHub、Stripe、Shopify、Slack、その他多数のサービスがHMAC-SHA256を使用しています。
Node.jsでWebhookシグネチャ検証を実装する方法
Node.jsでの実例から始めましょう。
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const hmac = crypto.createHmac('sha256', secret);
const computedSignature = hmac.update(payload).digest('hex');
return crypto.timingSafeEqual(
Buffer.from(computedSignature),
Buffer.from(signature)
);
}
注目すべき重要点:
- タイミング攻撃を防ぐために、常に
crypto.timingSafeEqualを使用してください。 - 文字列に対して
===を使用しないでください — 脆弱性があります。 - 定数時間比較を確実にするために、
Buffer.from()を使用してください。
Expressミドルウェアの例:
app.post('/webhooks/stripe', (req, res, next) => {
const signature = req.headers['stripe-signature'];
const secret = process.env.STRIPE_WEBHOOK_SECRET;
// Get raw body (Express needs middleware to preserve raw body)
const rawBody = req.rawBody || req.body; // use body-parser with verify option
if (!verifyWebhookSignature(rawBody, signature, secret)) {
return res.status(401).send('Invalid signature');
}
// Signature is valid → process the event
next();
});
Python実装 (FastAPI + Pydantic)
from fastapi import FastAPI, Request, HTTPException
import hmac
import hashlib
app = FastAPI()
def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
computed = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(computed, signature)
@app.post("/webhooks/github")
async def github_webhook(request: Request):
signature = request.headers.get("X-Hub-Signature-256")
if not signature:
raise HTTPException(status_code=401, detail="Missing signature")
payload = await request.body()
if not verify_signature(payload, signature.split('=')[1], SECRET):
raise HTTPException(status_code=401, detail="Invalid signature")
# Process webhook
return {"status": "ok"}
開発者が陥りやすい一般的な落とし穴(とその回避方法)
1. JSON.stringify()またはパースされたボディを使用する
多くのフレームワークはJSONを自動的にパースします。空白、キーの順序、書式設定が異なるため、これが検証を破綻させます。
解決策:常にパースする前に生のボディをキャプチャしてください。
- Expressの場合:
body-parserを{ verify: true }とともに使用します。 - FastAPIの場合:
await request.body()を使用します。
2. 文字列を === で比較する
タイミング攻撃によって情報が漏洩する可能性があります。crypto.timingSafeEqualまたはhmac.compare_digestを使用してください。
3. コード内にシークレットを保存する
環境変数またはシークレットマネージャー(AWS Secrets Manager、HashiCorp Vaultなど)を使用してください。
4. リプレイ攻撃の処理を忘れる
ほとんどのプロバイダーはタイムスタンプを含んでいます。イベントが最近のものであること(例:5分以内)を確認してください。
const timestamp = req.headers['X-Signature-Timestamp'];
if (Date.now() - timestamp > 5 * 60 * 1000) {
return res.status(401).send('Timestamp too old');
}
5. SHA-1の使用(まだ発生しています!)
GitHubは2022年にSHA-1を非推奨にしました。常にSHA-256を使用してください。
ApidogでWebhookシグネチャ検証をテストする
Webhookを手動でテストするのは手間がかかります。リクエストを送信し、ログを確認し、修正し、繰り返すことになります。
Apidogはこれを簡単にしてくれます。
Apidogプロジェクトで、左サイドバーの+アイコンをクリックし、「New Other Protocol APls」>「Webhook」を選択します。
Webhookを作成したら、エディターで以下のフィールドを埋めます。リクエストメソッド:通常はPOST。Webhook名:APIドキュメントとOpenAPIエクスポートに表示されます(例:order)。デバッグURL(オプション):テストリクエストの送信に使用される実際のURL。注:これはテスト目的のみであり、ドキュメントには含まれません。その他の情報:リクエストボディなど。
必要なすべてのフィールドに入力したら、保存をクリックしてください。
デバッグURLフィールドにWebhook URLを入力し、送信をクリックするだけで、Webhook呼び出しをシミュレートできます。
ApidogのWebhookシミュレーターのおかげで、デバッグ時間を何時間も節約できました。Stripeの正確なstripe-signature形式やGitHubのsha256=...プレフィックスにも対応しています。
実例:Stripe Webhookの検証
Stripeは特殊なヘッダー形式を使用しています。
stripe-signature: t=1681234567,v1=abc123...,v0=def456...
次のことを行う必要があります。
t=タイムスタンプを抽出します。v1=シグネチャ(推奨)を抽出します。payload + timestampを使用して再計算します。v1部分のみを比較します。
Stripeはこの複雑さを処理するための公式ライブラリを提供しています。
const stripe = require('stripe')('sk_...');
stripe.webhooks.constructEvent(payload, sigHeader, endpointSecret);
しかし、自分で実装する必要がある場合は、基盤となるHMACを理解することが重要です。
高度なトピック:複数のシグネチャを許容する
一部のプロバイダー(Stripeなど)は、後方互換性のために複数のシグネチャを送信します。あなたのコードは次のことを行うべきです。
- ヘッダーを
,で分割します。 - それぞれを試します。
- いずれかが一致すれば受け入れます。
2025年のセキュリティベストプラクティス
- Webhookシークレットを90日ごとにローテーションします。
- 可能な場合は短命のシークレットを使用します。
- 検証失敗をログに記録しますが、シークレットは決して記録しないでください。
- ブルートフォース攻撃を防ぐために、Webhookエンドポイントにレート制限をかけます。
- 常にHTTPSを使用します。
結論:小さな検証ステップ、大きなセキュリティ上の利益
Webhookシグネチャ検証は些細な詳細のように聞こえるかもしれません。しかし、これは安全なアプリケーションと、攻撃者が簡単に悪用できるアプリケーションとの違いを生みます。
これを正しく実装し、Apidogのようなツールで徹底的にテストすることで、あなたの統合が保護されていることを知って安らかに眠ることができます。
今すぐApidogを無料でダウンロードして、5分以内に最初のWebhookを検証しましょう。これは、あなたのコードが実際に機能することを証明する最速の方法です。
ボタン
