Webhooks
支付通知
当订单最终状态确定时,AinePay 会给商户系统推送订单状态变化的通知以方便商户端及时处理。
端点
- 方法:
POST - 目标 URL:
{notifyUrl}/ainepay/notify - 认证: 需要签名验证(含时间窗校验)
- Content-Type:
application/x-www-form-urlencoded - 请求头:
x-api-signature:HMAC-SHA256 十六进制小写x-api-timestamp:毫秒级时间戳,参与签名x-api-recv-window:接收窗口(毫秒),参与签名
投递行为
- Webhook 在订单状态发生需要通知商户的变化时创建。
- 在当前实现中,AinePay 为
PAID、EXPIRED、CANCEL和REFUND发送通知。 - 通过支付链接(PAYMENT_LINK)创建的订单不发送回调。
- AinePay 将任何 HTTP
2xx响应视为成功确认(不跟随重定向)。 - 如果端点返回非
2xx状态或请求失败,AinePay 会以指数退避方式重试:基础延迟5 秒,第 n 次重试延迟为5 秒 × 2^(n-1),最多尝试5次,之后不再重试。 - 当商户被限流时,本次投递以
30 秒退避重新调度。
请求字段
| 字段 | 类型 | 描述 | 是否必需 | 示例 |
|---|---|---|---|---|
| merchantId | string | 商户 ID。 | 是 | 20001 |
| orderId | string | 商户订单 ID。 | 是 | ORDER_10001 |
| userId | string | 商户定义的用户 ID。 | 是 | U_90001 |
| coin | string | 代币符号。 | 是 | USDT |
| chain | string | 链代码。 | 是 | ETH |
| qty | string | 订单金额,格式化字符串。 | 是 | 88.00 |
| status | string(enum) | 订单状态。在当前实现中,Webhook 状态为 PAID、EXPIRED、CANCEL 或 REFUND。 | 是 | PAID |
| expired | integer | 订单过期时间戳(毫秒)。 | 是 | 1760000600000 |
| created | integer | 订单创建时间戳(毫秒)。 | 是 | 1760000000000 |
| updated | integer | 订单最后更新时间戳(毫秒)。 | 是 | 1760000300000 |
签名验证
- AinePay 使用商户的 Webhook 验证密钥签署「URL 编码的 Webhook 正文 +
×tamp=<ts>&recvWindow=<rw>」。 - 签名在
x-api-signature中以小写十六进制发送;时间戳与窗口分别在x-api-timestamp、x-api-recv-window中发送,并参与 HMAC 计算。 - 签名输入的 body 段是按字段名排序的规范 URL 编码表单字符串;末尾的
timestamp/recvWindow为纯数字,不再做 URL 编码。 - 商户验证步骤:
- 读取
x-api-timestamp与x-api-recv-window,校验|now - timestamp| ≤ min(recvWindow, MAX_ACCEPTABLE_RECV_WINDOW),超出窗口直接拒绝(推荐MAX_ACCEPTABLE_RECV_WINDOW = 300000毫秒)。 - 对原始 body 重新解析、字母序排序、URL 编码,得到规范 body。
- 把
×tamp=<ts>&recvWindow=<rw>拼到规范 body 末尾,对结果做 HMAC-SHA256,与x-api-signature做恒定时间比较。
- 读取
- 因为
timestamp/recvWindow已纳入签名,攻击者无法在不重新签名的情况下篡改任一字段。 - Webhook 验证的 Java / TypeScript 示例代码请参阅身份验证。
示例请求
POST /ainepay/notify HTTP/1.1 Host: merchant.example.com Content-Type: application/x-www-form-urlencoded x-api-signature: <hex(HMAC-SHA256(notifySecret, body + "×tamp=" + ts + "&recvWindow=" + rw))> x-api-timestamp: 1760000300000 x-api-recv-window: 5000 chain=ETH&coin=USDT&created=1760000000000&expired=1760000600000&merchantId=20001&orderId=ORDER_10001&qty=88.00&status=PAID&updated=1760000300000&userId=U_90001
预期响应
| 字段 | 类型 | 描述 |
|---|---|---|
| HTTP 状态 | integer | 返回任何 2xx 状态以确认成功接收。 |
HTTP/1.1 200 OK Content-Type: text/plain ok
注意事项
- 按
orderId幂等处理 Webhook。 - 将 Webhook 视为状态更新信号,然后使用查询订单确认最新订单状态后再执行最终业务操作。
- 如果签名无效,不要信任该数据。