開発

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

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

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

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

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

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

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

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

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

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

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

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

スナップショットイベントとThinイベントの違いとAPIバージョン固定

Webhookのペイロード形式には二系統があります。従来のスナップショットイベントは、リソースオブジェクトの全体をペイロードに載せて届ける方式です。追加のAPI呼び出しなしで内容を読める代わりに、APIバージョンを上げるとオブジェクトの形が変わり、ハンドラの改修が要ります。もう一方のThinイベントは、コンパクトな通知だけを受け取り、詳細は受信後に自分で取得する方式です。StripeはThinイベントへの移行ガイドで、スナップショットイベントのThin版(例:customer.createdに対するv1.customer.created)を用意し、移行期は両方のハンドラを並走させてsnapshot_eventフィールドを冪等キーに使う手順を示しています。2026年9月時点でAPI v1リソースのThinイベントはプライベートプレビューのため、既存の決済実装はスナップショットイベントを前提にしつつ、受信するAPIバージョンをエンドポイント側で固定しておく進め方が現実的です。

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

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

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

Stripeを例にすると、実装でよく扱うイベントは次のとおりです。payment_intent.succeededは課金が確定したこと、payment_intent.payment_failedは決済の失敗を表します。checkout.session.completedはStripe Checkoutの決済フローが完了したことを示す高レベルのイベントで、自分で付けたメタ情報(注文IDなど)を保持する種別です。返金はcharge.refunded、チャージバック(異議申立)はcharge.dispute.createdで届き、定期課金ではinvoice.paidや請求失敗のイベントが更新のたびに飛びます。受信対象を絞り込むときはイベントタイプの公式一覧で正確な名称と発生条件を引き当ててから登録する手順です。PSPによってイベント名の体系は異なり、payment.capturedやpayment.expiredを使う例はKOMOJUのWebhookイベントと署名検証の実装で確認できます。まず自社の業務で反応すべきイベントを洗い出し、受信するイベント種別を絞り込むところから設計を始めます。

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

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

PSPごとに異なる署名ヘッダーとリトライ回数とイベントIDの比較

決済Webhookの実装は、PSPを変えるとそのまま移植できません。正当性の確認方法も再送の仕様も別物だからです。国内案件でよく候補に挙がる三社を、公式ドキュメントの記載で並べます。

PSP 正当性の確認 方式 識別子
Stripe Stripe-Signature HMAC-SHA256の署名 evt_で始まるID
PAY.JP X-Payjp-Webhook-Token 固定トークンの照合 evnt_で始まるID
KOMOJU X-Komoju-Signature SHA-2のHMAC X-Komoju-ID

再送の設計思想も揃っていません。Stripeはエンドポイントが一時的に処理できなかったイベントを最大3日間にわたり自動で再送します。対してPAY.JPのWebhook仕様は、4xxや5xxが返った場合のリトライを3分間隔・最大3回と定めており、レスポンスのHTTPステータスは200である必要があります。つまりPAY.JP側では、受信側が9分ほど落ちていればイベントは落ちる仕様です。この差はそのまま復旧設計の差になり、PAY.JPでは定期的なイベント走査による突き合わせを併走させる判断が要ります。KOMOJUはWebhooks仕様のとおり、登録直後にpingイベントを送るため、疎通確認をダッシュボードの操作だけで済ませられます。

作業手順|署名検証と冪等処理を備えた決済Webhook受信の実装

ここからは手を動かす手順です。ローカルへイベントを流し込み、署名を検証し、イベントIDで冪等にするところまでを、そのまま動かせる形で並べます。

Stripe CLIでイベントをローカルへ転送して受信処理を動かす手順

決済Webhookは本番のPSPからしか飛ばないため、まず手元へイベントを転送する経路を作ります。Stripe CLIのlistenはローカルのエンドポイントへイベントを中継し、triggerは任意のイベントを発火させるコマンドです。listenを起動するとwhsec_で始まる署名シークレットが表示されるので、これを環境変数に入れて次の署名検証で使います。

stripe login
stripe listen --forward-to localhost:4242/webhook
export STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxx
stripe trigger checkout.session.completed
stripe trigger payment_intent.succeeded
stripe trigger charge.refunded

コマンドの詳細はStripe CLIのイベント受信リファレンスにあります。正常系だけを流して満足せず、同じtriggerを二度叩いて冪等が効くか、受信側を落とした状態で発火させて再送が来るかまで見ておくと、本番前に穴が減ります。

生ボディとStripe-Signatureで署名を検証するハンドラの実装

署名検証で最もつまずくのは、フレームワークがJSONを自動パースしてしまい、生のリクエストボディが失われる箇所です。StripeのドキュメントもWebhookの構築手順で、生のリクエスト本文に手を加えないよう注意しています。Expressならexpress.rawをこのルートにだけ適用し、constructEventへ生ボディ・署名ヘッダー・エンドポイントシークレットの三つを渡します。

const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const app = express();
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;

app.post('/webhook', express.raw({ type: 'application/json' }), async function (req, res) {
  let event;
  try {
    event = stripe.webhooks.constructEvent(
      req.body,
      req.headers['stripe-signature'],
      endpointSecret
    );
  } catch (err) {
    return res.status(400).send('signature verification failed');
  }

  const isFirst = await markEventSeen(event.id, event.type);
  res.status(200).json({ received: true });

  if (isFirst) {
    await enqueue(event.type, event.id, event.data.object);
  }
});

app.listen(4242);

引数の意味は署名確認エラーの解決ガイドにまとまっており、検証に失敗したら400を返して処理を打ち切ります。注目してほしいのは順序です。署名検証と冪等記録を終えた直後に200を返し、業務処理はキューへ逃がしています。

イベントIDを一意キーにして冪等に処理を記録するテーブルの設計

冪等の実体は、イベントIDに一意制約を張ったテーブル一つで足ります。挿入できたら初回、衝突したら再送と判定する形です。判定と記録を同じトランザクションに収めることで、二つのワーカーが同じイベントを同時に受けても、処理が走るのは片方だけになります。

CREATE TABLE webhook_events (
  event_id     VARCHAR(64) PRIMARY KEY,
  event_type   VARCHAR(64) NOT NULL,
  received_at  TIMESTAMP   NOT NULL DEFAULT CURRENT_TIMESTAMP,
  processed_at TIMESTAMP   NULL
);

BEGIN;
INSERT INTO webhook_events (event_id, event_type)
VALUES ('evt_3QxxxxxxxxxxxxxxDummy', 'checkout.session.completed')
ON CONFLICT (event_id) DO NOTHING;
COMMIT;

影響行数が1なら初回、0なら再送です。PAY.JPならevnt_で始まるIDを、KOMOJUならX-Komoju-IDヘッダーの値を同じ列に入れれば、PSPを跨いでも同じ判定ロジックで済みます。注文テーブル側にも「この注文を確定したイベントID」を持たせておくと、後から事故を追跡するときの手がかりになります。

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

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

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

Webhookの受信エンドポイントは公開URLなので、URLさえ知っていれば誰でもPOSTを送れます。署名検証を省けば、偽の「支払い成功」通知で入金のない注文が確定してしまう。この一点が、決済で検証を必須にする理由です。Stripeは各リクエストにStripe-Signatureヘッダーを付け、Webhook署名シークレットと組でHMAC-SHA256の署名を計算しています。PAY.JPのように署名ではなくアカウント固有トークンの照合で正当性を確かめる方式もあるため、採用するPSPの方式を先に確認する順序になります。署名検証の具体的な組み方はWebhook共通の機構のためWebhookの実装方法の署名検証と同じ考え方で、決済では検証失敗を金銭事故の入口として厳格に扱う点が違いです。

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

決済Webhookの配信は「少なくとも一度」を保証する方式で、ネットワークの都合やリトライで同じイベントが複数回届きます。冪等性を担保しないと、同じ支払いイベントを二度処理して売上を二重計上したり、在庫を二重に引き当てたりする結果になる。対策は前章のテーブルのとおりで、処理と記録を同一トランザクションに収めるところまでが一組です。この冪等キーはオーソリのリトライ対策とも共通の考え方で、決済処理全体を通した設計の芯になります。API呼び出し側で付けるIdempotency-Keyの生成と再試行の作法はStripe APIの使い方:PaymentIntentの作成・APIキー認証・冪等キーをNode.jsとcurlで実装で解説しています。

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

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

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

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

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

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

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

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

未配信イベントをList Events APIで取り直して欠損を埋める復旧手順

受信側の障害が長引くと、再送の窓を過ぎたイベントは戻ってきません。Stripeは過去30日分のイベントを保持しており、delivery_success=falseを指定すれば、いずれかのエンドポイントへ配信できなかったイベントだけを取り出せます。障害の直前に届いた最後のイベントIDをending_beforeに渡し、欠損区間を手動で流し込む段取りです。

curl -G https://api.stripe.com/v1/events \
  -u sk_test_xxxxxxxxxxxxxxxx: \
  -d limit=100 \
  -d delivery_success=false \
  -d "types[]=checkout.session.completed" \
  -d ending_before=evt_3QxxxxxxxxxxxxxxDummy

手順の詳細は未配信イベントの処理ガイドに記載されています。取り出したイベントは、通常の受信経路と同じ冪等テーブルを通して流し込む手順です。前章の設計ができていれば、既に処理済みのイベントは自動的にスキップされ、欠損分だけが反映されます。障害復旧の手数は、平時の冪等設計にそのまま比例します。

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

ステージングでは、正常系に加えて異常系の分岐を意図的に踏ませます。署名シークレットをわざと誤った値にして400が返るか、同一イベントを二度流して冪等テーブルが弾くか、処理を遅延させて再送が来るか。この三つを踏んでおけば、本番で慌てる場面はかなり減ります。PAY.JPやKOMOJUのように管理画面からテスト送信やpingを打てるPSPなら、疎通確認はそちらが早い経路です。採用するPSPのドキュメントで通知仕様とテスト手段を確認してから実装に入る段取りにします。stripe triggerの合成イベントではなく、拒否・3Dセキュア・不審請求といった実際の決済結果からイベントを発生させたいときは、Stripeテストカード一覧と使い方:3Dセキュア・決済拒否・不審請求をテストする手順のテストカードで決済を通す手順が使えます。stripe listenやstripe triggerなど、ローカル検証で打つStripe CLIの具体的なコマンドはStripe CLIの使い方:インストールからstripe listen・triggerでのWebhookローカル検証までで解説しています。

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

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

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

次の条件に収まるなら、内製での実装が現実的です。決済がStripe Checkoutのような標準構成で、注文確定を単一のイベントで駆動でき、副作用が「注文ステータス更新」と「完了メール」程度に収まるケース。この範囲なら、本記事の作業手順の章に載せたコードがほぼそのまま骨格になります。まず受けるイベントを絞った標準構成で公開し、要件が増えてから受信イベントと副作用を足していくのが、工数を抑える進め方です。

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

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

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

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

そもそもWebhookとは何で、決済Webhookとはどこが違いますか?

Webhookは、あるサービスでイベントが起きたときに、指定したURLへHTTP POSTで通知を送る仕組み全般を指します。SlackのIncoming WebhookもGitHubのプッシュ通知も同じ骨格です。決済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のドキュメントで通知仕様とテスト手段を確認してから実装に入ります。

Webhookが届かなかった分はあとから回収できますか?

Stripeなら回収できます。イベントは過去30日分が保持され、List Events APIに delivery_success=false を指定すると配信に失敗したイベントだけを取り出せるためです。ただし再送の窓はPSPごとに違い、PAY.JPのリトライは3分間隔・最大3回と短いので、こちらは定期的なイベント走査による突き合わせを別途組み込む前提で設計します。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

ほか 14 件の記事からもリンクされています。

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.23 テックブログ Renta!不正アクセスでサービス停止:再開未定の現状と運営側の停止判断・証拠保全・段階的再開
  2. 2025.01.09 コラム アドレサブルCM(Addressable CM)とは?フジテレビで出る表示の意味・仕組みと視聴データの扱い
  3. 2026.07.09 テックブログ Muse Imageとは?Metaの画像生成AIの使い方・API料金(1枚0.01ドル)と日本での提供状況【2026年10月】
  4. 2025.12.10 コラム 今の50代は何世代?バブル世代・団塊ジュニア・氷河期の境目と特徴
  5. 2026.10.21 テックブログ ふるさとチョイス不正アクセスで445万件流出:パスワード変更・対象確認と再開時期(10月23日第2報まで)

RELATED POSTS 関連記事

目次