Webhook解説:署名付きペイロードとリトライ

Backend2026-10-24TryQuickToolBox

APIを構築し、イベントが発生したときに他のサービスへ通知する必要が出てきました。Webhookエンドポイントを設定したものの、HTTPリクエストを確実に配信するのは思ったより難しいとすぐに気づくでしょう。ネットワーク障害、悪意ある攻撃者、重複イベントが混乱を引き起こす可能性があります。このガイドでは、署名付きペイロードとリトライを備えたWebhookを実装し、堅牢な連携を構築する方法を解説します。

Webhookとは何か?

Webhookはユーザー定義のHTTPコールバックです。アプリケーションでイベントが発生すると、購読者が提供したURLへHTTP POSTリクエストを送信します。これにより、ポーリングなしでサービス間のリアルタイム通信が可能になります。

例えば、決済処理サービスは課金が成功したときにアプリへWebhookを送信できます。アプリはその場で注文ステータスを更新できます。

なぜWebhookペイロードに署名するのか?

Webhookエンドポイントは公開されているため、誰でも偽のリクエストを送信できます。検証を行わないと、アプリが不正なイベントを処理してしまう可能性があります。ペイロードに署名することで、リクエストが自サービスから発信され、改ざんされていないことを保証できます。

署名には通常、送信者と受信者の間で共有されるシークレットを使用します。送信者はペイロードに対してハッシュベースのメッセージ認証コード(HMAC)を計算し、ヘッダーに含めます。受信者はHMACを再計算して比較します。

署名の生成と検証方法

ほとんどの実装ではHMAC-SHA256を使用します。以下はPythonでの簡単な例です:

import hmac
import hashlib
import json

def generate_signature(payload, secret):
    body = json.dumps(payload, separators=(',', ':')).encode('utf-8')
    signature = hmac.new(secret.encode('utf-8'), body, hashlib.sha256).hexdigest()
    return signature

def verify_signature(payload, signature, secret):
    expected = generate_signature(payload, secret)
    return hmac.compare_digest(expected, signature)

Webhookを送信するときは、X-Signatureのようなヘッダーに署名を含めます。受信者は生のボディを抽出し、HMACを再計算して比較します。タイミング攻撃を防ぐため、常に定数時間比較関数を使用してください。

確実な配信のためのリトライ実装

Webhookはインターネット経由で送信されるため、失敗は避けられません。サーバーがダウンしている、ネットワークが遅い、購読者がエラーを返すといったことが起こります。指数バックオフを伴うリトライが不可欠です。

リトライ戦略

一般的なアプローチは、遅延を増やしながらリトライすることです:1分、5分、30分、2時間など。最大試行回数(例:10回)または合計時間枠(例:24時間)に達したら停止します。

特定の条件でのみリトライします:ネットワークエラー、5xxレスポンス、429(Too Many Requests)。4xxエラー(429を除く)ではリトライしないでください。これらはクライアント側のミスを示しており、リトライしても解決しないためです。

リトライロジックの例

以下はrequestsライブラリを使用したPythonでの簡略化されたリトライ機構です:

import requests
import time
from requests.exceptions import RequestException

def send_webhook(url, payload, secret, max_retries=5):
    headers = {'Content-Type': 'application/json'}
    signature = generate_signature(payload, secret)
    headers['X-Signature'] = signature
    
    for attempt in range(max_retries):
        try:
            response = requests.post(url, json=payload, headers=headers, timeout=10)
            if response.status_code < 500 and response.status_code != 429:
                return response.status_code
        except RequestException:
            pass
        
        # Exponential backoff with jitter
        delay = (2 ** attempt) + (random.random() * 0.1)
        time.sleep(delay)
    
    return None  # Failed after retries

本番環境では、失敗した試行をキューに保存し、非同期で処理します。Celeryやクラウドキュー(AWS SQS、Google Cloud Tasks)などのツールが役立ちます。

Webhookセキュリティのベストプラクティス

重複と冪等性の処理

リトライは重複配信を引き起こす可能性があります。ペイロードに一意のイベントIDを含め、受信者側で処理済みIDを追跡します。イベントIDが以前に見られた場合は無視します。

あるいは、イベントハンドラーを冪等に設計します。例えば、同じデータでレコードを複数回更新しても、1回行うのと同じ結果になるようにします。

Webhookのテスト

ngrokなどのツールを使ってローカルサーバーを公開し、Webhook配信をテストします。エラーコードを返して失敗をシミュレートし、リトライが期待どおりに行われることを確認します。

FAQ

署名付きWebhookペイロードとは何ですか?

署名付きペイロードには暗号署名(通常はHMAC)が含まれ、受信者はリクエストが信頼できる送信元から来て改ざんされていないことを検証できます。

失敗したWebhookは何回リトライすべきですか?

信頼性の要件によります。一般的な慣行は、24時間にわたって指数バックオフで5〜10回リトライすることです。購読者が手動でリトライをトリガーできるようにもしてください。

署名なしでWebhookを使用できますか?

できますが、リスクがあります。署名がないと、誰でも偽のイベントを送信できます。不正を防ぐため、本番環境では常にWebhookに署名してください。

Webhookペイロードをテストする準備はできましたか?送信前にJSONペイロードを検証して整形するには、JSON Formatterをご利用ください。