跳到主要内容

Webhook

Webhook 允许您的系统从 PayerScan 接收自动实时通知。系统向商户创建发票时提供的 callback_url 发送 POST 请求。

事件类型

PayerScan 在 status 字段中返回 2 种主要事件:

  1. completed — 支付成功:已确认与发票匹配的交易(区块链或 Binance Pay)。
  2. expired — 发票过期:发票已超过支付等待时间。

商户需要提供一个公开 URL(建议使用 HTTPS),接收 POST 请求,处理 payload,并在规定的超时时间内(例如 10 秒)返回 HTTP 2xx


Payload 结构

1. 支付成功时的 Webhook(status: completed)

当找到匹配的交易(正确的地址、正确的金额、在时间窗口内),系统将发票更新为 completed 并向 callback_url 发送 POST,payload 如下:

Payload(JSON body)

{
"merchant_id": "MERCHANT_001",
"api_key": "YOUR_API_KEY",
"request_id": "order-1234",
"trans_id": "TID-ABC123DEF4567890",
"status": "completed",
"amount": "100",
"token_symbol": "USDT",
"token_price": "1",
"token_amount": "100.0026",
"network_symbol": "BSC",
"from_address": "0x8894e0a0c962cb723c1976a4421c95949be2d4e3",
"to_address": "0xc221460115e2CfCa5bF089A7e647b11cb9631efE",
"transaction_hash": "0xd346b32b83b35376d42a2464598fbf565fffb39e6569200f034a5e8342c532d7"
}
字段描述
merchant_id商店的商户代码。
api_keyAPI Key(供您交叉验证使用)。
request_id您的订单 ID(供您与内部发票对比)。
trans_id发票代码。用于更新对应的订单。
status始终为 "completed"
amountUSD 金额(字符串)。
token_symbol币种/代币(例如 USDT)。
token_price交易时的汇率(字符串)。如不可用,可能回退为 amount
token_amount实际转账的加密货币数量(字符串)。极少数情况下可能为 null
network_symbol网络(例如 BSC、TRC20、ETH、SOL、BINANCE_ID、OKX_UID、BYBIT_UID 等)。
from_address发送方钱包地址。
to_address接收方钱包地址(商店的)。
transaction_hash区块链上的交易哈希。

商户要求

  1. 接收 POST 请求,endpoint 对应 callback_url
  2. 解析 JSON body。
  3. 验证(可选但建议):将 merchant_idtrans_id 与您的内部数据进行对比。
  4. 更新订单:将与 trans_idrequest_id 匹配的订单标记为已支付;如需保存 transaction_hash
  5. 返回 HTTP 2xx(例如 200 OK),在 超时时间 内(例如 10 秒)。不需要 response body;系统只需 2xx 状态码即视为 webhook 成功。

如果您返回 4xx/5xx 或超时,系统会将 callback 标记为 failed 并启用 重试 机制(重试次数和间隔取决于后端配置)。


2. 发票过期时的 Webhook(status: expired)

当发票过渡到 expired 状态(超过 expires_at),系统向 callback_url 发送 POST,payload 更为简单:

Payload(JSON body)

{
"merchant_id": "MERCHANT_001",
"request_id": "order-1234",
"trans_id": "TID-ABC123DEF4567890",
"status": "expired",
"amount": "100"
}
字段描述
merchant_id商店的商户代码。
request_id您的订单 ID(如果创建发票时已提供)。
trans_id发票代码。用于更新对应的订单。
status始终为 "expired"
amountUSD 金额(字符串)。

您可以使用此信息将订单更新为"已过期"、取消预留等。您仍应返回 HTTP 2xx,以免系统将其视为错误。


3. Webhook 处理示例(Node.js)

app.post('/webhook/payment', express.json(), async (req, res) => {
const { merchant_id, api_key, trans_id, request_id, status, amount, transaction_hash } = req.body;

// 验证 merchant_id(completed 和 expired 均适用)
if (merchant_id !== process.env.PAYERSCAN_MERCHANT_ID) {
return res.status(401).send('Unauthorized');
}

// 验证 api_key(仅 completed — expired 不包含 api_key)
if (status === 'completed' && api_key !== process.env.PAYERSCAN_API_KEY) {
return res.status(401).send('Unauthorized');
}

// 通过 request_id 或 trans_id 查找订单
const order = await orderService.findByRequestId(request_id) || await orderService.findByTransId(trans_id);
if (!order) {
return res.status(404).send('Order not found');
}

if (status === 'completed') {
await orderService.markPaid(order, { transaction_hash, amount });
} else if (status === 'expired') {
await orderService.markExpired(order);
}

res.status(200).send('OK');
});

签名验证

为确保 webhook 确实来自 PayerScan,您应该验证签名或交叉验证数据。

  • HTTPS: 始终对 callback_url 使用 HTTPS 以防止窃听。
  • API Key 验证(仅 completed): completed webhook 的 payload 中包含您的 api_key。将其与您存储的 API key 进行比较以验证真实性。注意:expired webhook 不包含 api_key

幂等性

一个 trans_id 只会收到一个 completed 事件,但重试可能会重新发送。请确保您的处理逻辑具有 幂等性(多次更新是安全的,不会重复入账)。

Webhook 失败重试

当商户服务器返回错误(4xx/5xx)或超时,系统按以下计划重试 webhook:

次数时间
第 1 次立即(发票完成时)
第 2 次10 秒后
第 3 次30 秒后
第 4 次30 秒后
第 5 次60 秒后

最多 5 次尝试。5 次失败后,callback_status 变为 failed,不再进行后续尝试。

商户应确保其 endpoint 稳定并快速返回 2xx(< 10 秒),以避免不必要的重试。