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

# Webhook 通知

> StablePay Webhook 事件类型、请求头、签名校验要求，以及 payment.failed 中 frozen 状态的处理建议。

StablePay 会通过 Webhook 将关键支付、退款、订阅和发票事件异步推送到你在商户后台配置的回调 URL。

本页汇总 Webhook 的事件类型、请求头、签名校验要求，以及近期新增的风控冻结状态适配说明。

## 在 Merchant Portal 中配置 Webhook

在你的服务端能够接收 Webhook 通知之前，需要先创建 API 店铺，并在 [店铺和开发者](https://merchant.stablepay.co/#/merchant/stores) 页面中配置回调地址。

### 第一步：创建 API 店铺

1. 打开 [店铺和开发者](https://merchant.stablepay.co/#/merchant/stores) 页面。
2. 点击【创建店铺】。
3. 渠道类型选择 **API**。
4. 按实际业务信息填写店铺名称和域名。
5. 提交申请并等待 StablePay 审核。

![在店铺和开发者页面创建 API 店铺](https://static.stablepayfi.ai/developer.jpeg)

### 第二步：店铺启用后配置 Webhook

当店铺状态变为【已启用】后：

1. 返回 [店铺和开发者](https://merchant.stablepay.co/#/merchant/stores) 页面。
2. 打开对应店铺的操作菜单。
3. 点击【Webhook】。
4. 填写你的 Webhook 回调 URL 并保存。
5. 如有需要，也可以在同一操作菜单中点击【密钥】，创建服务端接入所需的 API Key 和 Secret Key。
6. 如果有效期设置为 `0天`，表示该密钥长期有效。

![在已启用店铺的操作菜单中创建密钥并配置 Webhook URL](https://static.stablepayfi.ai/webhook.jpeg)

<Note>
  建议使用可被 StablePay 公网访问的 HTTPS 服务端地址作为 Webhook 回调地址，并与浏览器页面或前端路由分开。请不要把前端页面 URL 直接作为 Webhook 目标地址。
</Note>

## 事件类型

| 类别 | 事件类型                                                                                                                                                                          |
| -- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 支付 | `payment.completed`、`payment.failed`、`payment.expired`、`payment.cancelled`                                                                                                    |
| 退款 | `refund.succeeded`、`refund.failed`                                                                                                                                            |
| 订阅 | `subscription.created`、`subscription.trialing`、`subscription.active`、`subscription.past_due`、`subscription.canceled`、`subscription.updated`、`subscription.incomplete_expired` |
| 发票 | `invoice.created`、`invoice.paid`、`invoice.payment_failed`                                                                                                                     |

## Webhook 请求头

| 请求头                      | 说明                                                             |
| ------------------------ | -------------------------------------------------------------- |
| `Content-Type`           | 固定 `application/json`                                          |
| `User-Agent`             | `StablePay-Webhook/1.0`                                        |
| `X-StablePay-Signature`  | 对 `{timestamp}.{nonce}.{raw_body}` 做 HMAC-SHA256 后得到的小写 hex 签名 |
| `X-StablePay-Timestamp`  | Unix 时间戳（秒）                                                    |
| `X-StablePay-Nonce`      | 随机字符串（16\~64 字符），参与签名计算，每次通知唯一                                 |
| `X-StablePay-Event-Type` | 事件类型，例如 `payment.completed`                                    |
| `X-StablePay-Event-ID`   | 事件唯一 ID，用于幂等去重                                                 |

## 签名校验

请使用原始请求体字节参与验签，不要在校验前先解析 JSON 再重新序列化。

签名串格式如下：

```text theme={"system"}
sign_payload = {timestamp} + "." + {nonce} + "." + {raw_body}
```

校验建议：

1. 读取 `X-StablePay-Signature`、`X-StablePay-Timestamp`、`X-StablePay-Nonce`
2. 校验时间戳与当前时间相差不超过 5 分钟
3. 校验 nonce 长度为 16\~64 字符
4. 使用 Secret Key 对签名串做 HMAC-SHA256
5. 使用恒定时间比较方式校验签名

> 签名校验代码示例可参考 [集成前准备](/zh/api/getting-started)。

## 响应与重试

* 商户服务需在 **30 秒内**返回 HTTP `2xx`
* 返回 `429` 或 `5xx` 会进入重试队列，其他 `4xx` 默认不重试
* 最多重试 **10 次**
* 建议使用 `X-StablePay-Event-ID` 或请求体中的 `id` 字段做幂等去重

## payment.failed 与 frozen 状态

近期 StablePay 加强了风控能力。上线后，当支付订单触发高风险风控策略时，资金可能会被暂时冻结。这类场景通常涉及疑似黑灰产、异常交易或其他高风险行为，订单需要经过进一步审核后再确认后续处理结果。

在该机制上线后，StablePay **仍然会继续使用现有的 `payment.failed` webhook 事件类型** 通知商户系统，但回调中的支付状态会新增：

```json theme={"system"}
"status": "frozen"
```

这意味着 `payment.failed` 事件中的 `data.object.status` 现在至少有两种处理分支：

* `failed`：普通支付失败，按原有失败逻辑处理
* `frozen`：风控冻结，不应直接视为普通支付失败

不同渠道或集成来源下，业务扩展字段可能出现在 `metadata` 中，也可能以独立字段形式返回。请以 `type`、`id`、以及 `data.object` 中与你业务处理直接相关的核心状态字段为准。

### frozen 示例回调

```json theme={"system"}
{
  "id": "evt_1778818565572372433",
  "data": {
    "object": {
      "amount": 1,
      "status": "frozen",
      "currency": "USDT",
      "metadata": {
        "source": "api",
        "wc_order_id": "order_0b543c39"
      },
      "order_id": "order_0b543c39",
      "session_id": "20260515110100101649130000272078",
      "exchange_rate": ""
    }
  },
  "type": "payment.failed",
  "created_at": 1778818549
}
```

### 处理建议

当你收到 `payment.failed` 事件时，请额外判断 `data.object.status`：

* 当 `status = "failed"` 时，按原有支付失败逻辑处理
* 当 `status = "frozen"` 时，建议标记为“风控冻结”或“待风控审核”

对于 `frozen` 场景，**不要**直接按普通支付失败处理，也不建议自动执行以下动作：

* 自动补单
* 自动发货
* 自动释放服务权益或访问权限

## 事件样例

### payment.completed

```json theme={"system"}
{
  "id": "evt_1778835561972546443",
  "type": "payment.completed",
  "created_at": 1778835561,
  "data": {
    "object": {
      "amount": 1,
      "status": "completed",
      "currency": "USDT",
      "order_id": "order_56929d9f",
      "session_id": "20260515110100101170210000082012",
      "exchange_rate": "",
      "source": "api"
    }
  }
}
```

### payment.failed

```json theme={"system"}
{
  "id": "evt_1778834854265555041",
  "type": "payment.failed",
  "created_at": 1778834851,
  "data": {
    "object": {
      "amount": 1,
      "status": "frozen",
      "currency": "USDT",
      "order_id": "order_31509b43",
      "session_id": "20260515110100101170210000082010",
      "exchange_rate": "",
      "source": "api"
    }
  }
}
```

### payment.expired

```json theme={"system"}
{
  "id": "evt_1778836650899324281",
  "type": "payment.expired",
  "created_at": 1778836650,
  "data": {
    "object": {
      "amount": 0,
      "status": "expired",
      "currency": "",
      "order_id": "order_d3ff11a8",
      "session_id": "20260515110100101170210000082011",
      "exchange_rate": "",
      "source": "api"
    }
  }
}
```

### payment.cancelled

```json theme={"system"}
{
  "id": "evt_1770864227443530013",
  "type": "payment.cancelled",
  "created_at": 1770864227,
  "data": {
    "object": {
      "amount": 0,
      "status": "canceled",
      "currency": "",
      "order_id": "order_29",
      "session_id": "20260212110100101170210000028002",
      "exchange_rate": "",
      "source": "api"
    }
  }
}
```

## 相关页面

* [集成前准备](/zh/api/getting-started)
