Webhook
Webhook 允许您的系统从 PayerScan 接收自动实时通知。系统向商户创建发票时提供的 callback_url 发送 POST 请求。
事件类型
PayerScan 在 status 字段中返回 2 种主要事件:
completed— 支付成功:已确认与发票匹配的交易(区块链或 Binance Pay)。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_key | API Key(供您交叉验证使用)。 |
request_id | 您的订单 ID(供您与内部发票对比)。 |
trans_id | 发票代码。用于更新对应的订单。 |
status | 始终为 "completed"。 |
amount | USD 金额(字符串)。 |
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 | 区块链上的交易哈希。 |
商户要求
- 接收 POST 请求,endpoint 对应
callback_url。 - 解析 JSON body。
- 验证(可选但建议):将
merchant_id或trans_id与您的内部数据进行对比。 - 更新订单:将与
trans_id或request_id匹配的订单标记为已支付;如需保存transaction_hash。 - 返回 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"。 |
amount | USD 金额(字符串)。 |
您可以使用此信息将订单更新为"已过期"、取消预留等。您仍应返回 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):
completedwebhook 的 payload 中包含您的api_key。将其与您存储的 API key 进行比较以验证真实性。注意:expiredwebhook 不包含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 秒),以避免不必要的重试。