<!-- Source: https://docs.infraio.xyz/ja/webhooks/overview -->
<!-- Last updated: 2026-10-04 -->

# Webhook — 概要

Webhook は **信頼できる** シグナルです。ブラウザのコールバック
(`onSuccess`) やダッシュボードの表示は補助的な情報であり、
Webhook が真実のソースです。

## 配信保証

- **少なくとも 1 回。** サーバーがタイムアウト内に 2xx を返さない場合、
  1 つのイベントは最大 **6 回** 配信される可能性があります。
  ハンドラは冪等にしてください — `X-Delivery` で重複排除を行います。
- **HTTP リクエストごとに 1 イベント。** バッチ配信は行いません。
- **エンドポイント単位の独立性。** 複数のエンドポイントを登録している場合、
  それぞれが独立した配信とリトライのトラックを持ちます。
  あるエンドポイントが遅くても他には影響しません。
- **署名付き。** すべてのペイロードに `X-Signature` ヘッダーが付きます
  (シークレットローテーション後の 24 時間ウィンドウ中は
  `X-Signature-Prev` も付きます)。本文を扱う前に必ず検証してください。
  詳しくは [署名検証](https://docs.infraio.xyz/ja/webhooks/signature-verification) をご覧ください。

## サブスクライブ可能なイベント種別

| イベント | 発火条件 |
| --- | --- |
| `payment.settled` | オンチェーン送金がチェーンの確認カウントを満たしました。**注文を支払い済みにする際に使用してください。** |
| `payment.failed` | 法定通貨決済が決済プロバイダにより拒否された場合。暗号資産のタイムアウトでは発火しません — それらは代わりに `checkout.expired` として表面化し、短額の暗号資産決済は `payment.underpaid` として表面化します。 |
| `payment.underpaid` | 資金は届いたものの注文合計に満たない場合(典型例: ステーブルコインの送金手数料が金額から差し引かれた)。 |
| `payment.overpaid` | 注文合計を超える資金が届いた場合。余剰分は記録されますが自動返金はされません。 |
| `order.created` | 新しい注文が作成されました — B2B API 呼び出しまたはチェックアウトセッションからの変換。 |
| `order.canceled` | 注文がキャンセルされました。ペイロードの `data.reason` で手動キャンセルと `payment_timeout` (未払いの注文がタイムアウトした場合) を区別します。 |
| `order.resolved` | `PARTIAL_PAID` の注文が `PAID` へ解決されました — マーチャントが不足分を許容しました。 |
| `order.reopened` | 以前に自動キャンセルされた注文 (`canceled_reason=payment_timeout`) がマーチャントにより再オープンされました。 |
| `checkout.created` | バイヤーが注文のチェックアウトを開きました。 |
| `checkout.completed` | バイヤー側の操作が完了 (オンチェーン決済の確定を意味しません — それは `payment.settled` で判定)。 |
| `checkout.expired` | バイヤーが離脱し、セッション TTL が切れました。 |
| `payment.refund.requested` | 返金レコードが作成されました — マーチャント開始の API 呼び出し、または顧客提出の返金リクエストフォーム経由。 |
| `payment.refund.approved` | 保留中の返金が承認ワークフローを通過しました。 |
| `payment.refund.rejected` | 保留中の返金が却下されました。 |
| `payment.refund.executed` | 返金のオンチェーン送金が確定し、レコードが最終状態 `executed` に移行しました。 |
| `refund_request.created` | 返金リクエストトークンが発行されました。`data.source` は `b2b` / `dashboard` / `renewal`。任意のサブスクライブ — 注文ごとにどのトークンが現在有効かを追跡する監査パイプラインに便利。 |
| `refund_request.renewal_requested` | トークン期限切れ後にバイヤーが「新しいリンクを要求」をクリック。**サブスクライブを強く推奨** — 再発行ウィジェットに新しい対応項目が来たというマーチャントへの合図です。 |
| `refund_request.renewed` | 再発行が承認され、新しいトークンが古いものを置き換えました。`data.old_token` / `data.new_token` が監査チェーンを形成します。 |
| `refund_request.canceled` | マーチャントがダッシュボードからトークンを `CANCELED` に遷移させました (例: 再発行リクエスト拒否、live リンクの停止)。冪等 — 最初の遷移のみ発火。`data.reason` は任意のマーチャントノート。 |

### 予定（近日公開）

> **Note:**
>
> **近日公開。** これらのイベントは定期請求書とサブスクリプションに関するもので、
> まだご利用いただけません。上記のサブスクライブ可能なテーブルには**含まれておらず**、
> 現時点ではサブスクライブできません。[定期請求書](https://docs.infraio.xyz/ja/guides/recurring-invoices) を参照してください。

| 予定のイベント | 発火のタイミング |
| --- | --- |
| `subscription.created` | サブスクリプションが作成されたとき。 |
| `invoice.created` | 請求サイクルの請求書が作成されたとき。 |
| `invoice.paid` | 請求書が支払われたとき。 |
| `subscription.past_due` | 請求書が支払期限を過ぎても未払いのとき。 |
| `subscription.canceled` | サブスクリプションがキャンセルされたとき。 |

ダッシュボードのエンドポイントフォームにも同じイベントが一覧表示されます。
存在しないイベントへのサブスクライブは、エンドポイントの保存時に拒否されます。

> **Note:**
>
> **テストイベントはサブスクライブできません。** ダッシュボードの
> エンドポイント単位の **Send Test** ボタンは、`webhook.test.ping`
> イベントをそのエンドポイント 1 つだけに、リトライなしで即座に送信します。
> 上のカタログには現れません — サブスクライブしたからではなく、エンドポイントを
> 登録しているから受信します。

> **Note:**
>
> ハンドルするイベントだけにサブスクライブしてください。各エンドポイントは
> 独自のイベントフィルタを持ちます; ワイルドカード `"*"` は
> 「将来追加されるものを含む全イベント」を意味します。
> サブスクライブするイベントを絞ることでハンドラがシンプルになり、
> エンドポイントでエラーが起きたときのリトライも減ります。

## ペイロード + ヘッダー

**HTTP ボディはイベント固有のデータオブジェクトそのものです。** Stripe スタイル
の外側エンベロープはありません — イベント種別、配信 ID、発行タイムスタンプ
などのフィールドは代わりに **ヘッダー** に入ります。`payment.settled` の
ボディは次のような形です:

```json
{
  "receipt_id":        "rcp_…",
  "order_id":          "ord_…",
  "payment_intent_id": "pin_…",
  "checkout_session_id": "cst_…",
  "merchant_id":       "mer_…",
  "customer_id":       "cus_…",
  "total":             "49.00",
  "currency":          "USD",
  "payment_method":    "crypto",
  "token":             "USDC",
  "network":           "polygon",
  "tx_hash":           "0x…",
  "deposit_address":   "0x…",
  "treasury_address":  "0x…",
  "amount_received":   "49.00",
  "confirmations":     5,
  "metadata":          { /* per-event */ }
}
```

他のイベントは独自のフィールドを持ちます。フィールド名は安定 (lower
snake_case); オンチェーンのトランザクションハッシュは常に `tx_hash` です。

`tx_hash` はネットワーク固有の形式のトランザクション識別子です（EVM チェーンでは `0x…`、TRON・Solana・TON ではネイティブのハッシュまたは署名）。TRON、Solana、TON では購入者がマーチャントのトレジャリーウォレットへ直接支払うため、`deposit_address` が含まれない場合があります。`confirmations` は [対応チェーン・資産](https://docs.infraio.xyz/ja/concepts/chains) に従います。

### 受信リクエストのヘッダー

```http
Content-Type:      application/json
X-Event:           payment.settled
X-Delivery:        7c9e6679-7425-40de-944b-e07fc1f90ae7
Idempotency-Key:   7c9e6679-7425-40de-944b-e07fc1f90ae7
X-Timestamp:       1729536000
X-Signature:       sha256=9a8b7c…
X-Signature-Prev:  sha256=fa31b2…    (ローテーション猶予ウィンドウ中のみ)
```

| ヘッダー | 内容 |
| --- | --- |
| `X-Event` | イベント種別 (例: `payment.settled`)。JSON をパースせずに済ませたい場合、プロキシ層でこれにルーティングしてください。 |
| `X-Delivery` | 配信行を識別する UUID。同じ `(event, endpoint)` ペアの **すべてのリトライをまたいで安定** — 冪等キーとして使ってください。 |
| `Idempotency-Key` | `X-Delivery` のミラー (同じ値)。すべての配信で設定されます。 |
| `X-Timestamp` | 試行が送信された Unix 秒。ペイロードに含めて署名するので、捕捉された `(body, X-Signature)` ペアを永続的にリプレイすることはできません — 許容範囲外のタイムスタンプの配信は拒否してください。 |
| `X-Signature` | `HMAC-SHA256(secret, X-Timestamp + "." + raw_body)` の `sha256=<hex>`。詳しくは [署名検証](https://docs.infraio.xyz/ja/webhooks/signature-verification)。 |
| `X-Signature-Prev` | 同じアルゴリズムを **前の** シークレットで適用したもの。ローテーション後 24 時間ウィンドウの間のみ存在 — 切り替え中、新旧どちらの鍵を使う検証側でも配信を受け入れ続けられるようにします。ウィンドウが閉じるとヘッダーは送信されません。 |

## リトライスケジュール

エンドポイントがタイムアウト内に `2xx` を返さない場合、
次のスケジュールでリトライします (初回試行からの相対時刻):

| 試行 | 遅延 | 累計 |
| --- | --- | --- |
| 1 | 0s | 0s |
| 2 | +1 分 | 1m |
| 3 | +5 分 | 6m |
| 4 | +15 分 | 21m |
| 5 | +1 時間 | 1h 21m |
| 6 | +6 時間 | 7h 21m |

試行 6 が失敗すると配信は **Failed** になり、
アカウントのメール宛に通知されます。失敗したイベントは
ダッシュボードの **Developers → Webhooks → Delivery history** パネルから
リプレイできます。各リプレイは独自の `X-Delivery` を持つ新しい配信です。

## エンドポイントの登録

[マーチャントダッシュボード](https://app.infraio.xyz) から:

1. **Developers → Webhooks** → **+ Add endpoint**
2. URL を貼り付け — `https://…` のみ (プレーン HTTP は拒否されます;
   作成フォームは `localhost`、プライベート IP レンジ、ユーザー情報を
   含む URL もブロックします)
3. サブスクライブするイベントを選択 (全イベントは `*`)
4. 環境を選択 — **test** または **live** (それぞれ独自のシークレットを
   持ち、互いに混在することはありません)
5. 保存 → ダッシュボードが署名シークレット (`whsec_…`) を **1 度だけ** 表示します。
   サーバー側に保存してください; 次の 2 つの機能で使います。

マーチャントごと、環境ごとに最大 **10 個** のエンドポイントを登録できます
(例: 本番フルフィルメント用、ステージングミラー用、Slack 通知用)。
それぞれが独立したリトライ状態とシークレットを持ちます。

## 各エンドポイントのライフサイクル操作

各エンドポイントカードの ⋮ メニューには以下が並びます:

- **Edit** — URL、説明、サブスクリプションリストを変更します。
  新しい URL は作成時と同じ `https://`/SSRF ルールで再検証されます。
- **Send Test** — `webhook.test.ping` エンベロープを現在のシークレットで
  署名して同期 POST します。ダッシュボードに HTTP ステータス、レイテンシ、
  応答の先頭 512 バイトが表示されます。テスト ping はリトライされないので、
  結果は即座に返ります。
- **Rotate Secret** — 新しいシークレットを生成します。前のシークレットは
  **24 時間** 有効のままです (この間、配信は `X-Signature` と
  `X-Signature-Prev` の両方を含むので、検証側がどちらの鍵を使っていても
  再デプロイ中もイベントを受け入れ続けられます)。
- **Reveal Secret** — 既存のシークレットを再表示します。新規の 2FA 検証で
  ゲートされ、監査ログに記録されます; コピーを紛失し、かつローテーションが
  許容できない場合のみ使用してください。
- **Enable / Disable** — 配信履歴を失わずにエンドポイントのオン / オフを切り替えます。
  無効化されたエンドポイントはダッシュボードに残りますが、新しい配信は
  受け取りません。
- **Delete** — 永続的に削除します。後で再有効化する可能性があるなら
  Disable を使ってください。

## ハンドラのコツ

1. **2xx をすばやく返す。** 重い処理の前に `200 OK` で確認応答 — フルフィル
   メントはバックグラウンドジョブに移してください。試行ごとの
   タイムアウトは **10 秒** で、それより長く応答を保持するとリトライが
   トリガーされます。タイムアウトはプラットフォーム側で固定で、マーチャント
   設定では変更できません — ハンドラがどうしてもより長い時間を必要とする
   場合はサポートに連絡してください。
2. **`X-Delivery` で重複排除する** (または `Idempotency-Key` — 同じ値)。
   2xx を返しても上流プロキシが接続を切ってリトライがトリガーされる可能性が
   あります; 配信 ID は同じ配信行のすべてのリトライにわたって安定なので、
   これが正しいキーです。
3. **未知のイベント種別を許容する。** 新しいイベントが現れる可能性があります;
   4xx ではなく 200 + no-op を返してください。さもないとそれらの配信が
   リトライされ続けます。
4. **ビジネスロジックの横に `X-Delivery` をログする。** 何か起きたとき、
   それが我々の側と御社の側を結合するキーです。

## 次に

- [署名検証](https://docs.infraio.xyz/ja/webhooks/signature-verification) — 正確なアルゴリズム +
  リプレイ対策パターン。
- [コンセプト → セッション](https://docs.infraio.xyz/ja/concepts/sessions) — 各イベント発火時に
  セッションがどの状態にあるか。
