正当な決済通知であっても、同じ注文を二度履行してはいけません。 暗号資産決済のWebhookには、独立した二つの対策が必要です。まずイベントの送信元を検証し、次に繰り返し処理しても業務上の効果が重複しないようにします。署名が答えるのは前者、冪等性が答えるのは後者です。
本ガイドは、デジタル商品の単発購入、ライセンス、アカウントへの残高チャージをYolfiと連携する開発者向けです。標準のWebhook形式と推奨するアプリケーション設計を説明するもので、完成済みの注文履行サービスではありません。サブスクリプションでは利用権が契約のライフサイクルにも左右されるため、単発注文のルールをすべての更新に流用しないでください。
正当なWebhookでも重複が起こる理由
受信側がイベントを正常に保存していても、HTTPレスポンスが失われることがあります。その場合、送信側は再試行します。また、アクセス権を付与した後、完了を記録する前にワーカーが停止する可能性もあります。どちらも、顧客が二度目の支払いをしたことを意味しません。
YolfiのWebhookイベントリファレンスには、少なくとも一度の配信と、複数のエンドポイントそれぞれで独立した再試行が説明されています。最初の配信を処理している最中も含め、同じイベントが再び届く前提で設計してください。特定の再試行スケジュールに依存したり、通知が必ず作成順にアプリケーションへ届くと想定したりしないでください。
次の三つの識別子を分けて扱います。
| 識別子 | 識別する対象 | 推奨する制御 |
|---|---|---|
署名されたイベントの id |
繰り返しの配信を含む、決済事業者の一つのイベント | 決済事業者、組織、環境の範囲内で一意のイベントレコードを保持 |
| 請求書または支払いの参照値 | 一回の支払い試行 | 対象の商取引上の注文との対応関係を保存 |
| 業務上の注文参照値 | 顧客が受け取るべきもの | その注文に対する履行処理の一意性を保証 |
イベントの重複排除だけでは不十分です。同じ注文に対して成功した二つの支払い試行が、異なるイベントIDを持つ場合があります。注文単位の保護により、追加の入金を別途確認している間も、ライセンスや残高の二重付与を防げます。
フィールドを信頼する前に元のリクエストを検証する
最新のYolfi署名手順に従ってください。標準のリクエストでは、エンドポイントの署名用シークレットを使い、元のリクエスト本文に対して計算したHMAC-SHA256をBase64でエンコードした X-Yolfi-Signature を使用します。組織のAPIキーはWebhookのシークレットではありません。
JSONミドルウェアが本文を変更する前に、元の本文を取得します。署名を計算し、定時間比較関数で照合してください。署名がない場合や形式が不正な場合は、業務処理を実行せずに拒否します。署名用シークレットはシークレット管理サービスや保護された環境設定に保存し、ソースファイルや問い合わせ対応のログには残さないでください。
検証後に初めてペイロードを解析し、内容を検証します。重複排除には、署名されたペイロードの id を使います。X-Yolfi-Event-ID は便宜上のコピーであり、個別に署名された値ではありません。使用する場合はペイロードのIDとの一致を必須にしてください。受信エンドポイントを想定する組織に紐づけ、orgId が存在する場合は検証します。ユーザーが指定したメタデータでテナントの認可を決めてはいけません。
有効な署名があっても、過去の配信が新しいものになるわけではありません。処理済みイベントIDの保持は引き続き必要です。Yolfiの文書にある署名は、タイムスタンプに基づく鮮度確認ではないため、HMAC検証だけでリプレイ攻撃を防げるとは説明しないでください。
先に保存し、受領を応答してから非同期で処理する
永続的な受信箱を使います。認証済みのイベントと処理状態を記録するアプリケーションのテーブルです。イベントの挿入にはデータベースの一意性制約を使ってください。「存在するか」を別に確認してから、保護されていない挿入を行う方式ではいけません。並行したリクエストが両方とも確認を通過する可能性があります。
永続的に受け入れた後は、速やかに 200 OK を返します。認証済みの重複イベントがすでに保存されている場合も同様です。永続化に失敗したら、受け入れたという応答を返してはいけません。すぐ応答してからメモリ上のタスクを動かすだけでは永続性はありません。再起動によって、アプリケーションが保持していた唯一のコピーを失う可能性があります。
その後、ワーカーが保存済みイベントを読み込み、注文との対応関係を検証し、業務状態を遷移させます。再試行回数、次の対応、アラート担当者を明確にし、失敗した処理を追跡可能にしてください。イベント本文には顧客情報が含まれる場合があるため、必要なペイロードデータだけをアクセス制限された保存先に保持します。
決済の確認と正しい注文への対応付けを履行条件にする
Yolfi Webhookガイドでは、注文履行の主なトリガーとして payment.confirmed を挙げています。invoice.created は請求書が作成されたという意味であり、顧客が支払ったという意味ではありません。ブラウザーのリダイレクトやスクリーンショットは、検証済みのサーバー側決済確認の代わりにはなりません。
イベントをサーバーに保存済みの注文へ対応付けます。標準フローでは、単独のAPI Checkoutイベントに checkoutSessionId と merchantOrderId が含まれ、paylinkId は含まれない場合があります。Paylink IDがないだけで、有効な単独購入を拒否しないでください。同様に、再利用可能なPaylink IDだけでは購入者の注文を一意に特定できません。
提供を許可する前に、想定する組織、環境、支払い試行、取引金額の前提を確認します。文書の amount は、決済トークン建てで加盟店が最終的に受け取る金額です。現地通貨建ての元の請求総額と自動的に一致するものではありません。この二つの数値を同じ単位として比較しないでください。サーバーで当初承認した注文と、その価格設定の前提を保持します。
動的な購入では、API Checkoutフローを使って注文とセッションの対応関係を保持します。固定商品では、決済リンクが再利用可能な決済の入口になりますが、提供するかどうかを判断するのは引き続きアプリケーションです。
イベントテーブルだけでなく実際の業務処理を保護する
注文の履行がデータベース内で完結する場合は、イベントの処理結果と注文の状態遷移を一つのトランザクションで記録します。単発注文につき一度の利用権付与など、業務処理の一意性を強制してください。重複イベントでは既存の結果を返し、利用権を追加で付与してはいけません。
履行のために別のサービスを呼び出す場合は、永続的な送信箱を使います。実行予定の操作を注文の状態遷移と一緒に保存し、その後ワーカーに配信させます。相手側が対応している場合は、後続の操作に、注文を基準とした安定した冪等性キーを付けてください。外部での操作が成功した後にワーカーが停止すると、送信箱だけでは外部での重複実行を防げません。
送信先が冪等性にも結果照会にも対応していない場合は、取り消せない操作を無条件に再試行せず、結果が不確かなものとして照合対象に残してください。HTTP Webhookが、あらゆるデータベース、キュー、外部サービスをまたいで「厳密に一度」を保証することはできません。
古いイベントによって、完了済みの注文が以前の状態に戻らないようにします。invoice.overdue は一回の支払い試行に関するイベントであり、履行済みという事実を取り消してはいけません。継続的な利用権では、決済事業者は請求書イベントと subscription.overdue、subscription.cancelled を区別しています。文書にあるサブスクリプションの方針を別途実装してください。
本番の注文を受ける前に異常系をテストする
受信処理とワーカーに対して、次の受け入れテストを実施してください。
- 同じ署名済みイベントを二度配信し、同時配信も試す。保存されるイベントは一つ、業務上の効果も一度だけにする。
- 注文の状態遷移の前後でワーカーを中断する。復旧時にアクセス権を二重付与しない。
- 一つの注文に対して、確認済みの支払い試行を二件配信する。履行は一度にし、追加の入金に印を付ける。
- 不正な署名、組織の不一致、不明な注文を送る。いずれも履行しない。
- 決済確認後に、それより古い請求書イベントを配信する。完了済みの履行を巻き戻さない。
- テストモードのイベントを本番の受信エンドポイントへ送る。実際のライセンス発行、発送、アカウントへの残高付与を行わない。
Yolfi管理画面のテストイベントは livemode: false と、evt_test_ で始まるIDを使います。配信やペイロード処理を検証できますが、実際の決済レコードは作成されません。テストと本番の業務効果を明示的に分けてください。テスト通知の成功は、購入代金の決済が完了した証拠にはなりません。
認証済みで処理待ちのイベント、ワーカーの失敗、決済確認済みなのに履行結果がないものを監視します。こうした例外を決済照合チェックリストと結び付けてください。二件目の入金が再試行ではなく実際の追加支払いなら、証拠を削除せず返金審査手順に従います。
よくある質問
署名の検証で残高の二重付与を防げますか?
いいえ。署名が検証するのは真正性と完全性です。正当なイベントでも複数回届くことがあり、過去に有効だったリクエストが再送されることもあります。イベントIDを永続化し、注文単位の業務制約を適用してください。
イベントを保存する前に成功を返すべきですか?
いいえ。永続的に受け入れてから受領を応答し、時間のかかる処理は非同期で実行してください。そうしないと、応答後の障害によって気付かないうちにイベントを失う可能性があります。
トランザクションハッシュだけで重複排除キーとして十分ですか?
Webhook処理には不十分です。イベント配信には文書にある署名済みイベントIDを使い、履行には自社の注文キーを使います。送金と注文は異なる識別対象であり、一つのトランザクションに複数の送金が含まれる場合があります。
アダプターのペイロードにも同じハンドラーを使えますか?
インターフェースの仕様を確認せずに流用することはできません。本記事で説明しているのは、アダプターが NONE の標準形式です。決済事業者との互換アダプターはペイロード構造が異なる場合があるため、標準の解析・対応付けルールを再利用する前に、該当する文書のフィールドを検証してください。
結論:確認し、永続化し、一度だけ履行する
信頼できる暗号資産決済の自動化では、認証済みイベントの配信と注文の業務上の効果を分離します。元のリクエストの署名を検証し、イベントを永続化し、注文を照合して、再試行しても安全に履行できるようにしてください。固定商品ではYolfi決済リンクから始め、動的な注文では文書に記載されたサーバー生成セッションを利用し、どちらでも購入ごとに一度だけ提供する責任はアプリケーションに持たせます。
