Webhook
Webhook 把收款(收单)订单事件推送到你控制的 HTTPS 地址,后端不用轮询就能知道付款到了。它按账户在 开发者 → Webhook(/home/developer/webhooks)里配置:保存回调地址、复制签名密钥、发送测试事件,并在那里查看每一次投递。该页需先通过你本人的个人身份认证(KYC)才能打开,保存设置、发送测试事件、重放投递也都需要。
对回调地址的要求
- 标准端口的公网 HTTPS 地址;内网或本机地址保存时会被拒绝。
- 10 秒内返回任意
2xx。不跟随重定向(3xx),重定向按失败计。 - 投递是 至少一次:用信封里的
id去重。
信封
json
{
"id": "3f0d7a2e-…",
"type": "acquiring.order.paid",
"createdAt": "2026-10-09T08:12:33.000Z",
"dataVersion": 1,
"data": {
"orderId": "…",
"orderNo": "…",
"status": "paid",
"amount": "120.00",
"receivedAmount": "120.00",
"currency": "USDT",
"paymentMethod": "usdt-trc20",
"custodyMode": "platform",
"verification": null,
"metadata": {}
}
}| 字段 | 含义 |
|---|---|
id | 事件 id(UUID);重试与重放都是同一个 id |
type | 事件类型,见下表 |
createdAt | 事实发生的时间(ISO 8601) |
dataVersion | data 的结构版本,目前为 1 |
data | 订单事实,金额为十进制字符串;metadata 是你建单时附带的对象 |
事件类型
type | 时机 |
|---|---|
acquiring.order.paid | 客户付款已确认 |
acquiring.order.settled | 资金已结算到你的余额 |
acquiring.order.funds_reorg_compensated | 链上重组影响了这笔付款,平台已为订单补偿 |
merchant.test | 「发送测试事件」按钮发出的事件,不重试 |
兑换订单目前不投递 Webhook,请轮询 GET /cash/v1/merchant/exchange/orders/{orderId}。
验签
每次投递带 X-Cash-Signature: t=<unix 秒>,v1=<hex>,其中 v1 = HMAC-SHA256(secret, "<t>.<原始请求体>")。请对 原始 请求体字节计算 HMAC(不要重新序列化 JSON),用恒定时间比较,并拒绝几分钟以前的时间戳以防重放。
js
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verifyCashWebhook(rawBody, signatureHeader, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(signatureHeader.split(',').map((part) => part.split('=')));
const expected = createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) <= toleranceSeconds;
return fresh && expected.length === parts.v1.length && timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}在控制台轮换签名密钥后,旧签名立即失效——先部署新密钥,再轮换。
重试、死信与重放
失败的投递(网络错误、超时、非 2xx、重定向)按指数退避重试:从 60 秒起、上限 1 小时,最多 8 次。最后一次仍失败后事件成为 死信,出现在 开发者 → Webhook 页(/home/developer/webhooks)的「Webhook 投递记录」里并带「重放」按钮,重放会用同一个 id 再发一次。每次尝试的结果、HTTP 状态与原因码都列在那里;请求载荷与密钥不会显示。