顧客が支払いを行うと、Stripeはバックエンドにpayment_intent.succeededイベントを発火し、エンドポイントは注文を支払い済みとしてマークすることになっています。この最後のステップは、静かに機能不全に陥ることがあります。ウェブフックが到着し、ハンドラがエラーをスローしても、「支払い済みのはずなのにアカウントでは未払いになっている」というサポートチケットが来るまで誰も気づきません。出荷するたびに、イベントが到着し、正しく処理されたことを証明するテストをCIに含めたいと考えるでしょう。
厄介なのは、ウェブフックがStripeからあなたへのインバウンドHTTPコールであり、あなたが行うリクエストではないということです。ほとんどのAPIテストツールは、リクエストを送信してレスポンスを確認するように作られており、これはウェブフックとは逆の形です。そこで問題となるのは、CI実行中に、人間の監視なしで、独自のスケジュールで到着するものについて、どのようにアサートするかということです。このガイドでは、Apidogを使ってそれを行うための正直でサポートされている方法を示しますが、その前に知っておくべき制約があります。イベント駆動型エンドポイントのテストについてより広い視野で知りたい場合は、まずウェブフックをテストする方法に関するガイドが準備段階を説明し、Stripe自身のウェブフックのドキュメントがイベント配信モデルをカバーしています。
設計の際に考慮すべき制約
Apidog自身のドキュメントに明記されている重要な事実があります。「Apidogはウェブフックのリッスンをネイティブにサポートしていません」。Apidogは公開URL上に常駐してStripeからのインバウンドコールをリアルタイムでキャッチするわけではありません。もしStripeをApidogのリスナーに向けてイベントが流れ込むのを期待していたとしても、その経路は存在しません。
それは行き止まりのように聞こえますが、そうではありません。単にテストの形が変わるだけです。ウェブフックが到着したときにそれを傍受する代わりに、自身のバックエンドでキャプチャし、保存し、その後Apidogにその保存されたレコードをクエリさせてアサートします。まずキャプチャし、次に検証します。この分離を受け入れれば、ワークフロー全体は簡単になり、重要なことに、データベースクエリは決定的で反復可能であるため、CIに完全に適合します。
キャプチャ・クエリパターンの概要
Apidogのドキュメントが推奨するパターンには、4つの要素があります。
- 受信するStripeウェブフックをキャプチャするためのエンドポイントをバックエンドサービスに作成する。
- ウェブフックイベントデータをデータベース内の
Stripe event logsテーブルに保存する。 - ApidogのPost-Request Processorを使用してデータベースをクエリする。
- 保存されたウェブフックイベントを取得し、期待される結果と照合して検証する。
これらのステップのうち2つはあなたのコード内にあり、2つはApidog内にあります。キャプチャエンドポイントとロギングテーブルは、あなた自身のアプリケーション内で実行されるため、構築する責任はあなたにあります。Apidogの仕事は、イベントがデータベースに入った後に始まります。そのデータベースに接続し、行を読み戻して、イベントが期待どおりに処理されたことを確認します。この分担を明確にすれば、あとは自然と上手くいきます。
ステップ1: キャプチャエンドポイントの構築
あなたのバックエンドには、StripeがPOSTできるルートが必要です。これは通常のアプリケーションコードであり、Apidogの機能ではありません。署名を検証し、イベントをログに記録する最小限のExpressハンドラは次のようになります。
import express from "express";
import Stripe from "stripe";
const app = express();
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;
app.post(
"/webhooks/stripe",
express.raw({ type: "application/json" }),
async (req, res) => {
let event;
try {
event = stripe.webhooks.constructEvent(
req.body,
req.headers["stripe-signature"],
endpointSecret
);
} catch (err) {
return res.status(400).send(`Signature check failed: ${err.message}`);
}
// Persist the event so a test can read it back later.
await db.query(
`INSERT INTO stripe_event_logs (event_id, type, payload, handled_at)
VALUES ($1, $2, $3, now())
ON CONFLICT (event_id) DO NOTHING`,
[event.id, event.type, JSON.stringify(event.data.object)]
);
if (event.type === "payment_intent.succeeded") {
const intent = event.data.object;
await markOrderPaid(intent.metadata.order_id);
}
res.json({ received: true });
}
);
ここでは2つのことが重要です。第一に、信頼する前にconstructEventでStripe署名を検証することです。これは、あらゆるウェブフック受信者にとって譲れないセキュリティステップです。このチェックの背後にある完全な理由を知りたい場合は、ウェブフック署名検証に関する私たちの解説で、なぜ生データ比較がそれを行う唯一の安全な方法であるかを詳しく説明しています。第二に、イベントをStripe event logsテーブルに書き込むことです。この行をApidogが読み取ります。Stripeは同じイベントを複数回配信できるため、ON CONFLICT DO NOTHING句によってログは冪等に保たれます。
ステップ2: Apidog環境でのデータベース接続
Apidogは対応する環境でのデータベース接続をサポートしており、この接続がこのパターン全体を機能させます。CI実行がターゲットとする環境(ステージングPostgresであろうと専用のテストデータベースであろうと)のデータベース接続を設定してください。接続が確立されると、テストステップはそのデータベースに対してSQLを実行し、実際の行を取得できます。
接続をテストしている環境と一致させてください。ステージングに対して実行されるテストはステージングデータベースをクエリするべきであり、そうすることでテストがトリガーするイベントがテストが読み取るイベントになります。環境の不一致は、合格しているキャプチャエンドポイントがアサーションに失敗する最も一般的な理由です。
ステップ3: ログをクエリするためのPost-Request Processorの追加
これが核心です。Post-Request Processorは、データベースをクエリし、ログに記録されたウェブフックイベントをテスト内で検証するApidogの機能です。テストシナリオのリクエストにこれをアタッチします。リクエストが実行された後、プロセッサはSQLを実行し、保存されたイベントを読み取り、その結果に対してアサートできるようにします。
payment_intent.succeededケースの現実的なフロー:
- テストシナリオが支払いをトリガーします。これは、支払いインテントを作成しStripeテストモードでそれを確認するリクエスト、または既知のテストイベントをキャプチャエンドポイントに発火するフィクスチャかもしれません。
- Stripeはウェブフックを
/webhooks/stripeルートに配信し、そこで署名を検証し、stripe_event_logsに1行を書き込みます。 - 次のステップにある
Post-Request Processorが、イベントのそのテーブルをクエリします。
プロセッサが実行するクエリはプレーンなSQLです:
SELECT event_id, type, payload, handled_at
FROM stripe_event_logs
WHERE type = 'payment_intent.succeeded'
ORDER BY handled_at DESC
LIMIT 1;
次に、返された行に対してアサートします。テストは、ログに記録されたデータが期待値と一致した場合に合格します。つまり、`type`が`payment_intent.succeeded`であり、`event_id`がトリガーしたものと一致し、`payload`の金額が請求したものと等しく、`handled_at`が入力されていることです。これは、行が単なるプレースホルダーではなく、ハンドラが実際に実行されたことを証明します。保存されたウェブフックイベントを取得し、期待される結果と比較し、アサーションに合格か失敗かを判断させます。
ウェブフックの配信タイミングは即時ではないため、クエリを実行する前にイベントが到着するのを少し待つ時間を設けてください。短い遅延ステップ、または失敗するまで数回クエリを再試行するポーリングループにより、テストがStripeの配信と競合するのを防ぎます。これは、ウェブフックの非同期性がテスト設計に漏れ出す唯一の場所であり、小さなリトライウィンドウがこれをきれいに処理します。
ローカル開発中のリアルタイム転送に関する注意点
キャプチャ・クエリパターンはCI向けに構築されており、データベースと保存されたレコードがまさに望ましいものです。ローカル開発は異なる状況です。ラップトップでハンドラを作成しているとき、Stripeは直接localhostに到達できないため、イベントをリアルタイムでマシンに転送するものが必要です。
そのためには、Apidogのドキュメントは、Stripe CLIとNgrokを例として挙げ、ウェブフックリレーサービスを指しています。Stripe CLIは、イベントをローカルポートに直接リッスンして転送できます:
stripe listen --forward-to localhost:3000/webhooks/stripe
これにより、ハンドラの構築中にライブイベントが提供されます。Ngrokも同様に、ローカルポートを公開URLで公開し、それをStripeエンドポイントとして登録することで同じ役割を果たします。これらを内部開発ループに使用し、パイプラインで実行されるアサーションにはデータベースと`Post-Request Processor`のフローに頼ります。この2つは補完的です。構築にはリレー、証明にはキャプチャ・クエリです。
ApidogのネイティブなWebhook機能と混同しないこと
Apidogには文字通りWebhookという機能がありますが、それがStripeイベントをキャッチする方法だと誤解しがちです。そうではなく、それらを混同すると午後を無駄にすることになります。ネイティブのWebhook機能は、アウトバウンドウェブフックを定義および文書化するためのものです。つまり、イベントが発生したときにあなた自身のシステムが呼び出すHTTPエンドポイントを意味します。システムは外部URLへの呼び出しを開始し、これはクライアントがあなたを呼び出す通常のエンドポイントとは逆です。これは、APIドキュメントで状態変更通知や非同期タスクの結果を記述するために使用され、Stripeからのインバウンドコールを受信するためではありません。
もしあなた自身のアウトバウンドウェブフックを文書化したい場合は、その流れは短いです:
- 左サイドバーの
+アイコンをクリックします。 - `New Other Protocol APIs`を選択し、次に`Webhook`を選択します。
- 必須フィールドに記入します。`Request Method` (通常はPOST)、`Webhook Name`、テスト専用のオプションの`Debug URL`、およびリクエストボディ、ヘッダー、設定用の`Other Info`です。
- `Save`をクリックします。
試すには、`Debug URL`フィールドにURLを入力し、`Send`をクリックしてウェブフック呼び出しをシミュレートします。覚えておくべき注意点が1つあります。`Debug URL`はテスト専用であり、公開されたドキュメントやOpenAPIエクスポートには表示されません。イベントコールバックの設計と文書化に関するより詳細な情報については、API設計におけるウェブフックに関する記事で、それらがどこに適合するかを説明しています。この記事の要約: ネイティブのWebhook機能はあなたのアウトバウンドイベントを定義し、キャプチャ・クエリパターンはStripeのインバウンドイベントを検証します。これらを明確に区別して考えてください。
バリエーションと堅牢化
基本的なアサーションが機能したら、いくつかの改良を加えて本番レベルにします。まず、イベントタイプ以上のものに対してアサートします。`event_id`をエンドツーエンドでチェックし、トリガーした正確なイベントが、以前の実行の残骸ではなく、検証したものであることを確認します。イベントが蓄積される場合は、テスト実行ごとに`stripe_event_logs`テーブルを切り捨てるかスコープを設定します。
次に、失敗パスをテストします。ハンドラが拒否すべきイベント(不正な署名や予期しないタイプ)を発火させ、`handled_at`タイムスタンプが書き込まれないことをアサートします。ハッピーパスのみをチェックするウェブフックテストスイートでは、実際に午前2時にあなたを呼び出すようなケースを見落とします。支払いウェブフックのベストプラクティスに関する私たちのメモでは、これらのテストに組み込む価値のある冪等性やリトライの動作について説明しています。
第三に、アサーションを単なる配信ではなく、ビジネス上の意味に厳密に合わせてください。「イベントが到着した」というだけでは、「注文が支払い済みになった」というよりも弱いです。ハンドラが`orders`テーブルを更新する場合、下流の状態が変更されたことを確認する2番目のクエリを追加して、テストがログの書き込みだけでなく、チェーン全体を証明するようにします。
これをマージゲートのさらに先に進めることもできます。一度シナリオがApidogに保存されたら、デプロイ間でも壊れたウェブフックハンドラが表面化するように、定期的に実行するようスケジュールします。ApidogでAPIテストをスケジュールする方法に関するガイドは、この同じ検証をタイマーで実行する方法を示しています。
Apidog CLIでワークフローを自動化
上記のすべては、無人で実行されるときに報われます。そこでApidog CLIの出番です。これは本質的にCIの物語であり、保存されたシナリオをパイプラインに組み込むことが自然な仕上げとなります。CLIをインストールし、トークンで認証します:
npm install -g apidog-cli
apidog login --with-token <YOUR_ACCESS_TOKEN>
次に、イベントログを保持するデータベースがある環境に対して、保存されたウェブフック検証シナリオをヘッドレスで実行します:
apidog run --access-token $APIDOG_ACCESS_TOKEN -t <SCENARIO_ID> -e <ENV_ID> -r cli
ここで、`-t`はテストシナリオID、`-e`は環境ID、`-r`はレポーターを選択します。CIアーティファクトのコンソール出力と並行してブラウザで閲覧可能なレポートが必要な場合は、`-r html,cli`を使用します。シナリオには`Post-Request Processor`とデータベースクエリが含まれているため、単一のコマンドでフローをトリガーし、`stripe_event_logs`の行を読み取り、アサーションが失敗した場合はゼロ以外の終了コードを返します。これはパイプラインがマージをゲートするために必要なものです。Apidog CLIインストールガイドではトークン設定を、CI/CDパイプラインウォークスルーではこのコマンド周辺の完全なGitHub Actionsの構成を示しています。
よくある質問
ApidogはStripeウェブフックを直接受信できますか? いいえ。Apidogのドキュメントには「ウェブフックのリッスンをネイティブにサポートしていません」と明記されています。イベントは自身のバックエンドエンドポイントでキャプチャし、データベースに保存し、Apidogが`Post-Request Processor`でそれを読み戻します。ローカル開発中のリアルタイム転送には、代わりにStripe CLIやNgrokのようなリレーを使用してください。
アサーションは実際どこで行われますか? テストシナリオ内のリクエストの`Post-Request Processor`ステップ内で発生します。環境で設定したデータベース接続を介して`Stripe event logs`テーブルをクエリし、保存されたイベントを取得して、期待値と比較します。ログに記録されたデータが一致した場合にテストは合格します。
データベース検証フローのために有料プランが必要ですか? このワークフローに関するApidogのドキュメントには、プランによる制限は記載されていませんので、このガイドでは何も推測しません。正直な答えは、料金ページの現在のプラン詳細を確認することです。Apidogをダウンロードし、テストプロジェクトを設定して、Post-Request Processorと環境データベース接続を自分で確認できます。
トリガーと配信の間の遅延はどのように処理すればよいですか? ウェブフックの配信は即時ではないため、クエリの前に短い待機時間またはポーリングリトライを追加して、テストがStripeと競合しないようにしてください。数秒間の数回のリトライで通常は十分です。非同期エンドポイントでのアサートに慣れていない場合は、Stripeの具体的な内容に入る前に、一般的なウェブフックをテストする方法ガイドから始めてください。
ネイティブのWebhook機能はここで全く役に立ちませんか? Stripeイベントをキャプチャするためには役に立ちません。その機能は、あなたのシステムが外部URLを呼び出す、あなた自身のアウトバウンドウェブフックを定義し、文書化するためのものです。それはドキュメントと設計ツールであり、この記事で使用されているインバウンドのキャプチャ・クエリパターンとは別物です。この2つを明確に区別してください。
まとめ
StripeをApidogに向けてライブでイベントをキャッチすることはできません。そう思ってしまうと、イライラする午後を過ごすことになります。サポートされている方法は、最初に見たときよりも明確です。ウェブフックを自身の専用エンドポイントでキャプチャし、`Stripe event logs`テーブルにログを記録し、その後Apidogの`Post-Request Processor`にそのレコードをクエリさせ、イベントが処理されたことをアサートさせます。保存されたシナリオを`apidog run`で実行すれば、マージごとに、実際の支払いイベントが注文を支払い済みに変更したことをパイプラインが証明します。クレジットカード不要で無料で試して、最も重要なウェブフックの背後にある実際のアサーションを設定してください。
