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.
polling 200, そして完了と呼ぶ。
このガイドは、実際に生産環境で使用するパスを示します。例は小さくてコピーしやすいですが、重要な部分を含んでいます: 未加工のボディの処理、HMAC検証、タイムスタンプのチェック、高速の承認、実用的なデバッグ。
目次
- Webhookとは何か、そしてなぜ使用するか
- Webhook HTTP要求の構造
- Webhook署名の安全な検証方法
- リプレイ攻撃を防ぐ
- Node.jsでWebhook受信者を作る
- PythonでWebhook受信者を作る
- GoでWebhook受信者を作成する
- 基本的なデバッグテクニック
- プロダクション用Webhookのチェックリスト
- Webhookに関するよくある質問
Webhookとは何ですか。なぜ使用しますか?
請求先のサービスプロバイダーは、請求書を02:13に支払いとしてマークします。アプリが02:14にそれを知った場合、顧客はすぐにアクセスできます。アプリが次のポーリングサイクルでそれを知った場合、顧客は待ちます、サポートチームはチケットを受け取り、ログは避けられるノイズで埋まります。Webhookはそのタイミング問題を解決するために、イベントが発生したときにHTTPコールバックを送信します。
実際には、Webhookはイベント駆動型のPOSTです。プロバイダーは変更を検出します。たとえば、 invoice.paid, order.created、 push、
This pattern appears 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.
実際のシステムで見られるパターン
A useful way to frame a webhook flow is as four parts working together:
- Source system. 事件を検出するサービス。
- Destination endpoint. そのHTTPルートが受け入れる。
- Event. 事件の名前、例えば
invoice.paidPayloadpush. - . その__CAPGO_KEEP_0__が必要な詳細が含まれるリクエストボディ。. The request body with the details your code needs.
The provider sends facts about something that already happened. Your job is to verify the sender, confirm the request is fresh, and apply the change once. That last part matters more than many basic tutorials admit. In production, duplicate delivery is normal behavior, not a corner case.
Practical rule: Webhookを使用してイベント駆動型の更新を行い、pollingを使用して予定された読み取り、バックフィル、または出発イベントを提供しないプロバイダー用に。
チームがより広範な ワークフロー自動化とデータ統合, webhooks usually become the event layer that keeps systems in sync without unnecessary request traffic. If you work on integration-heavy services, Capgo’s __CAPGO_KEEP_0__の バックエンド開発記事
は、リトライ、キュー、観察性、エラー処理などのコア問題が再試行、キュー、観察性、エラー処理などのコア問題が再現されるため、統合重視のサービスで役に立つコンテキストです。
生産環境で機能し、失敗しないセットアップは、通常、面白みのない設計です。必要なイベントのみにサブスクライブし、エンドポイントをプロバイダーまたはイベントファミリーによってスコープする。イベントIDを保存して、重複配信が再現されることなく、リクエストが検証され、キューに登録されたら、迅速に2xxのレスポンスを返し、次に遅いビジネスロジックを非同期に実行します。
The fragile version is easy to recognize. One generic endpoint handles everything. Signature checks get skipped during early testing and never come back. The handler writes directly to critical tables before checking whether the event is authentic or stale. That works in a demo and fails under retry storms, provider outages, or an attacker replaying old requests.
That trade-off defines the rest of this guide. The “hello world” version of a webhook receiver is small. The production-ready version adds signature verification, replay defense, duplicate handling, and debugging hooks from the start.
Webhook HTTP Requestの構成
Before writing code, it helps to look at the request as raw HTTP instead of as a framework object. A typical webhook is just an HTTP POST to a public endpoint with headers and a JSON body.
A simple raw request
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"
}
}
重要な部分は簡単です:
- Method. 実際の場合、Webhookの配信は通常POSTリクエストです。
- Content-Type. 最新のモダンプロバイダーはJSONを送信します。
- User-Agent. デバッグに役立ちますが、信頼できるものではありません。
- 署名ヘッダー. 提供者の有効性確認を実行します。
- タイムスタンプヘッダー. 古いまたは再送信された要求を拒否します。
ボディーの形状はなぜ重要か
あなたの code は通常、すべてのフィールドに気にしないです。 それがイベントのタイプ、イベントの識別子、ビジネス オブジェクトの中に気にします。 data それがなぜ良いハンドラーは、必要なのはイベントのタイプ、イベントの識別子、ビジネス オブジェクトだけをパースし、残りはトラブルシューティングのためにログします。
OpenAPIはこのパターンを直接モデル化しています。 OpenAPI 3.1.0は、トップレベルオブジェクトで、各WebhookはPath Itemと同様に記述され、提供者によってトリガーされます。 Canonical例では、Webhookに操作、JSON要求ボディ、受信を示すレスポンスがあります。 webhooks OpenAPIはこのパターンを直接モデル化しています。 OpenAPI 3.1.0は、トップレベルオブジェクトで、各WebhookはPath Itemと同様に記述され、提供者によってトリガーされます。 Canonical例では、Webhookに操作、JSON要求ボディ、受信を示すレスポンスがあります。 newPet OpenAPIはこのパターンを直接モデル化しています。 OpenAPI 3.1.0は、トップレベルオブジェクトで、各WebhookはPath Itemと同様に記述され、提供者によってトリガーされます。 Canonical例では、Webhookに操作、JSON要求ボディ、受信を示すレスポンスがあります。 post OpenAPIはこのパターンを直接モデル化しています。 OpenAPI 3.1.0は、トップレベルオブジェクトで、各WebhookはPath Itemと同様に記述され、提供者によってトリガーされます。 Canonical例では、Webhookに操作、JSON要求ボディ、受信を示すレスポンスがあります。 200 OpenAPIはこのパターンを直接モデル化しています。 OpenAPI 3.1.0は、トップレベルオブジェクトで、各WebhookはPath Itemと同様に記述され、提供者によってトリガーされます。 Canonical例では、Webhookに操作、JSON要求ボディ、受信を示すレスポンスがあります。 OpenAPI webhook example.
独自の受信者またはプロバイダ契約をドキュメント化している場合、強力な例は抽象的なスキーマの文章よりも役立ちます。私は、 SheetMergyのAPIドキュメントの例 を使用するのを好みます。なぜなら、それはリクエストの例、フィールドの説明、そして期待されるレスポンスがどのように組み合わさっているか明確に示すからです。
Webhookはトランスポート層では単純です。ほとんどの失敗はヘッダ、ボディのエンコード、または署名規則に関する誤った仮定によるものです。
Webhook署名の安全な検証方法
署名されたWebhookは1つの質問に答えます: そのペイロードは共有シークレットを知っている誰かのものですか?
これは、リクエストが最近であるか、またはすでに処理したことがあるかどうかを尋ねることとは異なります。署名検証は最初のゲートであり、最後のゲートではありません。

Webhook署名の検証フロー
通常のHMACフローは次のようになります。
- プロバイダのヘッダから署名を読み取ります。
- Capgoを使用する 未加工のリクエストボディ 正確に受け取ったままです。
- セキュアな設定からウェブホックシークレットを読み込みます。
- 同一のアルゴリズムを使用して、予想される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);
}
これは意図的に汎用性のあるものである。実際のプロバイダーは、署名にプレフィックスを付ける、タイムスタンプを署名内容に組み込む、またはハッシュを別の方法でエンコードすることがある。ルールは同じである。プロバイダーの正確な署名形式に従い、常に元のペイロードに対して検証すること。
再送信攻撃を防ぐ
署名されたWebhookは、数時間後に到着し、ハンドラーがそれを新しいものとして扱う場合でも、危険である。チームはそれが頻繁に発生することを想定していない。

署名検証は、共有シークレットでこのペイロードを作成したのは送信者だったかどうかを答える。再送信保護は、現在はこのリクエストを受け入れるべきかどうかを答える。
実際に重要な最小限のチェック
実用的な再送信防御は、署名されたタイムスタンプから始まる。プロバイダーはヘッダーまたは署名されたメッセージにタイムスタンプを含め、受信者はリクエストが小さな許容範囲外にある場合にそれを拒否する。
その流れは次のようになっているはずだ。
- プロバイダーが定義した場所からタイムスタンプを読み取るヘッダー名を推測しないように。
- RFC形式の日付または整数として解釈するプロバイダーの仕様に基づいて。
- サーバーの時刻と比較する.
- 古すぎるまたは将来のリクエストを拒否する.
- 署名スキームの一部としてタイムスタンプを検証する 提供者がサポートする場合。
最後の点は重要です。タイムスタンプが署名によってカバーされていない場合、攻撃者はオリジナルのボディに新しいタイムスタンプを挿入してリプレイ攻撃を実行できます。私は常に提供者の正確な署名形式を確認する前にタイムスタンプロジックを信頼するのを避けます。
許容範囲の選択肢
5分は一般的なデフォルトです。短すぎて攻撃の範囲を縮めますが、時刻のずれや通常のネットワーク遅延を乗り越えるのに十分です。
ここではトレードオフがあります。30秒のウィンドウはより安全に見えますが、実際のシステムではリトライ、キューング、地域的な遅延などが絡むと頻繁に破綻します。30分のウィンドウは操作が容易ですが、署名されたリクエストが漏洩した場合に攻撃者に与える時間が長くなります。最初は数分、サーバーをNTPと同期し、提供者の配信パターンがサポートする場合にのみ厳密化してください。
リプレイ防御はタイムスタンプチェックだけではありません
タイムスタンプ検証は古いリクエストをブロックしますが、有効なウィンドウ内で同じ署名されたイベントが 2 回送信された場合、同様のイベントを認識するにはアプリケーションは別のレイヤーを使用する必要があります。
2層目として使用する
- イベントIDまたは配信IDを短期間のストレージであるRedisにトラッキングする 短期間のストレージであるRedisにイベントIDまたは配信IDをトラッキングする
- idempotentハンドラーを扱う 繰り返し配信は、重複した注文、メール、請求アクションを生成しないようにする
- 古いリクエストを拒否する 理由コードとともに、秘密情報やフルセンスティブペイロードをログに記録しないようにする
- 検証とキューの重い作業を別の場所で行う後、速いレスポンスを返す 有効期限のウィンドウや削除を考慮しているチームは、パターンを認識するだろう。
Capgoのガイド Capacitorアプリのトークン削除パターン 有効期限が切れた資格情報やリクエストは、安全ではない
Node.jsでWebhook受信者を作る
NodeとExpressは、Webhook受信者をオンラインにしたい場合でも、最速の方法だ
ただ1つの罠が、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ボディキャプチャが発生します。 これにより、ハッシュのために元のバイトが保存されます。
- タイムスタンプはビジネスロジックの前にチェックされます。古いトラフィックに対しては、仕事をする必要はありません。
- ルートは
200速く 長時間の作業はキューまたはバックグラウンドタスクに属するものです。 - アクセプト後の処理は分離されています. どんなに下流のロジックが失敗しても、受信パスは小さくなる。
シークレットは、多くのウェブホック実装の弱点です。ソースに保管しないでください、テスト用のフィクスチャに貼り付けてはいけません、ログに反映してはいけません。CIハンドリングとローテーションについてより広いプロセスが必要な場合は、CapgoのCI/CDパイプラインにおけるシークレットの管理に関する ガイドが十分にカバーしています。 シークレットの管理を実際に動くものとして理解するには、短いウォークスルーが役立ちます。
実際のシステムでは変更したいこと
実際のプロバイダ統合では、永続ストレージにイベントIDの重複排除、リクエストIDを含む構造化ログ、承認パスの後ろにキューを追加することなどを実装したいです。また、複数のプロバイダが異なる署名形式を使用する場合、単一の汎用エンドポイントを避け、分離されたハンドラーを使用することで、より論理的で破壊されにくい実装が可能になります。
PythonでWebhook受信者を作成する
Flaskは、明確なリクエストハンドリングとHMAC用のPython標準ライブラリが用意されているため、クリーンなウェブホックの例として適しています。
主なことは、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)
__CAPGO_KEEP_0__
request.get_data() はここでの重要な呼び出しです。 これは、ボディのrawバイトを提供します。 すぐにでも へジャンプすると、署名の不一致が混乱を招く点で線を越えていることになります。 request.json実装に関するいくつかの注意点があります:
を使用するのではなく、平等の代わりに使用してください。
- ヘッダーが欠落している場合、クライアントの失敗として扱い、早期に拒否してください。
hmac.compare_digestJSONのパースに使用する場合は、Flaskが例外を上げるのではなく、エラーのハンドリングを制御したい場合は を使用してください。 - ルートを薄く保つ。 付加的なコストがかかる場合、ペイロードが何かをトリガーした場合、作業をキューに追加してください。 __CAPGO_KEEP_0__
- __CAPGO_KEEP_0__
silent=True__CAPGO_KEEP_0__ __CAPGO_KEEP_0__ - __CAPGO_KEEP_0____CAPGO_KEEP_0__
__CAPGO_KEEP_0__
Where teams usually get stuck
The common failure path is testing with a hand-built JSON body, then switching to a real provider and finding the signature no longer matches. That usually means one of three things: the provider signs a timestamped envelope, the signature is encoded differently than you assumed, or middleware changed the body before verification.
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.
Building a Webhook Receiver in Go
Go is a great choice for webhook receivers because the standard library is enough. You don’t need a framework to get a small, reliable handler, and the code is easy to keep honest.
The one thing to be careful with is body handling. r.Body is a stream. Read it once, hash the bytes you got, and then unmarshal from those same bytes.
A standard library example
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))
}
Why Go feels solid here
A few benefits stand out:
- __CAPGO_KEEP_0__. __CAPGO_KEEP_0__
- エッジの部分でタイプが役に立つ. ヘッダーパース、タイムスタンプの変換、JSONのデコードが明確に失敗する
- 標準の暗号化パッケージが十分. 基本的なHMAC検証のための追加の依存関係が必要ない
運用メモ
ウェブフックの量が増えると、Goの並行性モデルはHTTPエントリポイントを変更することなくバックグラウンドワークを拡散させるためのスペースを与えます。そうしても、受信者は狭いままにしておきましょう。受信、検証、承認、そして応答が戻る前にデータベースの処理を避けましょう。
Goのウェブフックハンドラーの中で最も強力なものは、基本的に面白くありません。トランスポート検証とビジネスロジックを混ぜないし、応答が戻る前にデータベースの処理をしない
基本的なデバッグテクニック
ウェブフックのバグは通常、スタックトレースではなくサポートメッセージとして表示されます。プロバイダーはイベントを送信したと言い、エンドポイントはアプリに何も届かなかった、または署名検証がリクエストが最初の目で有効に見えるように失敗したと言います。その時点で、デバッグは、バイトごとに精確にHTTP交換を再構築し、どこで破綻したかを証明することです。

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