> ## Documentation Index
> Fetch the complete documentation index at: https://waffo.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhook - 事件类型列表

> Waffo Webhook 所有事件类型的详细说明与 payload 示例。

## 事件类型

| eventType                                  | 说明                       |
| ------------------------------------------ | ------------------------ |
| `PAYMENT_NOTIFICATION`                     | 支付完成或失败                  |
| `REFUND_NOTIFICATION`                      | 退款完成或失败                  |
| `TOKENIZATION_NOTIFICATION`                | Token 生成、状态变更或卡片信息更新     |
| `SUBSCRIPTION_STATUS_NOTIFICATION`         | 订阅状态变更（激活、取消等，也用于订阅扣款事件） |
| `SUBSCRIPTION_PERIOD_CHANGED_NOTIFICATION` | 订阅周期变更                   |
| `SUBSCRIPTION_CHANGE_NOTIFICATION`         | 订阅变更（升降级）完成              |

## 通用结构

所有 Webhook 使用相同的请求结构：

```
POST {notifyUrl}
Content-Type: application/json
X-SIGNATURE: {Waffo 私钥签名}
```

## PAYMENT\_NOTIFICATION

支付完成后通知。

```json theme={null}
{
  "eventType": "PAYMENT_NOTIFICATION",
  "acquiringOrderId": "ACQ123456",
  "merchantOrderId": "ORDER123",
  "paymentRequestId": "REQ123",
  "orderStatus": "PAY_SUCCESS",
  "orderAmount": "100.00",
  "orderCurrency": "USD",
  "finalDealAmount": "100.00"
}
```

## REFUND\_NOTIFICATION

退款完成后通知，发送到创建退款时的 `refundNotifyUrl`。

```json theme={null}
{
  "eventType": "REFUND_NOTIFICATION",
  "acquiringRefundOrderId": "REF123456",
  "refundRequestId": "REFUND_REQ123",
  "refundStatus": "ORDER_FULLY_REFUNDED",
  "refundAmount": "100.00"
}
```

## TOKENIZATION\_NOTIFICATION

Tokenization 完成、Token 状态变更或 Token 卡片信息更新时通知，发送到调用 [Generate Token](/docs/zh/developer-docs/integration/tokenization/generate) 时传入的 `notifyUrl`。

```json theme={null}
{
  "eventType": "TOKENIZATION_NOTIFICATION",
  "result": {
    "tokenRequestId": "202510111010050001",
    "tokenId": "tok_xxxxxxxxxxxx",
    "tokenType": "CARD",
    "tokenStatus": "UNVERIFIED",
    "tokenData": {
      "maskedCardInfo": "481852******0147",
      "cardHolderName": "Zhang san",
      "cardExpiry": "01/2030",
      "cardBin": "481852",
      "cardScheme": "VISA",
      "cardType": "CREDIT_CARD",
      "cardIssuerName": "INTL HDQTRS-CENTER OWNED",
      "cardIssueCountryCode": "RUS"
    },
    "billingAddress": {
      "countryCode": "USA",
      "region": "CA",
      "city": "Cupertino",
      "postalCode": "95014",
      "address": "One Apple Park Way"
    },
    "merchantUserId": "merchant-user-001",
    "merchantInfo": {
      "merchantId": "your_merchant_id",
      "subMerchantId": ""
    }
  }
}
```

| 字段               | 说明                                                     |
| ---------------- | ------------------------------------------------------ |
| `tokenRequestId` | 商户发起 Tokenization 时传入的请求 ID。若状态变更由 Waffo 触发，该字段可能不存在   |
| `tokenId`        | Token ID。商户需安全保存，用于后续 Token 支付                         |
| `tokenType`      | Token 类型，目前为 `CARD`                                    |
| `tokenStatus`    | Token 状态：`UNVERIFIED`、`VERIFIED`、`EXPIRED`、`SUSPENDED` |
| `tokenData`      | 卡片摘要信息，包括脱敏卡号、有效期、BIN、卡组织、卡类型、发卡行与发卡国家                 |
| `billingAddress` | 持卡人账单地址                                                |
| `merchantUserId` | 商户侧用户 ID                                               |
| `merchantInfo`   | 商户信息                                                   |

## SUBSCRIPTION\_STATUS\_NOTIFICATION

当订阅状态发生变更时通知（包括激活、取消、扣款结果等）。

```json theme={null}
{
  "eventType": "SUBSCRIPTION_STATUS_NOTIFICATION",
  "subscriptionId": "SUB123456",
  "subscriptionStatus": "ACTIVE"
}
```

SDK 中可使用两种处理器：

* `onSubscriptionStatus()` — 直接处理此事件
* `onSubscriptionPayment()` — 当 `onSubscriptionStatus` 未注册时的降级处理

## SUBSCRIPTION\_PERIOD\_CHANGED\_NOTIFICATION

当订阅周期达到终态时通知，用于追踪续费结果。

```json theme={null}
{
  "eventType": "SUBSCRIPTION_PERIOD_CHANGED_NOTIFICATION",
  "subscriptionId": "SUB123456",
  "period": "3",
  "orderStatus": "PAY_SUCCESS"
}
```

## SUBSCRIPTION\_CHANGE\_NOTIFICATION

订阅变更（升降级）完成时通知。

```json theme={null}
{
  "eventType": "SUBSCRIPTION_CHANGE_NOTIFICATION",
  "result": {
    "subscriptionRequest": "new-sub-req-001",
    "originSubscriptionRequest": "orig-sub-req-001",
    "subscriptionId": "waffo-sub-12345",
    "subscriptionChangeStatus": "SUCCESS",
    "subscriptionAction": "UPGRADE",
    "remainingAmount": "50.00",
    "currency": "HKD"
  }
}
```

| subscriptionChangeStatus | 说明      |
| ------------------------ | ------- |
| `SUCCESS`                | 变更成功    |
| `CLOSED`                 | 变更失败/关闭 |

## 商户响应

所有 Webhook 必须返回 HTTP 200，body 为 `{"message":"success"}`，**且必须携带 `X-SIGNATURE` 响应头**（用商户私钥签名响应 body）。

<Warning>
  Waffo 同时验证 HTTP 状态码和响应 body。如果响应格式不正确或缺少签名，Waffo 会认为通知投递失败并重试。
</Warning>

### 使用 SDK（推荐）

SDK 的 `handleWebhook()` 方法自动处理签名验证、事件路由和响应签名：

```typescript theme={null}
const result = await waffo.webhook().handleWebhook(body, signature);
res.setHeader('X-SIGNATURE', result.responseSignature);
res.status(200).send(result.responseBody);
```

### 手动响应

如不使用 SDK，响应 body 必须是以下之一：

* `{"message":"success"}` — 已成功处理
* `{"message":"failed"}` — 处理失败，Waffo 将重试
* `{"message":"unknown"}` — 状态未知，Waffo 将重试

**重试策略**：`failed` 或 `unknown` 时，Waffo 最多重试 8 次（含首次），间隔从 30 秒递增到 8 小时。详见 [重试与失败恢复](/docs/zh/developer-docs/webhook/retry)。

***

## 订阅通知选择指南

订阅场景涉及三类通知，按需选择监听：

<Warning>
  `SUBSCRIPTION_STATUS_NOTIFICATION` 和 `SUBSCRIPTION_PERIOD_CHANGED_NOTIFICATION` 都是异步分发的 Webhook，受队列消费、重试、网络延迟等影响，到达顺序不保证；不要把回调到达顺序作为业务状态机依据。正确的处理方式（幂等去重、收到任一回调后先调用 `subscription/inquiry` 查询最终状态）见 [处理最佳实践](/docs/zh/developer-docs/webhook/best-practices)。
</Warning>

| 事件                                         | 触发时机                   | 适合场景                |
| ------------------------------------------ | ---------------------- | ------------------- |
| `SUBSCRIPTION_STATUS_NOTIFICATION`         | 订阅主单状态变更（激活、取消、关闭）     | 管理订阅生命周期：开通/关闭用户权益  |
| `SUBSCRIPTION_PERIOD_CHANGED_NOTIFICATION` | 每期续费的终态（重试过程不通知，仅最终结果） | 只关心每期最终结果，不关心中间重试   |
| `PAYMENT_NOTIFICATION`                     | 每次支付订单（含首期 + 续期每次重试）   | 了解每次扣款的失败原因、支付时间等详情 |

<Tip>
  **典型组合推荐：**

  * **最小集成**：监听 `SUBSCRIPTION_STATUS_NOTIFICATION` + `SUBSCRIPTION_PERIOD_CHANGED_NOTIFICATION`
  * **完整集成**：三种都监听，用 `PAYMENT_NOTIFICATION` 获取每次重试的详细失败原因
</Tip>
