事件类型
| 事件 | 触发时机 |
|---|---|
invoice.paid | Invoice transitioned to paid after a successful CNY collection. |
invoice.refunded | Invoice was refunded (manual or sandbox auto-refund). |
payment_order.paid | A specific payment order succeeded — fires before invoice.paid. |
payment_order.failed | A payment order failed or expired. |
payout.completed | Settlement payout for the period was wired. |
subscription.activated | Merchant activated a paid subscription. |
subscription.cancelled | Merchant cancelled a subscription. |
请求格式
我们 POST 一条 JSON body 到你配置的 URL,带上 4 个 BuffMoney 头:
x-bm-event— 事件类型(见上表)x-bm-delivery— 本次投递的 ID(用于去重)x-bm-signature— HMAC-SHA256(body, endpointSecret)x-bm-timestamp— 发送时的 Unix 时间戳
POST /your-endpoint
content-type: application/json
x-bm-event: invoice.paid
x-bm-delivery: dlv_8f2a...
x-bm-signature: 5f3a09c84d9e...
x-bm-timestamp: 1779850000
{
"id": "dlv_8f2a...",
"event": "invoice.paid",
"createdAt": "2026-05-25T10:50:00Z",
"data": {
"invoiceId": "...",
"invoiceNumber": "INV-...",
"merchantId": "...",
"collectionCurrency": "CNY",
"collectionAmountMinor": "72",
"settlementCurrency": "USD",
"netSettlementMinor": "10",
"paidAt": "2026-05-25T10:50:00Z",
"channel": "wechat"
}
}签名校验
用你的 endpoint secret 对 raw request body 做 HMAC-SHA256,跟 x-bm-signature 头比对。constant-time 比对避免 timing attack。
// Node.js — verify x-bm-signature
import crypto from "crypto";
export function verifyBuffMoneyWebhook(
rawBody: string,
signatureHeader: string,
endpointSecret: string
): boolean {
const computed = crypto
.createHmac("sha256", endpointSecret)
.update(rawBody, "utf8")
.digest("hex");
// constant-time compare
return crypto.timingSafeEqual(
Buffer.from(signatureHeader, "hex"),
Buffer.from(computed, "hex")
);
}响应要求
你的 endpoint 必须 5 秒内返回 2xx。失败(4xx / 5xx / 超时)会触发重试。
重试策略
失败递送指数退避重试:1m, 5m, 15m, 1h, 6h, 24h, 72h(共 7 次)。每次重试携带相同的 x-bm-delivery 头,用它去重。72 小时后投递放弃,事件标记 dead,可在后台手动重发。
幂等性
你的 handler 必须能处理重复递送。最简单的方式:在你侧建一个 deliveries 表,存 x-bm-delivery,处理前先查是否已存在。