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

# Webhook 總覽

Webhook 是**權威**訊號。瀏覽器回呼 (`onSuccess`) 與儀表板畫面僅是
輔助資訊;Webhook 才是真相之源。

## 投遞保證

- **至少一次。** 若你的伺服器未在逾時內回應 2xx,單一事件最多會被
  投遞 **6 次**。處理函式請保持冪等 — 以 `X-Delivery` 去重。
- **每個 HTTP 請求僅一個事件。** 不做批次。
- **每個端點獨立。** 如果你註冊了多個端點，各自擁有獨立的投遞與
  重試軌道。某個慢的端點不會拖累其他端點。
- **已簽章。** 每個酬載都帶 `X-Signature` 標頭(於密鑰輪替後的 24 小時
  視窗期內，還會附帶 `X-Signature-Prev`)。處理 body 前請先驗證。
  詳見[簽章驗證](https://docs.infraio.xyz/zh-TW/webhooks/signature-verification)。

## 可訂閱的事件類型

| 事件 | 觸發條件 |
| --- | --- |
| `payment.settled` | 鏈上轉帳達到該鏈的確認數。**用此事件標記訂單為已付款。** |
| `payment.failed` | 法幣付款被支付 provider 拒絕。**不會**因為加密貨幣超時而觸發 — 那些情境會以 `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` | Refund-request token 被鑄造。`data.source` 為 `b2b` / `dashboard` / `renewal`。訂閱與否為選擇性 — 對於追蹤每個訂單目前哪個 token 是有效的稽核管線很有用。 |
| `refund_request.renewal_requested` | 買家在 token 過期後點了「申請新連結」。**強烈建議訂閱** — 這是商家收到續期 widget 有新項目需要處理的訊號。 |
| `refund_request.renewed` | 續期通過核准，新的 token 取代舊的。`data.old_token` / `data.new_token` 形成稽核鏈。 |
| `refund_request.canceled` | 商家從儀表板把一個 token 翻為 `CANCELED`(例如駁回續期請求、終止仍有效的連結)。冪等 — 只有第一次轉換會發出。`data.reason` 是商家選填的備註。 |

### 規劃中(Coming soon)

> **Note:**
>
> **即將推出。** 這些事件屬於定期帳單與訂閱功能，目前尚未提供。
> 它們**不在**上方可訂閱的表格中，目前無法訂閱。請見
> [定期帳單](https://docs.infraio.xyz/zh-TW/guides/recurring-invoices)。

| 規劃中的事件 | 觸發時機… |
| --- | --- |
| `subscription.created` | 建立訂閱時。 |
| `invoice.created` | 建立某個計費週期的帳單時。 |
| `invoice.paid` | 帳單已付款時。 |
| `subscription.past_due` | 帳單逾期未付時。 |
| `subscription.canceled` | 訂閱被取消時。 |

儀表板的端點表單列出相同的事件。訂閱不存在的事件會在你儲存端點時
被拒絕。

> **Note:**
>
> **測試事件無法訂閱。** 儀表板上的逐端點 **Send Test** 按鈕會立即把一個
> `webhook.test.ping` 事件送到該端點，不會重試。它不在上面的目錄中:
> 你是因為註冊了端點才會收到它，而不是因為訂閱。

> **Note:**
>
> 只訂閱你會處理的事件。每個端點有獨立的事件過濾器;萬用字元 `"*"`
> 代表「所有事件，包含未來新增的」。訂閱較少事件可讓處理函式更簡單，
> 也能在你的端點出錯時減少重試。

## 酬載 + 標頭

**HTTP body 直接是事件專屬的 data 物件**。沒有 Stripe 風格的外層
信封 — 事件類型、投遞 ID、發出時間等欄位改放在**標頭**裡。對於
`payment.settled`,body 看起來像這樣:

```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":          { /* 每事件 */ }
}
```

其他事件帶各自的欄位。
欄位名稱穩定(lower snake_case);鏈上交易雜湊永遠是 `tx_hash`。

`tx_hash` 是該網路自有格式的交易識別碼(EVM 鏈上為 `0x…`;TRON、Solana、TON 上為原生雜湊或簽章)。對於 TRON、Solana 與 TON,買家直接付款到你的資金庫錢包,因此 `deposit_address` 可能不存在;`confirmations` 依照[支援的鏈與資產](https://docs.infraio.xyz/zh-TW/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)` pair 的所有重試間穩定** — 請以它作為冪等鍵。 |
| `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/zh-TW/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_…`)。請保存到伺服端;
   下面兩個功能會用到它。

每個商家每個環境最多可註冊 **10 個端點**(例如:一個用於正式履約，
一個用於預備鏡像，一個用於 Slack 通知)。每個端點有獨立的重試
狀態與金鑰。

## 每個端點的生命週期操作

每張端點卡片的 ⋮ 選單提供:

- **Edit** — 變更 URL、描述或訂閱列表。新 URL 會以與建立時相同的
  `https://`/SSRF 規則重新驗證。
- **Send Test** — 以你目前的金鑰同步 POST 一個 `webhook.test.ping`
  信封。儀表板顯示 HTTP 狀態、延遲與你回應的前 512 位元組。測試 ping 不會重試，
  因此能立即得到結果。
- **Rotate Secret** — 產生新金鑰。前一個金鑰仍維持 **24 小時** 有效
  (期間投遞同時帶 `X-Signature` 與 `X-Signature-Prev`,讓使用任一
  金鑰的驗證器在你重新部署期間都能繼續接受事件)。
- **Reveal Secret** — 重新顯示目前金鑰。受新的 2FA 驗證控管並記錄
  到稽核日誌;僅在你遺失副本且無法接受輪替時使用。
- **Enable / Disable** — 不遺失投遞歷史的情況下開啟或關閉端點。
  停用的端點仍保留在儀表板，但不再接收新投遞。
- **Delete** — 永久刪除。若可能再次啟用，請使用 Disable。

## 處理函式小技巧

1. **儘速回應 2xx。** 在做重活前先以 `200 OK` 確認 — 把履約移到
   背景作業。**每次嘗試的逾時為 10 秒**;回應保持超過此時間會觸發
   重試。此逾時為平台端設定，商家無法調整 — 若你的 handler 確實
   需要更多時間，請聯絡 support。
2. **以 `X-Delivery` 去重**(或同值的 `Idempotency-Key`)。即使你
   回應了 2xx,上游代理可能斷線並觸發重試;投遞 ID 在同一筆投遞
   列的每次重試之間穩定，因此是正確的鍵。
3. **容忍未知事件類型。** 可能會出現新事件;請回 200 + no-op,
   而非 4xx,否則這些投遞會持續重試。
4. **在業務邏輯旁邊記錄 `X-Delivery`。** 出問題時，這就是我們這邊
   與你那邊的串接鍵。

## 下一步

- [簽章驗證](https://docs.infraio.xyz/zh-TW/webhooks/signature-verification) — 精確演算法 +
  防重放模式。
- [概念 → 會話](https://docs.infraio.xyz/zh-TW/concepts/sessions) — 每個事件觸發時會話
  處於什麼狀態。
