Webhook 回调
每次订单状态变迁的签名回调。
携带 webhook_url 创建的订单在状态变化时,ZeroPay 会向该地址 POST JSON。事件包括:order.processing、order.success、order.failed、order.refunded、order.expired。
回调示例
{
"event": "order.success",
"created_at": "2026-06-12T16:05:12Z",
"order": {
"order_no": "zp_ord_9f2c41d8a3b7e6f0a1d2",
"external_id": "inv_10086",
"status": "success",
"amount_usd": 49.9,
"amount_out": "49260000",
"settle_tx_hash": "0xdef456...",
"...": "same shape as GET /v1/orders/{order_no}"
}
}签名验证
每个请求携带 X-ZeroPay-Event 与 X-ZeroPay-Signature: t=<unix>,v1=<hex>。v1 是以创建订单的 API Key 的 webhook_secret 为密钥、对 "<t>.<原始请求体>" 计算的 HMAC-SHA256。务必先验签再信任内容,并拒绝过旧的时间戳(如超过 5 分钟)。
验签(Node.js)
import crypto from "node:crypto";
export function verifyZeroPaySignature(req: Request, rawBody: string): boolean {
// Header: X-ZeroPay-Signature: t=1718200000,v1=hex(hmacSHA256(secret, t + "." + body))
const header = req.headers.get("X-ZeroPay-Signature") ?? "";
const parts = Object.fromEntries(
header.split(",").map((kv) => kv.split("=") as [string, string]),
);
const expected = crypto
.createHmac("sha256", process.env.ZEROPAY_WEBHOOK_SECRET!)
.update(parts.t + "." + rawBody)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(parts.v1 ?? "", "hex"),
Buffer.from(expected, "hex"),
);
}投递与重试
- 10 秒内返回任意 2xx 即视为投递成功。
- 失败按指数退避重试:1 分钟、5 分钟、30 分钟、2 小时——共 5 次,之后标记为 dead。
- 重试场景下事件可能乱序到达;请以回调体内的 order.status(或重新拉取订单)为准,不要假设事件顺序。
- 处理器需要幂等——同一事件可能被投递多次。