BuffMoneyBuffMoney
Build

Webhooks.

Subscribe to BuffMoney events. HMAC-SHA256 signature verification, auto-retry, idempotent delivery.

Event types

EventFires when
invoice.paidInvoice transitioned to paid after a successful CNY collection.
invoice.refundedInvoice was refunded (manual or sandbox auto-refund).
payment_order.paidA specific payment order succeeded — fires before invoice.paid.
payment_order.failedA payment order failed or expired.
payout.completedSettlement payout for the period was wired.
subscription.activatedMerchant activated a paid subscription.
subscription.cancelledMerchant cancelled a subscription.

Request format

We POST one JSON body to your configured URL with four BuffMoney headers:

  • x-bm-eventevent type (see table above)
  • x-bm-deliverydelivery ID (use for idempotency)
  • x-bm-signatureHMAC-SHA256(body, endpointSecret)
  • x-bm-timestampUnix 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.