ZeroPay

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(或重新拉取订单)为准,不要假设事件顺序。
  • 处理器需要幂等——同一事件可能被投递多次。
Webhook 回调 · Docs · ZeroPay