Optional analytics on public pages. No session recording. Support works either way. Privacy details
Webhooks
Events can arrive more than once or out of order. Deduplicate on event id and re-read the invoice.
| Event | When |
|---|---|
| invoice.created | Invoice created (only to endpoints that list it or "*") |
| invoice.payment_detected | Qualifying transfer seen |
| invoice.partial | Below the amount due; amountRemaining says how much is still owed |
| invoice.paid | Settled in full — fulfill. paymentException is overpaid if the customer sent more |
| invoice.expired | Window ended |
| invoice.cancelled | Unpaid invoice cancelled |
| invoice.reverted | Payment invalidated |
| invoice.manual_review | Needs your decision — paymentException is underpaid or late_payment, or null for a settlement exception |
{
"id": "evt_7Qx2mK9pL4sT8vW1yZ3aB6cD",
"object": "event",
"type": "invoice.paid",
"created": "2026-09-23T10:42:00.000Z",
"apiVersion": "2026-09-23",
"livemode": false,
"environment": "sandbox",
"data": {
"id": "inv_1048",
"object": "invoice",
"invoiceId": "inv_1048",
"status": "paid",
"chain": "sepolia",
"tokenSymbol": "USDC",
"fiatAmount": 150,
"fiatCurrency": "ZAR",
"cryptoAmount": "8.42",
"amountPaid": "8.42",
"amountRemaining": null,
"overpaidAmount": null,
"amountRefunded": "0",
"paymentException": null,
"customerReference": "order-1048",
"metadata": { "orderId": "1048" }
}
}Every event is kept for 30 days. After downtime, page through GET /events?createdAfter=… and process anything you have not stored, deduplicating on id.
const response = await fetch(`${process.env.BOSPAY_API_BASE}/events?type=invoice.paid&createdAfter=2026-09-23T00:00:00Z&limit=50`, {
method: "GET",
headers: {
"Content-Type": "application/json",
"BOSPay-Version": "2026-09-23",
"X-API-Key": process.env.BOSPAY_API_KEY,
}
});
if (!response.ok) {
throw new Error(`BOSPay request failed: ${response.status}`);
}
const data = await response.json();