Event types
| Event | Fires when |
|---|---|
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. |
Request format
We POST one JSON body to your configured URL with four BuffMoney headers:
x-bm-event— event type (see table above)x-bm-delivery— delivery ID (use for idempotency)x-bm-signature— HMAC-SHA256(body, endpointSecret)x-bm-timestamp— Unix timestamp at send time
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"
}
}Signature verification
Compute HMAC-SHA256 of the raw request body using your endpoint secret. Compare against the x-bm-signature header. Use a constant-time comparison to avoid timing attacks.
// 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")
);
}Response requirements
Your endpoint must return 2xx within 5 seconds. Failures (4xx, 5xx, timeout) trigger retries.
Retry policy
Failed deliveries retry with exponential backoff: 1m, 5m, 15m, 1h, 6h, 24h, 72h (7 attempts). Each retry carries the same x-bm-delivery header — use it for idempotency. After 72h, the delivery is marked dead and can be manually re-sent from the dashboard.
Idempotency
Your handler must be idempotent. Simplest pattern: maintain a deliveries table keyed by x-bm-delivery; check before processing.