A Practical Web Hook Example: Secure Implementation Guide

Webhookの実践的な例: セキュアな実装ガイド

Node.js、Python、Goのための完全なWebhook例を探してください。codeで署名を検証する方法を学び、リプレイ攻撃を防ぎ、エンドポイントをデバッグしてください。

マーティン・ドナディュー

マーティン・ドナディュー

コンテンツマーケター

実践的なWebホック例: 安全な実装ガイド

サービスが何かが起こったときに反応する必要がある場合があります。支払いが確定した。顧客レコードが変更された。リポジトリがプッシュされた。毎分APIをポーリングし、繰り返し「何か新しいものがあるか?」と尋ねてサイクルを浪費するか、またはイベントが発生したときにソースシステムが呼び出してください。

ほとんどのWebホックの例の記事はここで止まります。ルートを示し、JSONボディを印刷し、返します、そして終わりです。実際に誰かが偽の要求を送信したり、有効な要求を再生したり、またはハンドラが破綻したりすると、そのバージョンは正しく機能します。 200このガイドは、実際に生産環境で使用するパスを示します。例は小さくコピーできるように設計されていますが、重要な部分を含んでいます: rawボディハンドリング、HMAC検証、タイムスタンプチェック、高速アカウント、実用的なデバッグ。

目次

Webホックとは何か、そしてなぜ使用するか

Webhook とはなぜ使用する?

請求サービスは 02:13 に請求書を支払いとしてマークします。アプリが 02:14 にそれを知ると、顧客はすぐにアクセスできます。アプリが次のポーリングサイクルでそれを知ると、顧客は待ち、サポートはチケットを受け取り、ログは避けられるノイズで埋まります。Webhook はそのタイミングの問題を解決するために、イベントが発生したときに HTTP コールバックを送信します。

実際的な意味では、Webhook はイベント駆動型の POST です。プロバイダーは変更を検出します。たとえば、 invoice.paid, order.createdpush

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.

  • . __CAPGO_KEEP_0__が受け取るHTTPルート。
  • イベント. 生成された名前の変更、例えば invoice.paid または push.
  • ペイロード. codeが必要な詳細が含まれたリクエストボディ。

プロバイダーはすでに発生したことをについての事実を送信します。__CAPGO_KEEP_0__の役割は、送信者の確認、リクエストの新鮮さの確認、最後に変更の適用です。最後の部分は、多くの基本的なチュートリアルが認めていないように、生産環境では二重配信は通常の動作であり、エッジケースではありません。

実用的なルール: イベント駆動型の更新ではWebhookを使用し、スケジュールされた読み取り、バックフィル、または出発イベントを提供しないプロバイダーではPollingを使用します。

より広範な ワークフロー自動化とデータ統合を構築しているチームの場合、Webhookは通常、システムを無駄なリクエストトラフィックなしで同期させるイベント層になります。データ統合が重視されるサービスを開発している場合、Capgo’s バックエンド開発記事 有用なコンテキストは、リトライ、キュー、可視性、エラー処理に関連する基本的な問題が発生するためです。

実稼動環境で機能するものと機能しないもの

通常、耐久性が高いセットアップは、設計上、面白みがなくなる傾向があります。必要なイベントのみにサブスクライブし、エンドポイントをプロバイダーまたはイベントファミリーにスコープする。イベントIDを保存して、重複配信が繰り返される副作用を防ぎます。リクエストが検証されキューに登録されたら、迅速な2xxレスポンスを返し、次に遅いビジネスロジックを非同期で実行します。

脆弱なバージョンは容易に認識できます。1つの汎用エンドポイントがすべてを処理します。署名チェックは早期テスト中はスキップされ、戻ってこないのです。ハンドラーは、イベントが有効か古いものかを確認することなく、重要なテーブルに直接書き込むことになります。その結果はデモでうまくいきますが、リトライストーム、プロバイダー障害、または攻撃者が古いリクエストを再生した場合に失敗します。

このガイドの残りの部分は、このトレードオフによって定義されます。ウェブホック受信者の「ハローワールド」バージョンは小さく、実稼動用のバージョンは署名検証、リプレイ防御、重複処理、デバッグ用のフックを最初から組み込んだものになります。

ウェブホック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"
  }
}

重要な部分は簡単です:

  • Method実稼動環境でのウェブホック配信は、通常、POSTリクエストです
  • Content-Type現代のほとんどのプロバイダーはJSONを送信します。
  • User-Agentデバッグに役立つものの、信頼できるものではありません。
  • Signature headerプロバイダーの有効性のチェックを含む。
  • Timestamp header古いまたは再送された要求を拒否するために使用される。

ボディーの形状の重要性

あなたのcodeは通常、すべてのフィールドに気にしない。イベントのタイプ、イベントの識別子、ビジネスオブジェクトの中身だけに気にしている。 dataそのため、良いハンドラーは必要なものだけをパースし、残りはトラブルシューティングのためにログする。

OpenAPIはこのパターンを直接モデル化しています。OpenAPI 3.1.0は、トップレベルのWebhookサポートを追加しました。 webhooks object, 各のウェブホックは、提供元によってトリガーされるPath Itemと同様に記述される。 newPet ウェブホックの典型的な例では、 post 操作、JSON形式のリクエストボディ、受信を示すレスポンスを使用します。 200 OpenAPIウェブホックの例を参照してください。 独自の受信者またはプロバイダーの契約をドキュメント化している場合、強力な例は抽象的なスキーマの文章よりも役立ちます。.

例として、SheetMergyの__CAPGO_KEEP_0__ドキュメント例を参照するのを好みます。 SheetMergy’s API doc examples ウェブホックは、トランスポート層では単純です。多くの失敗は、ヘッダ、ボディのエンコード、署名ルールに関する誤った仮定によるものです。

ウェブホック署名の安全な検証方法

署名されたウェブホックは、共有シークレットを持つ誰かからこのペイロードが来ているかどうかを1つの質問に答えます。

これは、リクエストが最近かどうか、またはすでに処理したかどうかを尋ねることとは異なります。

署名検証は、最初のゲートではなく最後のゲートではありません。

__CAPGO_KEEP_0__

Webhook署名の検証フロー

通常のHMACフローは次のようになります:

  1. プロバイダーのヘッダーから署名を読み取ります。
  2. Webhook 元のリクエストボディ 提供されたままの状態で読み取ります。
  3. 安全な設定からWebhookシークレットを読み取ります。
  4. 同じアルゴリズムを使用して、予想されるHMACを再計算します。
  5. タイミングセーフな比較で、受信された署名と計算された署名を比較します。
  6. 一致しない場合はリクエストを拒否します。

そのraw-bodyステップは、ほとんどの場合に良好な実装が失敗する場所です。フレームワークがJSONを先にパースしたり、ホワイトスペースを再フォーマットしたり、エンコードの詳細を変更したりする場合、計算された署名はプロバイダーのものと一致しません。

実際のcodeで何を注意するか

よく見る間違いは次のとおりです:

  • パースされたJSONをハッシュ化する. これは行ってはならないことです。 JSON.stringify(req.body) 通常の文字列の等価性を使用する
  • . タイミングセーフな比較を使用する必要があります。シークレットをハードコーディングする
  • . 環境変数またはシークレットマネージャーに保管すること。ヘッダーだけに頼る
  • . 署名ヘッダーは、検証しないと意味をなさない。サービス間でシークレットの取り扱いを強化しているチーム向けに、__CAPGO_KEEP_0__のガイド

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 回処理します。

リプレイ攻撃を防ぐための 5 つの重要なセキュリティ対策を示すチェックリスト。

実際に重要な最小限のチェック

実用的なリプレイ防御は、署名されたタイムスタンプから始まります。プロバイダーはヘッダーや署名されたメッセージにタイムスタンプを含め、受信者は小さな許容範囲外のリクエストを拒否します。

このフローは次のようになります:

プロバイダーが定義した場所からタイムスタンプを読み取ります

  • __CAPGO_KEEP_0__. Do not guess the header name.
  • 整数またはRFC形式の日付として解釈する, 仕様に従ってプロバイダーの
  • サーバーの時刻と比較する.
  • 過去のものや将来のものを拒否する.
  • サインのスキームの一部としてタイムスタンプを検証する プロバイダがサポートする場合

タイムスタンプの論理を信頼する前に、プロバイダの正確な署名形式を確認する

許容範囲の選択

5分間は一般的なデフォルトです。攻撃の窓口を縮小するのに十分短く、時刻のずれや通常のネットワーク遅延を乗り越えるのに十分長い

30秒の窓口はより安全に見えますが、実際のシステムでは、リトライ、キューング、地域的な遅延などが関係する場合に頻繁に破棄されます。30分の窓口は操作が容易ですが、署名された要求が漏洩した場合に攻撃者に与える時間が長くなります。最初は数分、NTPとサーバーを同期し、プロバイダの配信パターンがサポートする場合にのみ、窓口を絞り込んでください。

リプレイ防御は単にタイムスタンプのチェックではありません

タイムスタンプ検証は古い要求をブロックしません。有効期間内に複数回処理されることはありません。同じ署名されたイベントがその期間内に2回届いた場合、まだアプリケーションはそれを認識する必要があります。

2層構造を使用します。

  • イベントIDまたは配信IDを 短期間のストレージであるRedisに保存します。
  • ハンドラをidempotentとみなします。 繰り返し配信が重複した注文、メール、請求アクションを生成しないようにします。
  • 古い要求を拒否します。 理由コードとともに、しかし、秘密情報やフルセンスティブペイロードをログに記録しないようにします。
  • 検証とキューの重い作業を別の場所で行うと、迅速なレスポンスを返します。 既に有効期限切れのウィンドウや削除を考慮しているチームは、このパターンを認識するでしょう。

Capgoのガイド Capacitorアプリケーションにおけるトークン削除パターン 同じ運用概念をカバーします。有効だったクレデンシャルまたはリクエストは、いつまでも信頼され続けるべきではありません。

署名されたものであっても古くなっていることは依然として安全ではありません。

Node.jsでWebhook受信者を作成する

Node.jsとExpressは、まだ最速の方法で本格的な受信者をオンラインにしたい場合、Node.jsとExpressはまだ最速の方法です。しかし、他のどのトラップよりも重要な1つのトラップがあります。Expressがオブジェクトに変換する前に、raw bodyにアクセスする必要があります。

木の机の上のノートパソコンが、Node.js受信者codeを表示しているVSCodeエディタ環境です。

本番向けの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キャプチャはミドルウェアで発生します。元のバイトをハッシュするために保存するためです。
  • タイムスタンプはビジネスロジックよりも前にチェックされます。古いトラフィックに対しての作業は無駄です。
  • The route returns quickly. Long-running work belongs in a queue or background task. 200 迅速にルートが返される。長時間の作業はキューまたはバックグラウンドタスクに属する。Post-ack processing is isolated. Even if downstream logic fails, the receiver path stays small.
  • Post-ack 処理は分離される。下流ロジックが失敗しても、受信者パスは小さくなる。Secrets are the weak point in a lot of webhook implementations. Don’t keep them in source, don’t paste them into test fixtures, and don’t echo them in logs.

Secrets are the weak point in a lot of webhook implementations. Don’t keep them in source, don’t paste them into test fixtures, and don’t echo them in logs. If you need a broader process around rotation and CI handling, Capgo’s guide to If you need a broader process around rotation and CI handling, __CAPGO_KEEP_0__’s guide to managing secrets in CI/CD pipelines covers the operational side well. CI/CD Pipelinesにおけるシークレットの管理のためのより広範なプロセスが必要であれば、__CAPGO_KEEP_0__のCI/CD Pipelinesにおけるシークレットの管理のためのガイドは、運用面をよくカバーしている。

A short walkthrough helps if you want to see the moving pieces in action:

短いウォークスルーが必要であれば、動作する部分を確認するのに役立つ。

What I’d change for a live system

実稼働システムの場合に変更したいことは何ですか?

Flaskは、クリーンなWebhookの例として適しているのは、要求の処理が明示的であり、Pythonの標準ライブラリはHMAC用にすでに必要なものを提供しているからです。

Nodeと同じように、主なことは、raw requestバイトと、パースされたJSON辞書ではなく、確認することです。

署名とタイムスタンプのチェックを含む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() ここでいう「key call」は、実際のところはこれです。ボディのrawバイトを提供します。すぐに、 request.jsonにジャンプすると、すでに署名の不一致が混乱を招く境界線を越えていることになります。

実装に関するいくつかの注意点:

  • を使用するのではなく、平等の代わりに hmac.compare_digest クライアントの失敗として、欠落したヘッダーを扱い、早期に却下する
  • の代わりに
  • を使用する silent=True JSON パース用 Flask が例外を上げるのではなく、エラー処理を制御したい場合は。
  • ルートを薄く保つペイロードが高コストの処理をトリガーした場合、作業をキューイングする。

セキュリティチェックを緩和してサインチェックの不一致をデバッグしないでください。デバッグするには、ハッシュしたバイトを正確に印刷し、プロバイダーが期待するフォーマットを正確に印刷してください。

チームが通常どのようにストUCKになるか

通常の失敗パスは、手作りの JSON ボディでテストし、実際のプロバイダーに切り替えてサインが一致しないことを発見することです。その場合、通常は 3 つの理由のいずれかが原因です: プロバイダーがタイムスタンプ付きのエンベロープを署名する、署名が想定どおりにエンコードされていない、またはミドルウェアがボディを変更したことです。

その場合、ランダムに暗号化 code を変更しないでください。ヘッダとボディをキャプチャし、ハッシュを再現するための小さな孤立したスクリプトを作成し、最後に Flask ルートに戻してください。

Go で Webhook 受信者を構築する

Go は Webhook 受信者として素晴らしい選択肢です。標準ライブラリは十分で、フレームワークを使用する必要はありません。 code は簡単に誠実さを保つことができます。

1 つの注意点は、ボディの処理です。 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のデコードはすべて明確に失敗します。
  • 標準の暗号化パッケージは十分です. 基本的なHMAC検証のために追加の依存関係は必要ありません。

運用に関する注記

ウェブフックの量が増えると、Goの並行性モデルはHTTPエントリポイントを変更することなくバックグラウンドワークを拡散させるためのスペースを与えます。 それでも、受信者は狭くしてください。 受け取る、検証する、承認する、そして返信する。

Goのウェブフックハンドラーの中で最も強力なものは、混乱を避けるためにトランスポート検証とビジネスロジックを混ぜず、レスポンスが戻る前にデータベースの重い作業を行わないものです。

基本的なデバッグテクニック

ウェブフックのバグは通常、スタックトレースではなくサポートメッセージとして表示されます。プロバイダーはイベントを配信したと言い、エンドポイントは何もアプリに到達しない、または署名検証がリクエストが最初の目で見てみると有効に見えるリクエストで失敗したと報告します。ここで、デバッグは、正確にHTTP交換を再構築し、バイトごとに証明することです。どこで破れたかを示すものです。

ウェブフックのデバッグにおける5つの基本ツールとテクニックのリスト

実用的なデバッグツールキット

wire形式から始めましょう。

署名チェックが失敗した場合、受信した元のリクエストボディと検証に使用されたヘッダーをキャプチャしてください。実際には、バグはしばしば面白くありません。フレームワークはJSONをパースした後にハッシュした、プロキシはエンコードを変更した、またはテスト再生では元のタイムスタンプヘッダーを無視した。パースされたオブジェクトをログに記録するだけでは十分ではありません。元のバイトと検証入力が必要です。

これらのツールは、問題を迅速に分離するのに役立ちます:

  • リクエストキャプチャ. 検証中はヘッダー、コンテンツタイプ、コンテンツ長、変更されていないボディをログに記録してください。
  • リクエスト検査エンドポイント. 例えば、 webhook.site 送信元が送信したものを確認するのに役立ちます。
  • ローカルトンネリング. ngrok と同様のツールを使用すると、プロバイダーをループに残しながらローカル受信者とテストできます。
  • マニュアル再生. __CAPGO_KEEP_0__ またはプロバイダー ペイロードが問題であるかを確認する最速の方法は、同じボディとヘッダーを使用して Postman または Postman を再構築することです。 curl or Postman using the same body and headers. That is the quickest way to confirm whether your code or the provider payload is the issue.
  • . 送信者 ダッシュボードには、レスポンス コード、リトライ履歴、リクエスト ID が含まれます。これらの ID をログと照合して、問題を特定できます。パターンは重要です。外から内へ作業してください。まず、プロバイダーが期待どおりに送信したことを確認してください。次に、サーバーが同じバイトを受信したことを確認してください。最後に、__CAPGO_KEEP_0__ が同じバイトを同じシークレットとタイムスタンプのルールでハッシュしたことを確認してください。

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 つの質問に答えるべきです。

質問

有用なログ フィールドUseful log field should include timestamp, request id, response code, and provider payload.
リクエストが届いた?ルート、メソッド、受信日時
なぜ却下された?ヘッダーが欠落している、タイムスタンプが古い、署名が失敗している
後で関連付けられることはできる?イベントID、プロバイダーリクエストID

4番目のフィールドは実際のシステムでは役に立つ request_id ローカルで生成されたもので、リクエストをアプリ、キュー、ワーカーログを通して追跡できるようにしてください。

保存するものを選択的にしましょう。秘密情報はログに記録しないでください。顧客データ、アクセストークン、請求情報が含まれる生産環境のフルペイロードをダンプしないでください。代わりにメタデータと短いボディハッシュをログに記録してください。リトライと2つの配信が同一であるかどうかを確認することができます。

元の入力と同じように失敗を再現する

基本的なチュートリアルではこの部分を省略します。失敗したリクエストを正確に再現できない場合は、推測していることになります。

失敗したウェブフックを保存する

  • 未加工のバイト
  • すべての署名関連ヘッダー
  • リクエストタイムスタンプ
  • コンテンツタイプ
  • プロバイダーのリクエストID

次に、ステージングエンドポイントに再生してみてください。再生が成功した場合、トランジット中で何が変更されたかを比較してみてください。一般的な原因には、リクエストボディを標準化するミドルウェア、文字エンコードの不一致、ヘッダーを削除または書き換えるロードバランサが含まれます。私は、実際のリクエストボディではなく、プリティプリントされたダッシュボードビューのコピーからペイロードがコピーされたチームによる失敗も見たことがあります。ホワイトスペースの差 alone でHMAC検証を破ることができました。

より広範なリリースとモバイルトランスポートのトラブルシューティングのための、Capgoのガイド Capacitorのツール. Different transport, same lesson. Capture the actual request path before changing application code.

異なるトランスポート、同じ教訓。実際のリクエストパスをキャプチャする前に、codeを変更しないでください。

署名検証が失敗した場合、実際のバイト、検証に使用された正確なヘッダー、タイムスタンプ値を触る前に、暗号化を __CAPGO_KEEP_0__。

生産用のWebhookのためのチェックリスト

セキュリティと正しさのチェック

  • すべてのリクエスト署名を検証する. エンドポイントURLが漏洩する。テストURLがチャットで共有される。署名検証は、共有シークレットを知っている送信者がいることを示す制御です。
  • 古いリクエストを拒否する. 有効な署名が古いペイロードに付与されていても、再生できる。提供者のリトライモデルに合致するタイムスタンプの許容範囲を設定する。
  • JSONをパースしたものではなく、raw bodyをハッシュする. ミドルウェアはキーを並べ替える、ホワイトスペースを正規化する、またはエンコードを変更することができる。検証は、到着したexactバイトに実行する必要がある。
  • codeから署名シークレットを保管する. 環境変数はベースライン。シークレットマネージャーは、定期的にクレデンシャルをローテートするか、複数の環境で実行する場合に適切です。
  • 認証エラーで失敗する. 署名ヘッダーが欠落している、不正な、または予想外のスキームを使用している場合、リクエストを拒否し、理由をログに記録する。

信頼性のチェック

  • 承認が速い. プロバイダーは通常、2xxを成功として扱います。 したがって、リクエストを検証し、必要なものを永続化し、キューまたはワーカーに遅延処理を実行してください。
  • ハンドラを無害化する. 同じイベントが複数回到着する可能性があるため、イベントID、配信ID、または安定したプロバイダー識別子に依存する側作用を分離する必要があります。
  • 予測可能なエラーコードを返す. 不正な入力の場合に使用する 400 、検証失敗の場合に使用する 401 、システムが問題である場合にのみ使用する 403 。 これにより、プロバイダーのリトライ動作を推論しやすくなります。 5xx パースする前に制限を設定する
  • . リクエストサイズ、コンテンツタイプ、ヘッダーの数を早期に制限することで、ウェブホックエンドポイントが一般的なインジェクションホールに変化するのを防ぎます。__CAPGO_KEEP_0__
  • __CAPGO_KEEP_0__は契約を狭く維持する. 受け取ったフィールドとイベントタイプのみを受け入れるようにしてください。ローズパーシングは最初は便利に感じるかもしれませんが、提供者APIの変更時には高価になります。

観察性のチェック

良好なWebhook操作は面白く見えません。チームは3つの質問に迅速に答えることができます: 受け取りましたか?検証しましたか?下流処理が成功しましたか?

標準を使用してください:

  • 受け取り、検証、処理を別々の結果として追跡する.
  • リクエストID、イベントID、署名ステータス、タイムスタンプスキューをログに記録する.
  • キュー遅延、ハンドララテンシー、リトライボリュームを測定する.
  • ステージングまたは再送信フローの安全な再生パスを維持する.
  • パターン変更に基づいてアラートする__CAPGO_KEEP_0__は、より広範な運用ポイントの例として便利です。更新フローには、リリース配信と観察性のツールが含まれ、Webhook関連フローにも触れます。実践的な教訓です。配信システムには、受け取りから完了までの可視性が必要です。

Capgoは提供者変更の際に高価になる

チームが上記のチェックをカバーしている場合、ウェブホーク受信者は通常、生産用として良好な状態になります。 どのアイテムが欠けている場合、デモの際ではなく、インシデントの際にそのギャップが表面化する傾向があります。

ウェブホークに関するよくある質問

何のステータスcodeを返すべきですか?

ウェブホークを受け入れた場合、2xxを返します。 但し、検証が失敗した場合、クライアントまたは認証エラーを返します。 例えば、入力が不正である場合には、"422: Unprocessable Entity"、認証データが不正である場合には、"401: Unauthorized"を返します。 そのロジックを一貫して維持することで、プロバイダーのダッシュボードをより簡単に理解できるようになります。 ウェブホークを同期的に処理するべきですか? 通常、いいえ。 それを検証し、承認し、実際の作業をキューまたはバックグラウンドワーカーに送信するようにしてください。 これにより、配信パスが速くなり、遅延した下流処理によって引き起こされる重複リトライが減ります。 400 リトライの扱い方は? 401 リトライが発生することを前提として、ハンドラーに無害性を組み込んでください。 同じイベントを受信した場合、副作用の重複を防ぐことができます。 イベントIDまたはプロバイダーの配信IDは、そのようなアンカーとして一般的に使用されます。

ウェブホークのステータスは__CAPGO_KEEP_0__

ウェブホークを受け入れた場合、2xxを返します。 但し、検証が失敗した場合、クライアントまたは認証エラーを返します。 例えば、入力が不正である場合には、"422: Unprocessable Entity"、認証データが不正である場合には、"401: Unauthorized"を返します。 そのロジックを一貫して維持することで、プロバイダーのダッシュボードをより簡単に理解できるようになります。

ウェブホークを同期的に処理するべきですか?

通常、いいえ。 それを検証し、承認し、実際の作業をキューまたはバックグラウンドワーカーに送信するようにしてください。 これにより、配信パスが速くなり、遅延した下流処理によって引き起こされる重複リトライが減ります。

What if events arrive out of order?

設計ハンドラを順序耐性にすることができる場合に限り、順序を許容するように設計してください。ビジネスプロセスがシーケンスを必要とする場合、古いトランジションを検出するために十分な状態を永続化するのではなく、イベント順序が配信順序を反映していることを仮定するのではなく、古いトランジションを検出するために十分な状態を永続化してください。

How do I deal with webhook version changes?

ハンドラロジックを意図的にバージョン化してください。プロバイダースペシフィックパースを分離し、コードベースにペイロードの仮定を散らばさず、実際にキャプチャされたサンプルとともにテストを追加して、新しいフォーマットに対応する前にロールアウトする前に、提供者固有のパースを分離し、コードベースにペイロードの仮定を散らばさず、実際にキャプチャされたサンプルとともにテストを追加してください。


If your team ships Capacitor or Electron apps, Capgo は、関連する理由でチームに価値がある。チームに制御された方法で署名されたWeb更新を配信し、ロールアウトの動作を観察し、インシデントから回復するためにアプリストアのレビューを待つ必要がなくなるため、同じエンジニアリングの本能に沿ったウェブフックの設計:入力の検証、リリースパスの観察、回復の高速化が必要です。

Capacitor アプリのリアルタイム更新

Web層のバグが生じた場合、Capgo を通じて修正を配信し、数日間待つ必要のないアプリストアの承認を待つのではなく、ユーザーはバックグラウンドで更新を受け取り、ネイティブの変更は通常のレビュー経路を通じます。

はじめに

ブログの最新記事

Capgo は、プロフェッショナルなモバイルアプリを作成するために必要な最良の洞察を提供します。