You’ve got a service that needs to react when something happens somewhere else. A payment clears. A customer record changes. A repo gets a push. You could poll an API every minute and waste cycles asking “anything new?” over and over, or you can let the source system call you when the event happens.
戻ります。 200、そして、完了と呼ぶ。 そのバージョンは、誰かが偽の要求を送信したり、有効な要求を再生したり、またはフレームワークが署名検証前にボディを解析したためハンドラーが破綻したりするまで、正しく動作します。
このガイドは、実際に生産環境で使用するパスを取っています。 例は小さくてコピーできるように設計されていますが、重要な部分を含んでいます: raw body handling、HMAC検証、タイムスタンプチェック、高速アッセント、実用的なデバッグ。
目次
- Webhookとは何か、そしてそれを使用する理由
- Webhook HTTP要求の構造
- 安全にWebhook署名を検証する方法
- リプレイ攻撃の防止
- Node.jsでWebhook受信者を作る
- PythonでWebhook受信者を作る
- GoでWebhook受信者を作る
- 必須のデバッグテクニック
- Webhookがプロダクション用に準備されているかどうかのチェックリスト
- targetLanguage
Cloudflare
Capacitor
GitHub invoice.paid, order.createdCapgo pushcode
実際のシステムでは、このパターンが現れます。 それは、ビジネスイベントに簡潔にマップされるからです。 Stripeは支払い結果を投稿します。 GitHubはリポジトリのアクティビティを投稿します。 Shopifyは注文の更新を投稿します。 形式は単純ですが、生産的な動作はそうではありません。 金銭、権限、または在庫を更新するWebhookには、リトライ、重複、信頼できないトラフィックが含まれる場合に、どのパブリックAPIエンドポイントと同じ注意を払う必要があります。
心のモデルが役に立つ
Webhookフローをフレームする有用な方法は、4つの部分が協力しているように見えることです。
- ソースシステムイベントを検出するサービス
- 宛先エンドポイントWebhookを受信するHTTPルート
- イベントイベント名が付いた変更、例えば
invoice.paidまたはpush. - ペイロード. The request body with the details your code needs.
既に発生した事実についての情報を提供するプロバイダーが送信します。 その情報を検証し、リクエストが最新であることを確認し、最後に変更を適用するというのは、多くの基本的なチュートリアルが認めていない重要な部分です。 実稼動環境では、重複配信は通常の動作であり、コーナーキースではありません。
実用的なルール: イベント駆動型の更新にはWebhookを使用し、スケジュールされた読み取り、バックフィル、または出発イベントを提供しないプロバイダーに適した読み取りにはポーリングを使用します。
より広範な ワークフロー自動化とデータ統合のチームが作業している場合、Webhookはシステムを無駄なリクエストトラフィックなしで同期させるイベント層になります。 統合重視のサービスで作業している場合、Capgoの バックエンド開発記事 は、リトライ、キュー、観察性、エラー処理などのコア問題がリトライ、キュー、観察性、エラー処理などのコア問題が現れることを意味します。
実稼動環境で機能するものと機能しないもの
機能するセットアップは、通常、面白みのない設計です。 必要なイベントにのみサブスクライブし、エンドポイントをプロバイダーまたはイベントファミリーによってスコープする。 イベントIDを保存して、重複配信が副作用を繰り返さないようにします。 リクエストが検証されキューに登録されたら、速い2xxのレスポンスを返し、次に遅いビジネスロジックを非同期に実行します。
脆弱なバージョンは容易に認識できます。1 つの汎用エンドポイントはすべてのものを処理します。署名のチェックは、早期テスト中にはスキップされ、戻って来ません。ハンドラーは、イベントが有効か古いものかを確認する前に、直接重要なテーブルに書き込むことになります。その結果はデモで機能しますが、リトライの嵐、プロバイダーの障害、または攻撃者が古い要求を再生した場合には失敗します。
その取引は、このガイドの残りの部分を定義します。ウェブホック受信者の「hello world」バージョンは小さく、生産用のバージョンは署名検証、再生防御、重複処理、デバッグ用のハックを最初から組み込んでいます。
ウェブホックのHTTP要求の構造
ウェブホックの要求を書く前に、codeを書く前に、HTTP要求をフレームワークのオブジェクトとしてではなく、raw HTTPとして見ることが役に立ちます。ウェブホックは一般的に、パブリックエンドポイントへのHTTP POST要求で、ヘッダーとJSONボディが含まれます。
シンプルなraw要求
POST /webhooks/orders HTTP/1.1
Host: your-app.example
Content-Type: application/json
User-Agent: Provider-Webhooks/1.0
X-Webhook-Signature: sha256=abc123example
X-Webhook-Timestamp: 1712345678
{
"event": "order.created",
"id": "evt_123",
"data": {
"order_id": "ord_456",
"status": "created"
}
}
重要な部分は簡単です:
- メソッド実際には、ウェブホックの配信は通常POST要求です。
- Content-Typeほとんどの現代のプロバイダーはJSONを送信します。
- User-Agentデバッグに役立つかもしれませんが、信頼できるものではありません。
- 署名ヘッダ. 提供者の有効性確認を実行します。
- タイムスタンプヘッダ. 古いまたは再送信された要求を拒否します。
ボディーの形状が重要な理由
あなたのcodeは通常、すべてのフィールドに気にしないです。 それがイベントのタイプ、イベントの識別子、ビジネスオブジェクトの中に気にします。 data. それがなぜ、良いハンドラーは必要なものだけをパースし、残りはトラブルシューティングのためにログします。
OpenAPIはこのパターンを直接モデル化しています。 OpenAPI 3.1.0は、トップレベルの webhooks オブジェクトを追加しました。 それぞれのWebhookは、Path Itemと同様に記述されますが、プロバイダーによってトリガーされます。 Canonical例では、Webhookに newPet オペレーション、JSON要求ボディ、受信を示す post レスポンスが含まれます。 200 Webhook OpenAPI webhook example.
独自の受信者またはプロバイダ契約をドキュメント化している場合、具体的な例は抽象的なスキーマの文章よりも効果的です。私は、次のような参照を使用することを好みます。 SheetMergyのAPIドキュメント例 具体的な例、フィールドの説明、そして期待されるレスポンスがどのように組み合わさっているかが明確になるためです。
Webhookはトランスポート層では単純です。多くの失敗は、ヘッダ、ボディのエンコード、署名ルールに関する誤った仮定によるものです。
Webhook署名の安全な検証方法
署名されたWebhookは1つの質問に答えます: このペイロードは誰かが共有シークレットを知っている人から来ていますか?
これは、リクエストが最近か、またはすでに処理したことがあるかを尋ねることとは異なります。署名検証は最初のゲートではなく、最後のゲートではありません。

通常のHMACフローは次のようになります:
プロバイダのヘッダから署名を読み取ります。
- signature
- スタイルガイドを読みましょう。 リクエスト本体 正確に受信したままです。
- セキュアな設定からウェブホックシークレットを読み込んでください。
- 同様のアルゴリズムを使用して、予想されるHMACを再計算する。
- 受信の署名と計算された署名をタイミング安全に比較してください。
- リクエストが一致しない場合、拒否します。
フレームワークがJSONを先に解釈したり、スペースの配置を変えたり、エンコードの詳細を変更したりする場合、計算された署名はプロバイダーのものと一致しません。
What to watch for in real code
これらの最もよく見られる間違いです。
- JSONをパースしたハッシュ. しないでください。
JSON.stringify(req.body)そして、期待どおりに一致することを確認します。 - 通常の文字列の等価性を使用します。.タイミング安全な比較を使用します。
- シークレットをハードコードする.環境変数またはシークレットマネージャーに保管してください。
- ヘッダーのみに依存する.署名ヘッダーの意味は、検証しない限りありません。
サービス間でシークレットの取り扱いを強化するチーム向けに、Capgoの APIのアプリストアの準拠性のためのシークレットの鍵のセキュリティのガイド は関連しています。シークレットのローテーション、スコープ付きのアクセス、ログ内の漏洩を回避することは、ウェブホック受信者にとっても重要です。
一般的な検証の例
const crypto = require('crypto');
function verifySignature(rawBody, receivedSignature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
const a = Buffer.from(receivedSignature, 'utf8');
const b = Buffer.from(expected, 'utf8');
if (a.length !== b.length) return false;
return crypto.timingSafeEqual(a, b);
}
これは意図的に汎用的なものです。実際のプロバイダーは、署名をプレフィックスする、タイムスタンプを署名内容に組み込む、またはハッシュを別の方法でエンコードすることがよくあります。ルールは同じです。プロバイダーの正確な署名形式に従い、常に元のペイロードと検証します。
再送信攻撃を防ぐ
署名されたWebhookは、数時間後に到着し、ハンドラーがそれを新しいものとして扱う場合でも危険です。チームはそれが起こることを期待しています。プロキシはトラフィックを記録し、リクエストペイロードは間違った場所に漏れ出します、またはプロバイダーはネットワーク障害後にリトライし、エンドポイントは同じイベントを2回処理します。

署名検証は、共有シークレットで作成したペイロードの送信者が誰であるかを確認する質問に答えます。再送信保護は、現在は受け入れるべきかどうかという別の質問に答えます。生産的な受信者には両方が必要です。
実際に重要な最小限のチェック
実用的な再送信防御は、署名されたタイムスタンプから始まります。プロバイダーはヘッダーや署名されたメッセージにタイムスタンプを含め、受信者は小さな許容範囲外のリクエストを拒否します。
そのフローは次のようになります。
- プロバイダーが定義した場所からタイムスタンプを読み取りヘッダーの名前を推測しないでください。
- 整数またはRFC形式の日付として解釈しますプロバイダーの仕様に基づいて
- サーバーの時刻と比較します.
- 過去のリクエストや将来のリクエストを拒否する.
- 署名スキームの一部としてタイムスタンプを検証する 提供元が対応している場合
最後の点は重要です。タイムスタンプが署名にカバーされていない場合、攻撃者はオリジナルのボディに新しいタイムスタンプを挿入してリプレイできます。私は常に提供元の正確な署名形式を確認する前にタイムスタンプロジックを信頼します。
許容範囲の選択
5分は一般的なデフォルトです。攻撃ウィンドウを縮小する程度に短く、時刻のズレや通常のネットワーク遅延を乗り越える程度に長いです。
ここではトレードオフが発生します。30秒のウィンドウはより安全に見えますが、実際のシステムではリトライ、キューング、地域的な遅延などが絡むと頻繁に破綻します。一方、30分のウィンドウは操作が容易ですが、署名されたリクエストが漏洩した場合に攻撃者に与える時間が長くなります。最初は数分、サーバーをNTPと同期し、提供元の配信パターンが対応する場合にのみ、許容範囲を絞り込んでください。
リプレイ防御は単にタイムスタンプの検証ではありません
タイムスタンプ検証は古いリクエストをブロックしますが、有効なウィンドウ内で複製処理を防止しません。同一の署名されたイベントがウィンドウ内で 2 回送信された場合、Application はそれを認識する必要があります。
2 つのレイヤーを使用してください:
- イベント ID または配信 ID を短期間のストレージ (Redis) にトラッキングする in a short-lived store such as Redis.
- ハンドラを idempotent とみなす したがって、繰り返し配信は、重複した注文、メール、請求アクションを生成しないようにする。
- 古い要求を拒否する 理由コードとともに、しかし、秘密情報やフルセンスティブペイロードをログに記録しないようにする。
- 検証とキューの重い作業を別の場所で行った後、迅速なレスポンスを返す。 すでに有効期限切れのウィンドウや取り消しについて考えるチームは、このパターンを認識するだろう。
Capgoのガイド Capacitorアプリケーションにおけるトークン取り消しパターン トークンまたは要求が一度有効だったら、ずっと信頼されるべきではない。
署名された古いものはまだ安全ではない。
Node.jsでWebhook受信者を作る
Node.jsとExpressは、真剣な受信者をオンラインにしたい場合、最速の方法であるが、ある陷阱が他のものよりも重要である。Expressがオブジェクトに変換する前に、raw bodyにアクセスする必要がある。

実用的なExpressの例
const express = require('express');
const crypto = require('crypto');
const app = express();
const PORT = process.env.PORT || 3000;
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
// Capture raw body for signature verification
app.use(
express.json({
verify: (req, res, buf) => {
req.rawBody = buf;
},
})
);
function safeEqual(a, b) {
const aBuf = Buffer.from(a, 'utf8');
const bBuf = Buffer.from(b, 'utf8');
if (aBuf.length !== bBuf.length) return false;
return crypto.timingSafeEqual(aBuf, bBuf);
}
function verifySignature(rawBody, secret, receivedSignature) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
return safeEqual(expected, receivedSignature);
}
function isFresh(timestampHeader, toleranceSeconds = 300) {
const timestamp = Number(timestampHeader);
if (!Number.isFinite(timestamp)) return false;
const now = Math.floor(Date.now() / 1000);
return Math.abs(now - timestamp) <= toleranceSeconds;
}
app.post('/webhooks/example', async (req, res) => {
const signature = req.get('x-webhook-signature');
const timestamp = req.get('x-webhook-timestamp');
if (!WEBHOOK_SECRET) {
return res.status(500).send('Webhook secret is not configured');
}
if (!signature || !timestamp) {
return res.status(400).send('Missing required security headers');
}
if (!isFresh(timestamp)) {
return res.status(401).send('Stale webhook');
}
const valid = verifySignature(req.rawBody, WEBHOOK_SECRET, signature);
if (!valid) {
return res.status(401).send('Invalid signature');
}
// Acknowledge quickly
res.status(200).send('OK');
// Process after acknowledgement
try {
const event = req.body;
console.log('Accepted event:', event.event, event.id);
// enqueueJob(event)
} catch (err) {
console.error('Post-ack processing failed:', err);
}
});
app.listen(PORT, () => {
console.log(`Webhook receiver listening on ${PORT}`);
});
この構造が持続する理由
ここではいくつかの選択肢が意図的に行われました:
- Raw Body Captureはミドルウェアで発生します。 これにより、ハッシュのために元のバイトを保存できます。
- タイムスタンプはビジネスロジックの前にチェックされます。 古いトラフィックに対しての作業をする必要はありません。
- ルートは
200すぐに 長時間の作業はキューまたはバックグラウンドタスクに属するものです。 - Post-ack処理は分離されています. 連続するロジックが失敗しても、受信パスは小さくなる。
多くのウェブホック実装の弱点はシークレットです。ソースに保管しないでください、テスト用のフィクスチャに貼り付けてはいけません、ログに反映してはいけません。CIハンドリングとローテーションに関するより広いプロセスが必要な場合は、CapgoのCI/CDパイプラインにおけるシークレットの管理に関するガイドが十分にカバーしています。 シークレットの管理 CI/CDパイプラインにおけるシークレットの管理
実際のシステムでは何を変更するか
実際のプロバイダ統合では、永続ストレージにイベントIDの重複排除、リクエストIDを含む構造化ログ、確認パスの後ろにキューを追加するなど、イベントIDの重複排除、構造化ログ、確認パスの後ろにキューを追加するなど、実際のシステムでは何を変更するか
PythonでWebhook受信者を作成する
Flaskは、クリーンなWebhook例用途に適したフレームワークです。リクエストハンドリングは明確で、HMACのためにPythonの標準ライブラリが必要なので、PythonはNodeと同じように機能します。
メインのポイントは、Nodeと同じです。ハッシュ値を検証するには、パース済みのJSON辞書ではなく、rawリクエストバイトを使用する必要があります。
署名とタイムスタンプのチェックを含むFlask例
Flask固有の詳細
import os
import time
import hmac
import hashlib
from flask import Flask, request, jsonify
app = Flask(__name__)
WEBHOOK_SECRET = os.environ.get("WEBHOOK_SECRET", "")
def is_fresh(timestamp_header, tolerance_seconds=300):
try:
timestamp = int(timestamp_header)
except (TypeError, ValueError):
return False
now = int(time.time())
return abs(now - timestamp) <= tolerance_seconds
def verify_signature(raw_body, secret, received_signature):
expected = hmac.new(
secret.encode("utf-8"),
raw_body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, received_signature)
@app.route("/webhooks/example", methods=["POST"])
def webhook():
if not WEBHOOK_SECRET:
return "Webhook secret is not configured", 500
signature = request.headers.get("X-Webhook-Signature")
timestamp = request.headers.get("X-Webhook-Timestamp")
if not signature or not timestamp:
return "Missing required security headers", 400
if not is_fresh(timestamp):
return "Stale webhook", 401
raw_body = request.get_data()
if not verify_signature(raw_body, WEBHOOK_SECRET, signature):
return "Invalid signature", 401
payload = request.get_json(silent=True) or {}
# Acknowledge receipt
response = jsonify({"status": "ok"})
# In production, queue payload here instead of heavy sync work
print("Accepted event:", payload.get("event"), payload.get("id"))
return response, 200
if __name__ == "__main__":
app.run(port=5000, debug=True)
Flask固有の詳細
request.get_data() ここでは、呼び出しに必要なキーが何であるかを説明します。 これは、ボディのrawバイトを提供します。 すぐにでもボディの内容に直接アクセスすると、署名の不一致が混乱を招くようになります。 request.json署名の不一致が混乱を招くようになるのはすでにその線を越えていることになります。
実装に関するいくつかの注意点があります。
- Use
hmac.compare_digestを使用するのではなく、平等の代わりに使用してください。 - ヘッダが欠如している場合、クライアント側のエラーとして扱い、早期に拒否します。 and reject early.
- を使用してJSONのパースを実行します。エラーのハンドリングを制御したい場合は、Flaskが例外を上げるのではなく、代わりに使用してください。
silent=Trueルートを薄く保つこと。 、パイロードが何か高価なものをトリガーした場合、ワークをキューイングしてください。 - __CAPGO_KEEP_0____CAPGO_KEEP_0__
セキュリティチェックを緩和してサインャッチの不一致をデバッグするのではなく、ハッシュしたバイトを正確に印刷し、プロバイダーが期待するフォーマットを正確に印刷してデバッグする。
チームが通常どのようにブロックされるか
通常の失敗パスは、手作りのJSONボディでテストし、実際のプロバイダーに切り替えてサインが一致しないことを発見することです。その場合、通常は次の3つのことが原因です: プロバイダーはタイムスタンプ付きのエンベロープを署名する、署名が想定どおりにエンコードされていない、またはミドルウェアが検証前にボディを変更した。
When that happens, stop changing the crypto code at random. Capture the raw headers and raw body, reproduce the hash in a tiny isolated script, and only then put it back into the Flask route.
Webhook受信者を作成する
GoはWebhook受信者に適した選択肢です。標準ライブラリは十分で、フレームワークを必要とせずに小さく信頼できるハンドラーを作成できます。また、codeは簡単に正直に保つことができます。
ボディハンドリングに注意する必要があるのは一つだけです。 r.Body ストリームです。1回だけ読み、取得したバイトをハッシュし、そして同じバイトからアンマーシャルする。
標準ライブラリの例
package main
import (
"crypto/hmac"
"crypto/sha256"
"crypto/subtle"
"encoding/hex"
"encoding/json"
"io"
"log"
"net/http"
"os"
"strconv"
"time"
)
type WebhookPayload struct {
Event string `json:"event"`
ID string `json:"id"`
Data json.RawMessage `json:"data"`
}
func isFresh(timestampHeader string, toleranceSeconds int64) bool {
ts, err := strconv.ParseInt(timestampHeader, 10, 64)
if err != nil {
return false
}
now := time.Now().Unix()
diff := now - ts
if diff < 0 {
diff = -diff
}
return diff <= toleranceSeconds
}
func verifySignature(rawBody []byte, secret string, received string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(rawBody)
expected := hex.EncodeToString(mac.Sum(nil))
if len(expected) != len(received) {
return false
}
return subtle.ConstantTimeCompare([]byte(expected), []byte(received)) == 1
}
func webhookHandler(secret string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
signature := r.Header.Get("X-Webhook-Signature")
timestamp := r.Header.Get("X-Webhook-Timestamp")
if signature == "" || timestamp == "" {
http.Error(w, "missing required security headers", http.StatusBadRequest)
return
}
if !isFresh(timestamp, 300) {
http.Error(w, "stale webhook", http.StatusUnauthorized)
return
}
rawBody, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "failed to read body", http.StatusBadRequest)
return
}
if !verifySignature(rawBody, secret, signature) {
http.Error(w, "invalid signature", http.StatusUnauthorized)
return
}
var payload WebhookPayload
if err := json.Unmarshal(rawBody, &payload); err != nil {
http.Error(w, "invalid json", http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
log.Printf("accepted event=%s id=%s", payload.Event, payload.ID)
}
}
func main() {
secret := os.Getenv("WEBHOOK_SECRET")
if secret == "" {
log.Fatal("WEBHOOK_SECRET is not set")
}
http.HandleFunc("/webhooks/example", webhookHandler(secret))
log.Println("listening on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Goがここで堅実な理由
いくつかの利点が目立っています:
- ハンドラーは明確です. No hidden middleware magic.
- エッジの部分でタイプが役に立つ。. ヘッダーパース、タイムスタンプの変換、JSONのデコードはすべて明確に失敗します。
- 標準の暗号化パッケージは十分です。. 基本的なHMAC検証のための追加の依存関係は必要ありません。
運用に関する注記
ウェブフックの量が増えると、Goの並行性モデルはHTTPエントリポイントを変更することなくバックグラウンドワークを拡散させるためのスペースを与えます。 その場合でも、受信者は狭くしてください。 受け取る、検証する、承認する、そして受信者に引き渡す。
Goのウェブフックハンドラーの中で最も強力なものは、つり合いがとれたものです。 それらは、運輸検証とビジネスロジックを混ぜ合わせないし、レスポンスが戻る前にデータベースの重い作業をしない。
基本的なデバッグテクニック
ウェブフックのバグは、通常、スタックトレースではなくサポートメッセージとして表示されます。 プロバイダーはイベントを送信したと言い、エンドポイントはアプリに何も到達しない、または最初から見てみれば要求が有効に見えるもので署名検証が失敗したと言います。 その時点で、デバッグは、正確にHTTP交換を再構築し、バイトごとに証明することです。 どこで破棄されたかを証明することです。

実用的なデバッグツールキット
Wire形式から始めましょう。
署名検証が失敗した場合、元の受信元のヘッダーと共に、元の受信元の本体をそのままキャプチャします。実際には、よくあるのは、フレームワークがJSONをパースした後でハッシュしたり、プロキシがエンコードを変更したり、テスト再生が元のタイムスタンプヘッダーをミスしたりすることです。パースされたオブジェクトをログするだけでは十分ではありません。元のバイトと検証入力が必要です。
これらのツールは、問題を迅速に分離するのに役立ちます:
- Raw Request Capture. 検証中はヘッダー、コンテンツタイプ、コンテンツ長、未変更本体をログします。
- Request Inspection Endpoints. 送信元が送信したものを確認するサービスがあります。
webhook.siteLocal Tunneling - と同様のツールは、プロバイダーをループさせながら、ローカル受信元でテストできます。.
ngrokManual Replay - . リクエストを再構築して. Rebuild the request with
curlまたは、Postmanを使用して同じボディとヘッダーで確認することができます。 これは、codeまたはプロバイダーのペイロードが問題であるかどうかを確認する最速の方法です。 - プロバイダーの配信ログ。送信元のダッシュボードには、レスポンスコード、リトライ履歴、リクエスト識別子が含まれます。これをログと照合できます。
パターンは重要です。外側から内側に向かって作業してください。まず、プロバイダーが期待どおりに送信したかどうかを確認してください。次に、サーバーが同じバイトを受信したかどうかを確認してください。最後に、codeが同じバイトを同じシークレットとタイムスタンプのルールでハッシュしたかどうかを確認してください。
実際に役に立つログを記録する
良いウェブホックログは、1つの検索で3つの質問に答えるべきです。
| 質問 | 役に立つログフィールド |
|---|---|
| リクエストが到着したか? | ルート、メソッド、受信日時 |
| なぜ却下された? | 欠落したヘッダー、古いタイムスタンプ、署名失敗 |
| 後で関連付けられることはできますか? | event_id, provider_request_id |
実際のシステムでは、4番目のフィールドが役に立ちます。ローカル request_id 生成された受信者のため、リクエストをアプリ、キュー、ワーカーログを通して追跡できます。
保存するものを選択してください。秘密をログに記録しないでください。顧客データ、アクセストークン、請求情報を含むフルプロダクションペイロードをダンプしないでください。代わりに、メタデータと短いボディハッシュをログに記録してください。リトライを比較し、2つの配信が同一であるかどうかを検証するには、それでも十分です。
元の入力とともに失敗を再現する
基本的なチュートリアルではこの部分を省略します。失敗したリクエストを正確に再現できない場合は、推測していることになります。
失敗したウェブホックを保存してください:
- 元のボディバイト
- すべての署名関連ヘッダー
- リクエストタイムスタンプ
- コンテンツタイプ
- プロバイダーのリクエストID
次に、ステージングエンドポイントに再生してみましょう。再生が成功したら、トランジットで何が変化したかを比較してみましょう。一般的な原因としては、リクエストボディを正規化するミドルウェア、文字エンコードの不一致、ヘッダを削除または書き換えるロードバランサなどが挙げられます。私は、チームが実際のリクエストボディではなく、プリティプリントされたダッシュボードビューからペイロードをコピーしたことによる失敗も見てきました。ホワイトスペースの差異だけでHMAC検証を破ることができました。
より広範なリリースとモバイルトランスポートのトラブルシューティングのために、同じデバッグの規範がCapgoのガイドに現れます。 Capacitorのツールのガイド.異なるトランスポートでも同じ教訓が現れます。実際のリクエストパスをキャプチャして、適用するアプリケーションcodeを変更する前に。
署名検証が失敗した場合、rawバイト、検証に使用されたexactヘッダ、タイムスタンプ値を確認する前に、暗号化codeを触ることなく。
生産用Webhookのチェックリスト
ステージングではWebhookハンドラーは通常問題なく見えますが、最初のリトライの嵐、不正なペイロード、2時ごろの署名不一致などで問題が発生するまで。生産用のバーは高くなります。受信者は偽のリクエストを拒否し、有効なリトライを受け入れ、操作者が失敗をデバッグするのに十分な信号を与えながら、敏感なデータを公開しないようにします。
セキュリティと正しさのチェック
- すべてのリクエスト署名を検証する.エンドポイントURLは漏洩します。テストURLはチャットで共有されます。署名検証は、共有シークレットを知っている送信者がいることを示す制御です。
- 古いリクエストを拒否する. 旧のペイロードに署名された有効な署名は、再送信されることができます。 これを防ぐには、プロバイダーのリトライモデルに合わせたタイムスタンプの許容範囲を設定してください。
- JSON をパースしたものではなく、raw body をハッシュしてください。. ミドルウェアは、キーを並べ替える、ホワイトスペースを正規化する、またはエンコードを変更することができます。 したがって、到着した exact バイトに検証を実行する必要があります。
- code から署名シークレットを保持してください。. 環境変数は基本的なものです。 ただし、クレデンシャルを定期的にローテートする場合、または複数の環境で実行する場合、シークレット マネージャーはより適切な選択肢です。
- 認証エラーの場合、失敗することを保証してください。.署名ヘッダが欠落している、不正な、または予想外のスキームを使用している場合、リクエストを拒否し、理由をログに記録してください。
信頼性のチェック
- 速いことを認めます。. プロバイダは、通常、2xx の場合に成功とみなします。 したがって、リクエストを検証し、必要なものを保存し、後続の作業をキューまたはワーカーに移行してください。
- ハンドラーを idempotent にします。. 同じイベントは複数回到着する可能性があります。 したがって、イベント ID、配信 ID、または安定したプロバイダーの識別子を使用して、側面効果を分離してください。
- 予測可能なエラー コードを返します。. 不正入力の場合を使用します。
400. または401、403、5xx、 - 、、
- 、. Accept only the fields and event types you support. Loose parsing feels convenient at first and becomes expensive during provider API changes.
、
、
標準を使用してください:
- 受け取り、検証、処理を別々の結果として追跡する.
- リクエストID、イベントID、署名ステータス、タイムスタンプのずれをログする.
- キューの遅延、ハンドラの遅延、リトライの量を測定する.
- ステージングまたは再送信ワークフローの安全な再送信パスを維持する.
- パターン変更にアラートする例として、署名失敗のスパイクや重複配信など
Capgo は、より広範な運用ポイントの例として便利です。 これには、リリース配信と観察性の更新ワークフローに伴うツールが含まれ、エコシステムの部分もウェブホック関連フローに触れています。 これは実践的な教訓です。 配信システムには、受信から完了までの可視性が必要です。
チームが上記のチェックをカバーしている場合、ウェブホック受信者は通常、生産用に十分な状態になります。 どのアイテムが欠けている場合、その欠陥はデモ中に発生するのではなく、インシデント中に表面化します。
ウェブホックに関するよくある質問
code のステータスを何を返すべきですか?
ステータスを返す 200 ウェブホックを受け入れると、検証が失敗した場合、エラーを返します。エラーは、失敗の理由に応じて、次のいずれかになります。 400 入力が不正である場合 401 認証データが不正である場合
ウェブホックを処理するには同步で行うべきか?
通常は、はい。ウェブホックを検証し、承認し、実際の作業をキューまたはバックグラウンドワーカーに送信します。これにより、配信パスが速くなり、遅い下流処理によって引き起こされる重複リトライが減ります。
リトライをどのように扱うべきか?
リトライが発生する可能性があるため、ハンドラーに idempotency を組み込んでください。同じイベントを受信しても、副作用が重複しないようにします。イベント ID またはプロバイダーの配信 ID は、通常の anchor です。
イベントが順序が乱れた場合どうする?
順序を許容できるハンドラーを設計することができます。ビジネス プロセスがシーケンスを必要とする場合、古いトランジションを検出するために十分な状態を保存するのではなく、配信順序がイベント順序を反映していると仮定するのではなく、順序を許容できるハンドラーを設計することができます。
ウェブホックのバージョン変更にどう対処する?
ハンドラーのロジックを意図的にバージョン化してください。プロバイダーの特定のパースを分離し、コードベースにペイロードの仮定を散らすのではなく、実際のキャプチャされたサンプルとテストを追加して、新しい形式に対応する前にサポートをロールアウトする前に
あなたのチームが Capacitor または Electron アプリを配布する場合、 Capgo 配布するWeb更新を署名付きで制御して、ロールアウトの動作を観察し、インシデントから回復することができます。アプリストアのレビューを待たずに配布することができます。これは、Solid Webhook の設計の背後にあるエンジニアリングのインスタントと同じです: 入力値を検証し、リリースパスの観察性を維持し、回復を高速化します。