跳转到主要内容

Documentation Index

Fetch the complete documentation index at: https://docs.xpaylabs.com/llms.txt

Use this file to discover all available pages before exploring further.

每当订单或收款状态变更时,XPayLabs 会向您配置的端点 URL 发送 HTTP POST 回调。无需轮询 API 获取状态变更——注册您的回调 URL,实时接收已签名的通知。

配置

在网关配置中设置您的 Webhook 端点 URL 和共享密钥:
xpay:
  merchant:
    callback-url: "https://yourapp.com/webhooks/xpaylabs"
    webhook-secret: "your-webhook-secret-here"
webhook-secret 用于计算每个 Webhook 负载中的 sign 字段,使您能够验证通知确实来自您的网关。

负载格式

每个 Webhook 回调都是一个 POST 请求,JSON 体遵循 NotifyPayload 结构:
字段类型描述
signstring用于负载验证的 HMAC-SHA256 签名
timestampinteger通知生成时的 Unix 时间戳
noncestring唯一事件标识符
notifyTypestring事件类型(参见事件类型
dataobject事件负载(NotifyOrder 对象)

示例负载

{
  "sign": "a1b2c3d4e5f6...",
  "timestamp": 1717000123,
  "nonce": "550e8400-e29b-41d4-a716-446655440000",
  "notifyType": "ORDER_SUCCESS",
  "data": {
    "orderId": "order_1042",
    "uid": "user_42",
    "orderType": "COLLECTION",
    "status": "SUCCESS",
    "reason": null,
    "amount": "250.00",
    "actualAmount": "249.50",
    "fee": "0.00",
    "transaction": {
      "chain": "TRON",
      "symbol": "USDT",
      "txid": "a1b2c3d4e5f6...",
      "from": "TXyz...",
      "to": "TWkKZkmuB8DpVeiMoHiKf99ZoFHzk73CqR",
      "amount": "250.00",
      "blockNum": 12345678,
      "confirmedNum": 3,
      "status": "SUCCESS",
      "timestamp": 1717000123
    }
  }
}

签名验证

使用您的 webhook-secret 验证 Webhook 签名:

算法

  1. data 对象(负载中的 data 字段)序列化为紧凑 JSON。
  2. 计算 HMAC-SHA256(data_json, webhook_secret)
  3. 转换为小写十六进制并与 sign 字段比较。

Node.js

import crypto from "crypto";

export function verifyWebhook(payload, secret) {
  const dataJson = JSON.stringify(payload.data);
  const expectedSign = crypto
    .createHmac("sha256", secret)
    .update(dataJson, "utf8")
    .digest("hex");

  if (!crypto.timingSafeEqual(
    Buffer.from(expectedSign, "hex"),
    Buffer.from(payload.sign, "hex")
  )) {
    throw new Error("无效的 Webhook 签名");
  }

  return true;
}

app.post("/webhooks/xpaylabs", express.json(), (req, res) => {
  try {
    verifyWebhook(req.body, process.env.XPAYLABS_WEBHOOK_SECRET);
  } catch (err) {
    return res.status(400).send("验证失败");
  }

  const event = req.body;
  res.status(200).send("ok");
});

Python

import hmac, hashlib, json
from flask import Flask, request

app = Flask(__name__)
WEBHOOK_SECRET = "your-webhook-secret"

@app.route("/webhooks/xpaylabs", methods=["POST"])
def handle_webhook():
    payload = request.get_json()
    data_json = json.dumps(payload["data"], separators=(",", ":"))
    expected_sign = hmac.new(
        WEBHOOK_SECRET.encode(),
        data_json.encode(),
        hashlib.sha256,
    ).hexdigest()

    if not hmac.compare_digest(expected_sign, payload["sign"]):
        return "无效签名", 400

    return "", 200

重试策略

如果您的端点在 10 秒内未返回 2xx 状态码,XPayLabs 会使用指数退避重试 Webhook:
尝试延迟
第 1 次重试~1 秒
第 2 次重试~5 秒
第 3 次重试~30 秒
第 4 次重试~5 分钟
4 次尝试失败后,通知被标记为失败。失败的 Webhook 会被记录,并可从网关管理面板重新发送。

最佳实践

  • 立即响应。 尽快返回 200,然后异步处理事件。
  • 验证每个负载。 在处理 Webhook 之前始终检查 HMAC 签名。
  • 使用 nonce 去重。 存储已处理的 nonce 以处理至少投递一次的模式。
  • 检查 notifyType 根据通知类型路由事件以正确处理每个事件。
Last modified on May 31, 2026