Documentation Index
Fetch the complete documentation index at: https://docs.waffo.ai/llms.txt
Use this file to discover all available pages before exploring further.
概要
Webhook は、注文、決済、サブスクリプション、返金などのイベントが発生したとき、サーバーにリアルタイム通知を配信します。| チャネル | 配信先 | ペイロードフォーマット |
|---|---|---|
http | あなたの HTTPS エンドポイント | RSA-SHA256 署名付き JSON エンベロープ |
feishu | Lark / Feishu Bot 受信 Webhook | インタラクティブカード(Lark フォーマット) |
discord | Discord チャンネル Webhook | Embed メッセージ(Discord フォーマット) |
telegram | Telegram Bot sendMessage URL | HTML 形式のテキスト |
slack | Slack 受信 Webhook | mrkdwn フィールド付き Attachment |
http チャネルは本ページで説明する JSON エンベロープと署名検証を使用します — 本ガイドの大部分はこのチャネルが対象です。IM チャネルでは各プラットフォーム固有のフォーマットが使われ、認証は URL のトークンによって行われます。署名の検証は不要です。
セットアップ
チャネルを選択し URL を準備
- HTTP — POST リクエストを受け付け
200を返すサーバーエンドポイントを構築します。 - Feishu / Discord / Telegram / Slack — 対象プラットフォームで Bot または受信 Webhook を作成し、その URL をコピーします。Telegram の場合はメッセージを受信する chat ID も控えておきます。
(HTTP のみ)検証用公開鍵を取得
ダッシュボード → 設定 → Webhooks に移動し、対象環境(Test / Production)の Webhook Public Key をコピーします。
Webhook を登録
ダッシュボード → 設定 → Webhooks から追加するか、
POST /v1/actions/store/add-webhook を呼び出します。各 Webhook レコードは 1 つのチャネル、1 つの URL、購読イベント、対象環境(Test の場合は testMode: true、Production の場合は false)を指定します。1 つのストアに複数の Webhook を登録できます。環境の隔離
各 Webhook はtestMode フラグによって 1 つの環境に登録されます。Test と Production は完全に独立しています:
| 項目 | Test | Production |
|---|---|---|
| Webhook レコード | testMode: true | testMode: false |
| 署名鍵(HTTP のみ) | Test 鍵ペア | Production 鍵ペア |
| 検証用公開鍵(HTTP のみ) | ダッシュボード Test 鍵 | ダッシュボード Production 鍵 |
| 配信時のセレクタ | ヘッダー X-Environment: test | ヘッダー X-Environment: prod(または省略) |
mode フィールドはソース環境を示します: "test" または "prod"。
ペイロードフォーマット
ヘッダー
| ヘッダー | 説明 |
|---|---|
Content-Type | application/json |
X-Waffo-Signature | 署名文字列: t=<timestamp>,v1=<signature> |
X-Waffo-Event | イベントタイプ(例:order.completed) |
ボディ
トップレベルフィールド
| フィールド | 型 | 説明 |
|---|---|---|
id | string | イベントエンティティ ID — ほとんどのイベントで eventId と同じ |
timestamp | string | イベント時刻(ISO 8601 UTC) |
eventType | string | イベントタイプ(イベントタイプを参照) |
eventId | string | ビジネスイベント識別子 — イベントタイプごとに異なるエンティティにマッピング(eventId マッピングを参照) |
storeId | string | Store ID |
storeName | string | ストア名 |
mode | string | "test" または "prod" |
data フィールド
data オブジェクトには取引の詳細が含まれます。一部のフィールドは常に存在し、その他は特定のイベントタイプまたはデータが利用可能な場合にのみ出現します。
常に存在:
| フィールド | 型 | 説明 |
|---|---|---|
orderId | string | 注文 ID |
orderStatus | string | 注文ステータス(例:"completed"、"active"、"canceling") |
buyerEmail | string | 購入者のメールアドレス |
currency | string | ISO 4217 通貨コード(例:"USD"、"JPY") |
amount | string | 税込みの取引合計額(表示フォーマット、例:"29.00") |
taxAmount | string | 税額(表示フォーマット、例:"2.90") |
productName | string | 商品名 |
orderMetadata | object | チェックアウトセッションからの注文レベルメタデータ(マーチャント定義のキーバリューペア) |
productMetadata | object | 商品の作成/更新時に設定された商品レベルメタデータ |
| フィールド | 型 | 条件 | 説明 |
|---|---|---|---|
merchantProvidedBuyerIdentity | string | チェックアウト時に設定 | マーチャントのカスタム購入者識別子 |
billingDetail | object | チェックアウト時に設定 | 請求先住所(country、isBusiness など) |
taxRate | number | 税率が適用された場合 | 税率の小数値(例:0.1 は 10%) |
taxName | string | 税率が適用された場合 | 税名(例:"Consumption Tax") |
subtotal | string | 利用可能時 | 税抜き小計(表示フォーマット) |
total | string | 利用可能時 | 税込み合計(表示フォーマット) |
productDescription | string | 商品に設定済み | 商品説明 |
order.completed、subscription.payment_succeeded):
| フィールド | 型 | 説明 |
|---|---|---|
paymentId | string | 決済 ID |
paymentStatus | string | 決済ステータス("succeeded"、"failed") |
paymentMethod | string | 決済方法(例:"card") |
paymentLast4 | string | 決済手段の末尾 4 桁 |
paymentDate | string | 決済日(ISO 8601 日付、例:"2026-03-10") |
paymentFailureReason | string | 失敗理由(決済が失敗した場合) |
subscription.*):
| フィールド | 型 | 説明 |
|---|---|---|
billingPeriod | string | "weekly"、"monthly"、"quarterly"、"yearly" |
currentPeriodStart | string | 現在の請求期間の開始日(ISO 8601 日付) |
currentPeriodEnd | string | 現在の請求期間の終了日(ISO 8601 日付) |
canceledAt | string | キャンセルタイムスタンプ(ISO 8601、キャンセル中/キャンセル済みの場合に存在) |
refund.succeeded、refund.failed):
| フィールド | 型 | 説明 |
|---|---|---|
refundStatus | string | "succeeded" または "failed" |
refundReason | string | 返金理由 |
refundCreatedAt | string | 返金作成タイムスタンプ(ISO 8601) |
paymentId | string | 元の決済 ID |
paymentStatus | string | 元の決済ステータス |
paymentMethod | string | 元の決済方法 |
paymentLast4 | string | 元の決済の末尾 4 桁 |
paymentDate | string | 元の決済日 |
金額は表示フォーマット文字列であり、最小通貨単位からすでに変換されています。例えば、USD
"29.00" = 2900 セント、JPY "4500" = 4500 円です。利用可能な場合は subtotal と total を使用して明細表示できます。eventId マッピング
eventId はイベントをトリガーしたビジネスエンティティを識別します。
| イベントタイプ | eventId のマッピング先 | 例 |
|---|---|---|
order.completed | Payment ID | PAY_6eYCunG3IMmIgcQOnaXdoA |
subscription.activated | Order ID | ORD_5dXBtmF2HLlHfbPNm0Wcnz |
subscription.payment_succeeded | Payment ID | PAY_6eYCunG3IMmIgcQOnaXdoA |
subscription.canceling | Order ID | ORD_5dXBtmF2HLlHfbPNm0Wcnz |
subscription.uncanceled | Order ID | ORD_5dXBtmF2HLlHfbPNm0Wcnz |
subscription.updated | Order ID | ORD_5dXBtmF2HLlHfbPNm0Wcnz |
subscription.canceled | Order ID | ORD_5dXBtmF2HLlHfbPNm0Wcnz |
subscription.past_due | Order ID + 月 | ORD_5dXBtmF2HLlHfbPNm0Wcnz-2026-04 |
refund.succeeded | Refund ID | REF_4cWAtlE1GKkGebONl9Xbnx |
refund.failed | Refund ID | REF_4cWAtlE1GKkGebONl9Xbnx |
subscription.past_due は eventId に -YYYY-MM を付加します。同一サブスクリプションにつき、カレンダー月ごとに最大 1 回の past_due イベントがトリガーされます。翌月もまだ滞納中の場合、新しいイベントが発行されます。イベントタイプ
一覧
| イベント | トリガー | eventId |
|---|---|---|
order.completed | 単発注文の決済が成功 | Payment ID |
subscription.activated | サブスクリプションの初回決済が成功 | Order ID |
subscription.payment_succeeded | 更新決済が成功(初回以外) | Payment ID |
subscription.canceling | キャンセルがリクエストされた — 期間終了まで有効 | Order ID |
subscription.uncanceled | キャンセルが取り消された | Order ID |
subscription.updated | 商品が変更された(アップグレード/ダウングレード) | Order ID |
subscription.canceled | サブスクリプションが終了(期間終了) | Order ID |
subscription.past_due | 更新決済が失敗 | Order ID + 月 |
refund.succeeded | 返金が完了 | Refund ID |
refund.failed | 返金が失敗 | Refund ID |
subscription.uncanceled と subscription.updated のイベントテンプレートは準備済みで、対応する機能のリリース後に有効化されます。イベント詳細
order.completed
order.completed
トリガー: 単発注文の決済が初めて成功した時。ペイロード:
data.amount— 決済金額(税込み)data.orderId— 単発注文 ID
- デジタル商品の配信(ライセンスキー、ダウンロードリンク、アクティベーションコード)
- 注文管理システムの更新
- 購入者への確認送信(Waffo の組み込みメールを使用しない場合)
order.completed がトリガーされるのは 1 回のみです。返金は refund.succeeded / refund.failed で通知されます。subscription.activated
subscription.activated
トリガー: 新規サブスクリプションの初回決済が成功した時(
pending → active)。ペイロード:data.amount— 初回決済金額(税込み)data.productName— サブスクリプション商品名
- 加入者のアカウントをプロビジョニングしてアクセスを付与
- サブスクリプション開始日を記録
pending から active に遷移した時にのみ発行されます。以降の更新には subscription.payment_succeeded が使用されます。subscription.payment_succeeded
subscription.payment_succeeded
トリガー: 定期更新決済が成功した時(初回決済以外)。ペイロード:
data.amount— 今期の更新金額(税込み)data.orderId— サブスクリプション注文 ID
- サービス期間の延長
- この請求サイクルの請求書を生成
- サブスクリプションが以前
past_dueだった場合、フルアクセスを復元
subscription.canceling
subscription.canceling
トリガー: 購入者またはマーチャントがキャンセルをリクエスト。サブスクリプションは現在の支払い済み期間の終了まで有効です。推奨アクション:
- 「サブスクリプションは [日付] に終了します」通知を表示
- リテンションフロー(例:割引更新)を提供
- アクセスを取り消さないでください — 購入者は現在の期間分を支払い済みです
subscription.uncanceled がトリガーされます)。subscription.uncanceled
subscription.uncanceled
トリガー: 現在の期間が終了する前にキャンセルが取り消された時。推奨アクション:
- 「まもなく終了」通知を削除
- 自動更新ステータスを復元
subscription.updated
subscription.updated
トリガー: サブスクリプション商品が変更された時(アップグレードまたはダウングレード)。ペイロード:
data.productName— 変更後の新しい商品名data.amount— 新しい金額
- 購入者のアクセスレベルを更新(機能の追加/削除)
- 請求記録を更新
subscription.canceled
subscription.canceled
トリガー: サブスクリプションが終了 — 支払い済み期間が終了し、これ以上の更新は行われません。推奨アクション:
- アクセスを取り消す(または無料プランにダウングレード)
- 猶予期間中データを保持(購入者が再登録する場合に備えて)
- 「サブスクリプション終了」確認を送信
subscription.past_due
subscription.past_due
トリガー: 更新決済が失敗し、サブスクリプションが滞納状態に入った時。ペイロード:
data.amount— 今期の請求金額eventId— フォーマット:{orderId}-YYYY-MM(月次重複排除)
- 購入者に支払い方法の更新を通知
- オプションでサービスを制限(完全に取り消すのではなく機能を制限)
- すぐにアクセスを取り消さないでください — PSP が自動的に課金をリトライする場合があります
past_due イベント。翌月もまだ滞納中の場合、新しいイベントが発行されます。refund.succeeded
refund.succeeded
トリガー: 返金が完了し、資金が返還された時。ペイロード:
data.amount— 返金金額(税込み)data.orderId— 元の注文 ID
- 配信済みデジタル商品を取り消す(ライセンスの失効、ダウンロードの無効化)
- 注文ステータスを「返金済み」に更新
refund.failed
refund.failed
トリガー: 返金処理が失敗した時。推奨アクション:
- 手動確認のために障害をログに記録
- 商品を取り消さないでください(返金は完了していません)
サブスクリプションライフサイクル
| 終了状態 | 意味 | Webhook を発行? |
|---|---|---|
canceled | サブスクリプション終了(購入者/マーチャントによるキャンセルまたは滞納) | subscription.canceled |
closed | アクティブ化されなかった — 決済がタイムアウト | No |
expired | 定期サブスクリプションが自然に終了 | No |
署名検証
本番環境では必ず署名を検証してください。 検証なしでは、誰でもエンドポイントに偽造リクエストを送信できます。アルゴリズム
SDK の使用(推奨)
SDK は公開鍵を埋め込み、環境を自動検出し、フォーマットの正規化を処理します。手動検証
TypeScript SDK を使用しない場合は、署名検証を手動で実装してください。レスポンス要件
- 2xx ステータスコードを返してください(推奨:
200) - 10 秒以内に応答してください
- レスポンスボディは問いません
リトライポリシー
配信失敗時は指数バックオフで自動的にリトライされます。| 項目 | 詳細 |
|---|---|
| リトライ回数 | 最大 3 回(初回を含めて合計 4 回) |
| 戦略 | 指数バックオフ |
| タイムアウト | 非 2xx またはタイムアウトウィンドウ内にレスポンスなし |
| 最終失敗 | すべてのリトライが尽きた後、配信は failed としてマークされます |
配信ステータス
| ステータス | 説明 |
|---|---|
pending | 作成済み、配信待ちまたはリトライ中 |
success | 配信成功(サーバーが 2xx を返した) |
failed | すべてのリトライが尽きた |
重複の処理
ネットワークの問題により、同じイベントが複数回配信される場合があります。イベント処理が冪等であることを確認してください。 重複排除にはeventType + eventId の組み合わせ(システム内で一意制約あり)を使用してください。
同一ビジネスイベント(同一の
eventType + eventId)は 1 つの配信レコードのみを作成します — 重複することはありません。ただし、リトライにより単一の配信がエンドポイントに複数回到達する場合があります。ベストプラクティス
必ず署名を検証する
必ず署名を検証する
常に
X-Waffo-Signature を検証してください。検証なしでは、誰でもエンドポイントに偽造リクエストを送信できます。HTTPS を使用する
HTTPS を使用する
本番環境の Webhook URL は、転送中のデータを保護するために HTTPS を使用する必要があります。
即座にレスポンスし、非同期で処理する
即座にレスポンスし、非同期で処理する
すぐに
200 を返し、ビジネスロジックはバックグラウンドで処理してください。レスポンスが遅いと不要なリトライが発生します。eventType + eventId で重複排除する
eventType + eventId で重複排除する
eventType + eventId の組み合わせで重複排除してください。同じ配信が複数回処理されても副作用がないようにしてください。タイムスタンプを確認する
タイムスタンプを確認する
リプレイ攻撃を防ぐために、
t タイムスタンプが現在時刻から 5 分以内であることを確認してください。正しい環境の鍵を使用する
正しい環境の鍵を使用する
Test と Production は異なる鍵ペアを使用します。ペイロードの
mode フィールドに公開鍵を一致させてください。受信ペイロードをログに記録する
受信ペイロードをログに記録する
デバッグのために受信したペイロードを保存してください。ダッシュボードでも配信ログクエリを提供しています。
購読したすべてのイベントを処理する
購読したすべてのイベントを処理する
まだ必要でなくても、購読しているすべてのイベントに分岐を追加してください。未処理のイベントには
200 を返してください — エラーを返すと不要なリトライがトリガーされます。テスト
テストイベントの送信(推奨)
ダッシュボードの「テストイベントを送信」ボタンを使用して、実際のトランザクションをトリガーせずにテストイベントを送信します。テストイベントは固定のサンプルデータ(amount 0、taxAmount 0、product “[TEST] Webhook Verification”)を使用し、常に Test 鍵で署名されます。 10 種類すべてのイベントタイプがサポートされています — 各イベントをテストしてハンドラーを検証してください。Test モードを使用する
- ダッシュボードで Test 環境の Webhook URL とイベントを設定します
- Test モードで実際の操作を行います(注文の作成、決済の処理)
- イベントは Test 署名鍵を使用して Test Webhook URL に送信されます
ローカル開発
トンネルを使用してローカルサーバーを公開します。配信ログ
ダッシュボードで Webhook 配信履歴を確認できます。- ステータス: pending / success / failed
- HTTP ステータスコード: サーバーのレスポンスコード
- レスポンスボディ: サーバーのレスポンス(1000 文字に切り詰め)
- タイムスタンプ: 最終配信試行
FAQ
Webhook が受信できない
- ダッシュボードで Webhook URL が設定され、公開アクセス可能であることを確認してください
- 正しいイベントタイプを購読していることを確認してください
- 正しい環境(Test / Production)を使用していることを確認してください
- ファイアウォールが Waffo からのリクエストを許可していることを確認してください
- ダッシュボードの「テストイベントを送信」で問題を切り分けてください
署名検証が失敗する
- 正しい環境の公開鍵(Test vs Production)を使用していることを確認してください
- 生のリクエストボディを使用していることを確認してください — パースされた JSON オブジェクトではありません
- ミドルウェアやプロキシがリクエストボディを変更していないか確認してください
- 署名入力の形式が
${t}.${rawBody}(タイムスタンプ + ドット + 生のボディ)であることを確認してください - TypeScript を使用している場合は、@waffo/pancake-ts SDK に切り替えてください — 鍵の選択とフォーマットの正規化を自動的に処理します
重複イベントが受信される
これは通常のリトライ動作です。エンドポイントが非 2xx を返したかタイムアウトした場合、システムがリトライします。ハンドラーが冪等であることを確認してください — 重複排除にはeventType + eventId の組み合わせを使用してください。
subscription.canceling と subscription.canceled の違い
canceling: キャンセルがリクエストされましたが、現在の支払い済み期間がまだ終了していません。サブスクリプションはまだ有効で、購入者はキャンセルを取り消すことができます(uncanceledがトリガーされます)。アクセスを取り消さないでください。canceled: サブスクリプションは終了しています。これは不可逆です — アクセスを取り消すか権限をダウングレードしてください。
各イベントの data.amount の意味
すべてのイベント: data.amount はそのイベントに対する取引金額(税込み)です。
order.completed/subscription.activated— 決済金額subscription.payment_succeeded— 今期の更新金額subscription.past_due— 今期の請求金額refund.succeeded/refund.failed— 返金金額subscription.canceling/subscription.canceled/subscription.uncanceled— サブスクリプションの期間あたりの金額