サービスが別の場所で何かが発生したときに反応する必要がある場合があります。支払いが確定した。顧客レコードが変更された。リポジトリがプッシュされた。APIを毎分1分ごとにポーリングし、繰り返し「何か新しいものがあるか?」と尋ねるのではなく、ソースシステムがイベントが発生したときに呼び出すようにすることができます。
ほとんどのWeb Hook例の記事はここで止まります。ルートを示し、JSONボディを印刷し、完了と呼びます。 200しかし、誰かが偽造された要求を送信したり、有効な要求を再生したり、またはハンドラーが破綻したりすると、そのバージョンは機能しません。
このガイドでは、実際に使用するパスを示します。例は小さくてコピーできるように設計されていますが、重要な部分を含んでいます:
未加工のボディの処理、HMAC検証、タイムスタンプのチェック、高速の承認、実用的なデバッグ
- 目次
- Webhook HTTP要求の解剖学
- Webhook署名の安全な検証方法
- リプレイ攻撃を防ぐ
- Node.jsでWebhook受信者を作る
- PythonでWebhook受信者を作る
- GoでWebhook受信者を作る
- 基本的なデバッグテクニック
- 生産用Webhookのチェックリスト
- Webhookに関するよくある質問
ウェブフックとは何か、それを使用する理由
請求先のサービスプロバイダーは、02:13に請求書を支払ったとマークします。アプリが02:14にそれを知った場合、顧客はすぐにアクセスできます。アプリが次のポーリングサイクルでそれを知った場合、顧客は待ちます、サポートチームはチケットを受け取り、ログは無駄なノイズで埋め尽くされます。ウェブフックは、このタイミングの問題を解決するために、イベントが発生したときにHTTPコールバックを送信します。
実際の場合、ウェブフックは、イベント駆動型のPOSTから1つのシステムから別のシステムへ送信されるものです。プロバイダーは、変更を検出します。たとえば、 invoice.paid, order.created、 push、
This pattern shows up in real systems because it maps cleanly to business events. Stripe posts payment outcomes. GitHub posts repository activity. Shopify posts order updates. The shape is simple, but production behavior is not. A webhook that updates money, access, or inventory deserves the same care as any public API endpoint, especially once retries, duplicates, and untrusted traffic enter the picture.
、
ウェブフックは、プロバイダーが変更を検出し、イベントデータをあなたが制御するURLに送信するイベント駆動型のPOSTです。これにより、ポーリングが生み出す「何か新しいものがあるの?」のループがなくなるので、多くの無駄なリクエストが削減されます。
- このパターンは、実際のシステムで見られるのはなぜですか?それは、ビジネスイベントにうまくマッピングするからです。Stripeは支払い結果を投稿します。__CAPGO_KEEP_0__はリポジトリのアクティビティを投稿します。Shopifyは注文の更新を投稿します。形状は単純ですが、生産的な動作はそうではありません。ウェブフックが金銭、アクセス、またはインベントリを更新する場合、リトライ、重複、信頼できないトラフィックが含まれる場合に、ウェブフックが公の__CAPGO_KEEP_1__エンドポイントと同じような注意を払う必要があります。. イベントを検出するサービス。
- 宛先エンドポイント. HTTPルートを受け取る
- イベント. 名前付きの変更が発生したときの例
invoice.paidペイロードpush. - . __CAPGO_KEEP_0__ が必要な詳細が含まれたリクエストボディ。. The request body with the details your code needs.
実用的なルール:
イベント駆動の更新用にウェブフックを使用し、スケジュールされた読み取り、バックフィル、または出発イベントを提供しないプロバイダー用にポーリングを使用します。 より広範な機能を開発しているチーム
targetLanguage ワークフロー自動化とデータ統合ウェブフックは通常、システムを無駄なリクエストトラフィックなしで同期に持つイベント層になります。統合重視のサービスを扱っている場合、Capgo’s バックエンド開発記事 は、リトライ、キュー、観測性、エラー処理などのコア問題が表面化するため、有用なコンテキストです。
Whatがうまくいき、Whatが失敗する
生産環境でうまくいくセットアップは、通常、面白くない設計です。必要なイベントのみにサブスクライブし、エンドポイントをプロバイダーまたはイベントファミリーでスコープする。イベントIDを保存して、重複配信が副作用をもたらさないようにします。リクエストが検証されキューに登録されたら、速い2xxレスポンスを返し、次に遅いビジネスロジックを非同期で実行します。
脆弱なバージョンは容易に認識できます。1つの汎用エンドポイントがすべてを処理します。署名チェックは早期テスト中にはスキップされ、戻ってこないのです。ハンドラーは、イベントが有効か古いものかを確認することなく、重要なテーブルに直接書き込むことになります。その結果はデモでうまくいきますが、リトライストーム、プロバイダー障害、攻撃者が古いリクエストを再生した場合に失敗します。
このガイドの残りの部分は、そのトレードオフによって定義されます。ウェブフック受信者の「hello world」バージョンは小さく、生産環境用のバージョンは署名検証、再生防御、重複処理、デバッグ用のフックを最初から組み込んだものになります。
Webhook HTTP リクエストの構造
codeを書く前に、HTTPのリクエストをフレームワークのオブジェクトとしてではなく、raw HTTPとして見ることが役に立つ。 通常のウェブホークは、ヘッダーとJSONボディを持つパブリックエンドポイントへのHTTP POSTリクエストだけである。
単純な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デバッグに役立つが、信頼できるものではありません。
- Signatureヘッダープロバイダーの正当性確認を含む。
- タイムスタンプヘッダー. 使用されていないまたは再送信された要求を拒否するために使用されます。
ボディーの形状はなぜ重要か
あなたの code は、通常、すべてのフィールドに気にしないです。イベントのタイプ、イベントの識別子、ビジネスオブジェクトの中身だけに気にします。 dataなぜなら、良いハンドラーは、必要なものだけをパースし、トラブルシューティングのために残りのものをログに残すからです。
OpenAPIは、このパターンを直接モデル化しています。OpenAPI 3.1.0は、トップレベルのオブジェクトを追加して、各ウェブフックがパスアイテムと同様に記述され、プロバイダーによってトリガーされるようにしました。canonical例では、ウェブフックの webhooks オペレーション、JSON要求ボディ、受信を示す newPet レスポンスが含まれています。 post OpenAPIウェブフックの例 200 あなた自身の受信者またはプロバイダーの契約をドキュメント化している場合、強力な例は抽象的なスキーマの文章よりも役立ちます。私は、 SheetMergyの __CAPGO_KEEP_0__ ドキュメントの例を参照するのを好きです.
. SheetMergy’s API doc examples __CAPGO_KEEP_0__
リクエスト例、フィールドの説明、そして期待されるレスポンスがどのように組み合わさっているかは明らかになるからです。
Webhookの署名検証
Webhookはトランスポート層では単純です。多くの失敗はヘッダ、ボディのエンコード、署名ルールについての誤った仮定によるものです。
Webhook署名の検証方法

これは、リクエストが最近であるか、すでに処理したものであるかを尋ねることとは異なります。署名検証は最初のゲートであり、最後のゲートではありません。
Webhook署名の検証フロー
- 通常のHMACフローは次のようになります:
- プロバイダーのヘッダから署名を読み取ります。 raw request body リクエストボディの読み取り: そのまま受信したものを読み取ります。
- セキュアな設定からウェブホックシークレットを読み込んでください。
- 同一のアルゴリズムを使用して、予想されるHMACを再計算してください。
- タイミング安全の比較を使用して、受信された署名と計算された署名を比較してください。
- リクエストを拒否するには、指定された値と一致する必要があります。
そのraw-bodyステップでは、ほとんどの場合、良好な実装が失敗する。フレームワークが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);
}
. 実際のプロバイダーは、署名をプレフィックスする、タイムスタンプを署名内容に組み込む、またはハッシュを別の方法でエンコードすることがある。ルールは同じです。プロバイダーの指定された署名形式に従い、常に元のペイロードと検証する。
リプレイ攻撃を防ぐ
. 署名されたウェブホックは、数時間後に到着し、ハンドラーがそれを新しいものとして処理した場合、危険である。チームはそのようなことが起こることを想定していない。プロキシはトラフィックをログする、リクエストペイロードが間違った場所に漏洩する、またはプロバイダーがネットワーク障害後にリトライし、同じイベントをエンドポイントが2回処理するなど、多くの場合に起こる。

__CAPGO_KEEP_0__の署名検証は、共有シークレットでこのペイロードを作成したのは送信者だったかどうかという1つの質問に答えます。再送信保護は、まだこのリクエストを受け入れるべきかどうかという別の質問に答える必要があります。生産的な受信者には両方が必要です。
__CAPGO_KEEP_0__の最小限のチェックは実際に重要です
実用的な再送信防御は、署名されたタイムスタンプから始まります。プロバイダーはヘッダーや署名されたメッセージにタイムスタンプを含め、受信者はリクエストが指定された小さな許容範囲外にある場合に拒否します。
__CAPGO_KEEP_0__の流れは次のようになります。
- プロバイダーが指定した場所からタイムスタンプを読み取りますヘッダーの名前を推測しないでください。
- RFC形式の日付としてまたは整数として解釈します。プロバイダーの仕様に基づいて。
- サーバーの時刻と比較します。.
- 過去のリクエストや過去のリクエストを拒否します。.
- 署名スキームの一部としてタイムスタンプを検証します。 プロバイダーがサポートする場合。
その最後の点は重要です。タイムスタンプが署名によってカバーされていない場合、攻撃者はタイムスタンプを新しいものに置き換え、元のボディを再生できます。私は常に提供者の正確な署名形式を確認する前にタイムスタンプロジックを信頼しません。
許容範囲の選択肢
5分は一般的なデフォルトです。攻撃ウィンドウを縮小する程度に短く、時刻のズレや通常のネットワーク遅延を乗り切る程度に長いです。
ここではトレードオフがあります。30秒のウィンドウはより安全に見えますが、実際のシステムでは、リトライ、キューング、地域的遅延などが絡むと、より頻繁に破綻します。30分のウィンドウは、操作が容易ですが、署名されたリクエストが漏洩した場合に攻撃者に与える時間が長くなります。最初は数分、サーバーをNTPと同期し、提供者の配信パターンがそれをサポートする場合にのみ、厳密に絞り込んでください。
リプレイ防御は単にタイムスタンプのチェックではありません
タイムスタンプの検証は古いリクエストをブロックしますが、有効なウィンドウ内で同じ署名されたイベントが複数回配信された場合、処理を繰り返すことはありません。
2層目の使用を検討してください
- イベントIDや配信IDを短期間のストレージであるRedisに追跡してください ハンドラーをidempotentとして扱ってください
- 繰り返し配信が重複した注文、メール、請求アクションを生成しないようにしてください。 古いリクエストを拒否した場合のログを取ってください
- 許容範囲の選択肢 理由コードとともに、秘密情報や完全な機密データをログに記録しない。
- 迅速な応答を返します。 検証と大量の作業を別の場所で行う後で。
Teams that already think about expiry windows and revocation will recognize the pattern. Capgo’s guide to Capacitorのガイド __CAPGO_KEEP_0__アプリケーションにおけるトークン削除パターン
有効期限切れの資格情報や要求は、元々有効であった場合でも、いつまでも信頼されるべきではない。
署名されたものであっても、古いものはまだ安全ではない。
Node.jsでWebhook受信者を作成する

木の机の上のノートブックでNode.js受信者__CAPGO_KEEP_0__を表示するVS__CAPGO_KEEP_1__エディター環境。
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}`);
});
生産的なExpressの例: なぜこの構造が持続するのか
いくつかの選択肢は意図的に行われました:
- Raw body のキャプチャはミドルウェアで発生します。 そのため、ハッシュのために元のバイトを保存します。
- タイムスタンプはビジネスロジックの前にチェックされます。古いトラフィックのために仕事をする必要はありません。
- ルートは
200すぐに返却されます。長時間の作業はキューまたはバックグラウンドタスクに属するものです。 - Post-ack 処理は分離されます。下流ロジックが失敗しても、受信パスは小さくなります。
シークレットは多くのウェブホック実装の弱点です。ソースに保管しないでください、テストフィクスチャに貼り付けてはいけません、ログに反映してはいけません。必要な場合は、CapgoのCI/CDパイプラインにおけるシークレットの管理のための より広範なプロセスについてのガイド __CAPGO_KEEP_0__
動作の側面を十分にカバーしています。
動作の流れを簡単に理解するには、短いガイドが役立ちます。
実際のサービスに統合する場合に変更したい点
実際のサービス統合の場合、イベントIDの重複削除、リクエストID付きの構造化ログ、承認パスの後ろにキューを追加するなどを行うと良いでしょう。また、複数のプロバイダーが異なる署名形式を使用する場合、単一の汎用エンドポイントを避けることが良いでしょう。分離されたハンドラーは、より論理的に理解し、より簡単に破壊することができます。
PythonでWebhook受信者を作成する
Flaskは、HMACのための標準ライブラリが用意されているため、クリーンなWebhookの例として適しています。
主なことはNodeと同じです。確認する対象は、パースされたJSON辞書ではなく、raw requestバイトです。
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() Flask固有の詳細 request.jsonここで、raw bodyのバイトを取得します。直接
にジャンプすると、署名の不一致が混乱を招くようになります。
- 使用
hmac.compare_digest__CAPGO_KEEP_0__の代わりに平等を使用。 - ヘッダーが欠落している場合、クライアントの失敗として扱う と早期に拒否する
- JSONのパースに使用
silent=True__CAPGO_KEEP_0__でエラー処理を制御したい場合は、Flaskが例外を上げるのではなく ルートを薄く保つ - ペイロードがコストのかかるものをトリガーした場合、作業をキューイングするサインチェックを緩和してデバッグしないでください。デバッグするには、ハッシュしたバイトを正確に印刷し、プロバイダーが期待するフォーマットを正確に印刷してください。
チームが通常どのように困るか
通常の失敗のパスは、手作りのJSONボディでテストし、実際のプロバイダーに切り替えてサインが一致しなくなった場合です。その場合、通常は次の3つのことが原因です: プロバイダーがタイムスタンプ付きのエンベロープを署名した、署名が想定していたものと異なる形式でエンコードされた、またはミドルウェアが検証前にボディを変更した。
Capgoを使用
その時点で、ランダムに暗号化された code を変更しないようにしてください。ヘッダとボディのRAWデータをキャプチャし、フラッシュルートに戻す前に、スクリプト内でハッシュを再現してください。
Goで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がここで堅牊な理由
いくつかの利点が際立っています。
- ハンドラーは明確です。隠れたミドルウェアの魔法はありません。
- エッジで型が役立ちます。ヘッダのパース、タイムスタンプの変換、JSONのデコードはすべて明確に失敗します。
- The standard crypto packages are enough. No extra dependency for basic HMAC verification.
運用ノート
Webhookの大量発生時、Goの並行処理モデルにより、HTTPエントリポイントを変更することなくバックグラウンドワークを拡散させるスペースが得られます。 その場合でも、受信者は狭くしてください。 受信、検証、承認、そして返信を手放す。
GoのWebhookハンドラの中で最も強力なものは、単調です。 それらは、運輸検証とビジネスロジックを混ぜ合わせないし、レスポンスが戻る前にデータベースの重い作業を行わない。
基本的なデバッグテクニック
Webhookのバグは、通常、スタックトレースではなくサポートメッセージとして表示されます。 提供者はイベントを送信したと言い、エンドポイントは何もアプリに到達しない、または署名検証がリクエストが最初の目で見てみると有効に見える場合に失敗したと言います。 その時点で、デバッグは、バイトごとに、正確なHTTP交換を再構築し、どこで破れたかを証明することです。

実用的なデバッグツールキット
ワイヤーフォーマットから始めましょう。
署名チェックが失敗した場合、受信したRAWリクエストボディと検証に使用したヘッダーを、元のバイトと検証入力とともにキャプチャしてください。実際には、バグは単調です。 フレームワークがJSONをパースした後ハッシュした、プロキシがエンコードを変更した、またはテスト再生が元のタイムスタンプヘッダーを無視したことが多いです。 パースされたオブジェクトをログするだけでは十分ではありません。 元のバイトと検証入力を必要とします。
これらのツールは、問題を迅速に分離するのを助けます:
- Raw request capture__CAPGO_KEEP_0__
- Request inspection endpointsServices like
webhook.sitehelp confirm what the sender transmitted. - Local tunneling.
ngrokand similar tools let you test against a local receiver while keeping the provider in the loop. - Manual replayRebuild the request with
curlor Postman using the same body and headers. That is the quickest way to confirm whether your code or the provider payload is the issue. - Provider delivery logsThe sender dashboard often includes response codes, retry history, and request identifiers you can match against your logs.
The pattern matters. Work from the outside in. First verify the provider sent what you expected. Then verify your server received the same bytes. Then verify your code hashed the same bytes with the same secret and timestamp rules.
実際に役に立つログを記録する
良いウェブホークログは、1つの検索で3つの質問に答えるべきです。
| 質問 | 役に立つログフィールド |
|---|---|
| リクエストが到着したか? | route, method, received_at |
| なぜ却下された? | missing_header, stale_timestamp, signature_failed |
| 後で関連付けられるか? | event_id, provider_request_id |
システムが実際に動作する場合、4番目のフィールドが役に立つ。ローカルフィールドを追加してください。 request_id 受信者の受信したリクエストから生成されたもので、App、キュー、ワーカーログを通してリクエストを追跡できます。
保存するものを選択してください。秘密をログに記録しないでください。顧客データ、アクセストークン、請求情報を含む全生産ペイロードをダンプしないでください。代わりにメタデータと短いボディハッシュをログに記録してください。リトライを比較し、2 つの配信が同じかどうかを確認するには、まだ可能です。
元の入力を使用して失敗を再現する
基本的なチュートリアルはこの部分を省略します。失敗したリクエストを完全に再現できない場合は、推測していることになります。
失敗したウェブフックを保存する
- 元のリクエスト本体のバイト
- すべての署名関連ヘッダー
- リクエストタイムスタンプ
- コンテンツタイプ
- プロバイダーのリクエストID
次に、ステージングエンドポイントに再生してみましょう。再生が成功した場合は、トランジット中で何が変化したかを比較してみましょう。一般的な原因には、リクエスト本体を正規化するミドルウェア、文字エンコードの不一致、ヘッダーを削除または書き換えるロードバランサなどがあります。私は、実際のリクエスト本体ではなく、ダッシュボードのプレッティプリントされた表示からペイロードをコピーしたチームが原因で失敗した例も見ました。ホワイトスペースの差異だけでHMAC検証を破ることができました。
より広範なリリースとモバイルトランスポートのトラブルシューティングの場合、同じデバッグの規範がCapgoのガイドにも現れます。 CapacitorのOTA更新のデバッグ用ツール. 異なるトランスポートでも同じ教訓を学ぶ。実際のリクエストパスを変更する前にアプリケーションcodeをキャプチャする。
署名検証が失敗した場合、検証に使用されたrawバイト、精確なヘッダー、タイムスタンプ値を確認し、暗号化codeを触る前に。
生産用Webhookのチェックリスト
Webhookハンドラーは、ステージングでは通常問題なく見えますが、最初のリトライの嵐、不正なペイロード、または2時未満の署名一致ミスまで。生産用のバーは高い。受信者は偽造された要求を拒否し、有効なリトライを承認し、操作者が失敗をデバッグするのに十分な信号を提供しながら、敏感なデータを露呈しない。
セキュリティと正確性のチェック
- すべてのリクエスト署名を検証する. エンドポイントURLは漏洩する。テストURLはチャットで共有される。署名検証は、共有シークレットを知っている送信者が知っていることを示す制御である。
- 古い要求を拒否する. 有効な署名が古いペイロードに付与されていても、再生できる。プロバイダーのリトライモデルに合わせたタイムスタンプの許容範囲を設定する。
- rawボディをハッシュする、JSONをパースしない. ミドルウェアはキーを並べ替える、ホワイトスペースを正規化する、またはエンコードを変更する。検証は、実際に到着したexactバイトに実行する必要がある。
- 秘密をcodeから外す. 環境変数は基本的なものです。クレデンシャルを定期的にローテートする場合や、複数の環境で実行する場合など、シークレット マネージャーがより適切な選択肢です。
- 認証エラーで失敗する.署名ヘッダーが欠落している、不正確な、または予期しないスキームを使用している場合、リクエストを拒否し、理由をログに記録します。
信頼性チェック
- 速い. プロバイダーは通常、2xxを成功として扱います。したがって、リクエストを検証し、必要なものを保存し、後処理をキューまたはワーカーに移行してください。
- ハンドラーを無害化する. 同じイベントが複数回到着する可能性があるため、イベントID、配信ID、または安定したプロバイダー識別子を使用して、イベントの副作用を分離する。
- 予測可能なエラーコードを返す. 不正な入力を指定して
400malformed input401または403検証失敗の場合、5xxシステムが問題の場合のみ。 これにより、プロバイダーのリトライ動作を推論しやすくします。 - 先にパースする前に、制限を設定してくださいCap要求サイズ、コンテンツタイプ、ヘッダーの数を早期に設定してください。 これにより、Webhookエンドポイントが一般的なインジェストホールに変わりません。
- 契約を狭く保つサポートするフィールドとイベントタイプのみを受け入れるようにしてください。 余分なパースは最初は便利ですが、プロバイダーのAPI変更の際に高価になります。
観察可能性のチェック
良好なWebhook操作は、面白くないように見えます。 チームは、3つの質問に迅速に答えることができます: 受信したか? 検証したか? 末端処理が成功したか?
その標準を使用してください
- 受信、検証、処理を別々の結果として追跡してください.
- リクエストID、イベントID、署名ステータス、タイムスタンプスキューをログしてください.
- __CAPGO_KEEP_0__のキュー遅延、ハンドララテンシー、リトライボリュームを測定.
- __CAPGO_KEEP_0__の安全な再生パスをステージングまたは再配信ワークフロー用に維持.
- パターン変更に応じて警告例えば署名失敗の急増や重複配信の場合
Capgoは、より広範な運用ポイントの例として役立ちます。更新ワークフローには、リリース配信と観察性のツールが含まれ、エコシステムの一部はウェブフック関連フローも触れています。実践的な教訓です。配信システムには、受信から完了までの可視性が必要です。
チームが上記のチェックをカバーしている場合、ウェブフック受信者は通常、生産用に良好な状態になります。どのアイテムが欠けている場合、そのギャップはインシデントの際に現れますが、デモの際には現れません。
ウェブフックに関するよくある質問
codeのステータスは何を返すべきですか?
ウェブフックを受け入れた場合、2xxを返してください。検証が失敗した場合、クライアントまたは認証エラーを返してください。失敗のマッチング、例えば入力が不正な場合 __CAPGO_KEEP_0__は、ウェブフックの受信者が生産用に良好な状態になるためのチェックをカバーしている場合、通常、ウェブフックの受信者は生産用に良好な状態になります。どのアイテムが欠けている場合、そのギャップはインシデントの際に現れますが、デモの際には現れません。 ウェブフックのステータスについてよくある質問 400 ウェブフックを受け入れた場合、2xxを返してください。検証が失敗した場合、クライアントまたは認証エラーを返してください。失敗のマッチング、例えば入力が不正な場合 401 無効な認証データの場合、ロジックを一貫して維持してください。 そうすることで、プロバイダーのダッシュボードはより容易に解釈できます。
ウェブフックを同期的に処理するべきですか?
通常、いいえ。 それを検証し、承認し、実際の作業をキューまたはバックグラウンドワーカーに送信してください。 そうすることで、配信パスが速くなり、遅延した下流処理によって引き起こされる重複リトライが減ります。
リトライをどのように扱うべきですか?
リトライが発生することを前提としてください。 ハンドラーに idempotency を組み込んでください。 同じイベントを受信した場合、副作用の重複を防ぎます。 イベント ID またはプロバイダーの配信 ID は、通常の anchor です。
イベントが順序が乱れた場合どうしますか?
可能な限りハンドラーを順序耐性で設計してください。 企業プロセスがシーケンスを必要とする場合、古いトランジションを検出するために十分な状態を保存するのではなく、配信順序がイベント順序を反映していることを前提にしないでください。
ウェブフックのバージョン変更にどう対処するべきですか?
ハンドラーのロジックを意図的にバージョン化してください。 プロバイダーの特定のパースを分離し、コードベースにペイロードの仮定を散らばらせないようにし、実際にキャプチャされたサンプルとともに新しい形式のサポートをロールアウトする前にテストを追加してください。
あなたのチームが Capacitor または Electron アプリを配信する場合、 Capgo は、関連する理由で知っておく価値があります。 それは、署名された Web アップデートを配信するための制御された方法をチームに与え、ロールアウトの動作を観察し、インシデントから回復するためにアプリストアのレビューを待つ必要がなくなるためです。 これは、ウェブフックの設計に共通するエンジニアリングの本能と同じです: 入力を検証し、リリースパスを観察し、回復を速くします。