開発

決済Webhookとは?仕組み・主要イベントと署名検証・冪等な注文確定の実装を開発視点で解説

決済Webhookとは、Stripeなどの決済プラットフォーム側で「支払いが成功した」「返金された」といった決済イベントが起きた瞬間、加盟店が用意した受信URLへ通知を送ってくれる仕組みです。ECサイトや決済システムを実装するうえで、この通知を正しく受け取り、注文確定や在庫引当を安全に駆動できるかが、二重課金や売上の二重計上といった事故を防ぐ分かれ目になります。この記事では「決済Webhookとは何か」という定義から一歩踏み込み、扱う主要イベントと注文確定の駆動点、署名検証と冪等性による二重処理の防止、イベント順序やリトライの副作用制御、内製と受託開発のどちらで進めるかの判断軸までを、開発者が設計に落とせる粒度で整理します。

まとめ|決済Webhookの仕組みと実装判断の要点

決済Webhookは、決済の成否をブラウザへ返す同期レスポンスだけに頼らず、PSPから自社サーバーへ非同期でイベント通知を届ける仕組みです。利用者が決済ボタンを押した後にブラウザを閉じても、支払い成功のイベントはサーバー側に届くため、注文確定・在庫引当・メール送信といった副作用は、この通知を起点に駆動する設計が土台になります。Webメッセージ通知のIncoming Webhookとは目的が異なり、扱うのは金銭が動く決済イベントである点が設計上の勘所です。

実装で外せないのは、署名検証・冪等性・即時応答の三点です。エンドポイントは公開URLのため、Stripe-Signatureヘッダーと署名シークレットで正規の通知かを検証し、偽の通知で処理が走る事態を止めます。配信は少なくとも一度(at-least-once)で同じイベントが再送されうるため、イベントIDを基準に処理済みを記録して二重処理を防ぐのが定石です。そして受信直後に2xxを返し、重い処理はキューへ逃がすことで、タイムアウトによる不要なリトライを避けられます。署名検証やリトライといった一般的なWebhook実装の機構はWebhookの実装方法と作り方|署名検証・リトライ・冪等性を解説で共通の型として確認でき、本稿はそれを決済イベントに適用する視点で読み進められます。

決済Webhookとは|決済イベントを非同期で受け取る仕組み

決済Webhookは、カード決済や定期課金の裏側で起きたイベントを、PSPが加盟店のサーバーへ知らせる通知の仕組みです。決済の入り口で走る与信処理とは別に、その後の成否や返金・チャージバックまでを取りこぼさず捕捉するための土台になります。

決済Webhookの定義と、同期レスポンスだけに頼れない理由

決済Webhookは、PSP側で支払い成功・失敗・返金・チャージバック・定期課金の更新といったイベントが発生した瞬間、加盟店が登録した受信URLへHTTP POSTで通知を送る仕組みです。なぜ同期レスポンスだけでは足りないかというと、決済フローの最終結果は利用者のブラウザ操作に依存しないからです。3Dセキュアの認証画面から戻る前にブラウザを閉じたり、通信が切れたりしても、支払いが成立していればPSPからサーバーへイベントが届きます。この通知を注文確定の正とすることで、画面遷移の途切れに左右されない決済処理を組めます。

Incoming Webhookや一般のWebhookとの違いと、決済ならではの要件

Webhookという言葉は、SlackやDiscordへメッセージを送るIncoming Webhookとして知る開発者も多い仕組みです。決済Webhookもイベント発生時にHTTPで通知するという骨格は同じですが、扱う対象が金銭を動かす決済イベントである点が決定的に異なります。通知が偽物なら不正な入金確定につながり、同じ通知を二度処理すれば売上の二重計上や在庫の二重引当という事故を招く挙動です。そのため署名検証・冪等性・順序制御を、通知系のWebhook以上に厳密に設計する必要があります。Webhook全般の作り方や署名検証・リトライの共通機構はWebhookの実装方法と作り方にまとめており、本稿はその機構を決済という文脈へ落とし込む位置づけです。

決済Webhookで扱う主要イベントと、注文確定をどのイベントで駆動するか

決済Webhookの設計で最初に決めるのは、どのイベントを受けて、どのイベントで注文を確定するかです。ここを曖昧にすると、支払い前に出荷したり、同じ注文を二度確定したりする事故につながります。

決済Webhookで押さえておく主要な決済イベントの種類と役割

Stripeを例にすると、実装でよく扱うイベントは次のとおりです。payment_intent.succeededは課金が確定したこと、payment_intent.payment_failedは決済の失敗を表します。checkout.session.completedはStripe Checkoutの決済フローが完了したことを示す高レベルのイベントで、自分で付けたメタ情報(注文IDなど)を保持する種別です。返金はcharge.refunded、チャージバック(異議申立)はcharge.dispute.createdで届き、定期課金ではinvoice.paidや請求失敗のイベントが更新のたびに飛びます。まず自社の業務で反応すべきイベントを洗い出し、受信するイベント種別を絞り込むところから設計を始めます。

注文確定をcheckoutとpayment_intentのどちらで駆動するか

注文確定をどのイベントで駆動するかは、決済Webhook実装で最も判断が要る論点です。Stripe Checkoutを使う構成では、注文確定(在庫引当・出荷指示・完了メール)はcheckout.session.completedを起点にするのが基本の型になります。このイベントはチェックアウトの完了を表し、注文と紐づくメタ情報を持つため、業務側の処理に直結させやすいからです。一方のpayment_intent.succeededは課金がクリアされた低レベルのイベントで、Checkout構成では課金明細の記録などの補助的な用途に留めます。両方のイベントで注文確定を走らせると、同じ注文を二度処理してしまうため、確定の駆動点は一つのイベントに寄せる設計にします。

決済Webhookの実装で外せない三点|署名検証・冪等性・即時応答

決済Webhookの事故は、正常系ではなく検証漏れや重複処理で起きます。署名検証・冪等性・即時応答の三点は、実装時に必ず織り込む前提です。

署名検証で偽の決済通知による不正な入金確定の処理が走るのを止める

Webhookの受信エンドポイントは公開URLなので、URLさえ知っていれば誰でもPOSTを送れます。署名検証を省くと、偽の「支払い成功」通知を送り込まれ、入金がないのに注文が確定してしまう恐れがあるからです。Stripeは各リクエストにStripe-Signatureヘッダーを付け、Webhook署名シークレットと組でHMAC-SHA256の署名を計算します。受信側はstripe.webhooks.constructEventなどで署名を検証し、一致しないリクエストは処理せず拒否する実装にします。署名検証の具体的な組み方はWebhook共通の機構のためWebhookの実装方法の署名検証と同じ考え方で、決済では検証失敗を金銭事故の入口として厳格に扱う点が違いです。

冪等性でイベントの再送による売上の二重計上や在庫の二重引当を防ぐ

決済Webhookの配信は「少なくとも一度」を保証する方式で、ネットワークの都合やリトライで同じイベントが複数回届きます。冪等性を担保しないと、同じ支払いイベントを二度処理して売上を二重計上したり、在庫を二重に引き当てたりします。対策は、受信したイベントのID(Stripeならevt_で始まる一意なID)をデータベースに記録し、既に処理済みのIDならスキップして早期に2xxを返す設計です。処理と記録は同一トランザクションに収め、途中で落ちても中途半端に反映されないようにします。この冪等キーはオーソリのリトライ対策とも共通の考え方で、決済処理全体を通した設計の芯になります。

即時に2xxを返し、重い副作用は非同期のキューへ逃がす設計にする

Stripeは受信側が2xxを返さないと、決済イベントを指数的バックオフで数日間リトライし続ける挙動です。署名検証と冪等チェックを終えたら、まず2xxを返してPSPに受領を伝え、在庫引当・メール送信・基幹連携といった重い処理はキューに積んで非同期に実行するのが定石です。同期処理のまま外部APIやメール送信を待つと、タイムアウトで2xxが返らず、PSPが再送を繰り返して同じイベントが積み上がります。受信は軽く、副作用は非同期に、という分離が安定運用の土台です。

決済Webhookで崩れやすい論点|イベント順序とリトライの副作用

署名・冪等・即時応答を押さえても、イベントの到達順序と再送時の副作用でつまずくことがあります。ここは決済ならではの難所です。

イベントの到達順序の逆転を注文と決済の状態機械で吸収する設計

Webhookのイベントは、送信された順に届くとは限りません。payment_intent.succeededより先に後続のイベントが届くようなことも起こり得ます。順序を前提に「前のイベントが来ているはず」と決め打ちすると、片方が遅れただけで処理が破綻します。対策は、注文や決済の状態を状態機械(未払い→支払い済み→出荷済み→返金済みなど)として持ち、受信イベントで現在の状態から許される遷移だけを適用する設計です。想定外の順序で来たイベントは、現状態と突き合わせて無視するか保留に回すことで、順序の揺れを吸収できます。

リトライ時のメール送信など外向きの副作用の多重発火を確実に止める

冪等キーで処理本体の二重実行を防げても、副作用の設計が甘いと再送のたびに完了メールが何通も飛ぶ、といった二次被害が出ます。メール送信や外部通知のような外向きの副作用も、送信済みフラグをイベント単位で管理し、再送時に再発火させない設計が必要です。在庫引当や出荷指示も同様に、同じ注文への操作が一度きりになるよう冪等に組みます。決済本体・在庫・通知のそれぞれで「同じイベントが二度来ても結果が変わらない」状態を担保するのが、副作用の多重発火を止める勘所です。

ローカルとステージング環境での決済Webhookの検証の進め方

決済Webhookは本番のPSPからしか飛ばないため、ローカル開発では検証手段を用意しておく必要があります。StripeならCLIのstripe listenで受信イベントをローカルのエンドポイントへ転送し、stripe triggerpayment_intent.succeededなどを任意に発火させて挙動を確かめられる仕組みです。署名検証・冪等・順序の各分岐を、正常系だけでなく再送や失敗イベントを含めて試すことで、本番前に穴を洗い出せます。PAY.JPなど国内のPSPもイベント通知の仕組みとテスト手段を備えているため、採用するPSPのドキュメントで通知仕様とテスト方法を確認してから実装に入る段取りです。

決済Webhookの実装を内製と受託開発のどちらで進めるかの判断軸

ここからは判断を言い切ります。決済Webhookの受信は、要件が単純なら内製で回りますが、扱うイベントが増え副作用が業務に深く絡むほど、設計品質が事故率を左右するため体制の選び方が結果を分けます。

内製で完結できる決済Webhook受信の実装が現実的になる要件

次の条件に収まるなら、内製での実装が現実的です。決済がStripe Checkoutのような標準構成で、注文確定を単一のイベントで駆動でき、副作用が「注文ステータス更新」と「完了メール」程度に収まるケースです。この範囲であれば、PSPのSDKで署名検証を行い、イベントIDで冪等にし、即時2xxを返して非同期処理へ回す、という基本の型で組み切れます。まず受けるイベントを絞った標準構成で公開し、要件が増えてから受信イベントと副作用を足していくのが、工数を抑える進め方です。

受託開発でWebhook設計を固めるべき複雑な決済要件の局面

逆に、次の要件が絡むと、初期の設計品質がその後の事故率を大きく決めるため、決済設計の経験がある体制で固める価値が出ます。複数PSPのイベントを一つの注文状態へ集約する構成、オーソリと売上確定を分ける二段階決済とWebhookの連動、返金・チャージバック・定期課金の更新失敗までを状態機械で扱う設計、そして基幹システムへの売上・在庫データ連携などです。これらはイベント順序・冪等・副作用制御が密に絡み、後から作り替えるとデータ不整合のリスクが高い領域になります。こうした重い決済実装は決済・サブスクリプションシステムの受託開発で設計から相談でき、決済方式の選定そのものを迷う段階では決済システムとは?仕組み・種類と接続方式・自社構築の判断軸で全体像から整理するのが近道です。二段階決済のオーソリと売上確定の分離はオーソリ(決済オーソリゼーション)とは、Stripe側の決済実装の型はStripeとは?決済プラットフォームの仕組み・手数料・実装パターンと合わせて設計に落とせます。

決済Webhookでよくある質問|仕組み・実装・テストの疑問

決済Webhookの実装検討でよく挙がる質問に、仕組み・実装・テストの観点から簡潔に答えます。

決済Webhookと決済APIの同期レスポンスはどう使い分けますか?

決済APIの同期レスポンスは、決済リクエストへの即時の結果を画面に返すために使います。ただし利用者のブラウザ操作や通信状況に左右されるため、注文確定の正としては使いません。最終的な成否や返金・チャージバックはWebhookで受け取り、その通知を起点に注文確定や在庫引当を駆動する使い分けが基本です。

同じイベントが二度届いたときの二重処理はどう防ぎますか?

決済Webhookは少なくとも一度の配信で、同じイベントが再送されます。受信したイベントのID(Stripeなら evt_ で始まる一意なID)を記録し、処理済みならスキップして2xxを返す冪等な実装が対策です。処理と記録を同一トランザクションに収め、途中で落ちても中途半端に反映されない設計にすることで、売上の二重計上や在庫の二重引当を防げます。

署名検証はなぜ必要ですか?

Webhookの受信URLは公開されているため、署名検証がないと第三者が偽の「支払い成功」通知を送り込め、入金がないのに注文が確定する恐れがあります。StripeならStripe-Signatureヘッダーと署名シークレットでHMAC-SHA256の署名を検証し、一致しない通知は処理せず拒否するのが基本です。決済では検証失敗を金銭事故の入口として、必ず実装します。

注文確定はどのイベントで行うべきですか?

Stripe Checkoutを使う構成では、注文確定は checkout.session.completed を起点にするのが基本です。このイベントは決済フローの完了を表し、注文と紐づくメタ情報を保持するためです。payment_intent.succeeded は課金確定の低レベルイベントで、両方で注文確定すると二重処理になるため、確定の駆動点は一つのイベントに寄せます。

ローカル環境で決済Webhookをテストするには?

本番のPSPからしか通知は飛ばないため、Stripeでは CLI の stripe listen で受信イベントをローカルのエンドポイントへ転送し、stripe trigger で任意のイベントを発火させて検証します。正常系だけでなく、再送・失敗・順序の逆転といった分岐まで試すのが安全です。採用するPSPのドキュメントで通知仕様とテスト手段を確認してから実装に入ります。

関連記事

資料請求

RELATED POSTS 関連記事