Billium
Webhooks

Webhooks

Real-time HTTP notifications for invoice and payment events.

Billium uses webhooks to push event notifications to your server. Instead of polling the API to check whether an invoice was paid, you register an HTTPS endpoint and Billium calls it whenever something happens.

Anatomy of a webhook request

Every delivery is an HTTP POST with a JSON body and the following headers:

HeaderDescription
Content-Typeapplication/json
x-signatureHMAC-SHA256 signature for verifying authenticity
x-webhook-idID of the webhook configuration that triggered this delivery
x-event-idUnique ID of the event
x-delivery-idUnique ID of this specific delivery attempt

Payload structure

Every event payload follows the same envelope:

{
  "event": "invoice.paid",
  "id": "evt_a1b2c3d4e5f6",
  "data": { ... },
  "timestamp": "2025-03-15T04:12:00.000Z"
}
FieldDescription
eventEvent type string — see the event catalog.
idUnique event ID. Store it to deduplicate retries.
dataEvent-specific payload — different shape per event type.
timestampISO 8601 timestamp of when the event was emitted.

Delivery behavior

  • Your endpoint must return a 2xx status within the configured timeout (default 30 seconds, configurable per webhook).
  • On failure, Billium retries with exponential backoff starting at 60 seconds, up to 5 attempts total.
  • 404, 410, and 451 responses are treated as a signal to stop and move the delivery to the dead-letter queue immediately — no retries.
  • Other 4xx responses (400, 401, 403, etc.) are treated as permanent failures and also skip retries.
  • 5xx responses and network timeouts are retried.
  • After all retries are exhausted, the delivery is moved to a dead-letter queue. Failed deliveries are retained for 30 days; successful deliveries for 7 days.

Make your webhook handler idempotent — Billium may deliver the same event more than once on retry. Use event.id to deduplicate.

Delivery guarantees

Webhook delivery pipelineWhen an invoice or payment event fires, Billium records it, processes the pending delivery, signs it, and POSTs it to your endpoint. Failures retry up to five times with exponential backoff before landing in the dead-letter queue.Event recordedinvoice.paidatomic w/ state changeDurable logretained until sentdurable events onlyDelivery workersign + POSTtimeout 30 sYour endpoint2xx → DELIVEREDRetry5xx / timeout, max 5Dead-letter queue404 / 410 / 451 / 4xx2xx5xxpermanentexponential backoff, 60 s base
Durable events flow through the retry log and survive Billium-side restarts. Best-effort events skip it and dispatch immediately.

Not all events are delivered with the same guarantees. Billium classifies events into two categories:

ClassGuaranteeBehavior
DurableAt-least-once.Billium records the event atomically with the state change that caused it and retries delivery until your endpoint responds with a 2xx. Events survive Billium-side restarts and transient failures. Your handler will see each event at least once, possibly more — always deduplicate by event.id.
Best-effortAt-most-once.Billium dispatches the event once as soon as it happens, without a retry log. If something goes wrong between the event and dispatch it may be dropped. Treat these as a performance optimization for live UI updates, not as a source of truth.

Durable events (11): invoice.paid, invoice.underpaid, invoice.overpaid, invoice.expired, invoice.cancelled, payment.detected, payment.confirmed, payment.paid, payment.underpaid, payment.overpaid, payment.expired.

Best-effort events (4): invoice.created, invoice.updated, payment.created, payment.updated.

Drive your business logic off durable events only. Use invoice.paid / invoice.expired / invoice.cancelled (and the payment.* durables) as the source of truth for fulfillment, refunds, and accounting. Best-effort events are intended for checkout UI refreshes and merchant dashboards — they are fire-and-forget and may be dropped.

Signature verification

Each delivery includes an x-signature header. Verify it before processing the payload to ensure the request came from Billium and was not tampered with.

x-signature: t=1741406520,v1=a3f9...

See Verify signatures for the full scheme and code examples, or use the @billium/node SDK which handles it for you.

Managing webhooks

You can create, update, and delete webhooks from the dashboard or with the @billium/node SDK. The REST endpoints are all under your merchant base path:

ActionEndpoint
List webhooksGET /api/v1/merchants/merchant/{merchantId}/webhooks
Create webhookPOST /api/v1/merchants/merchant/{merchantId}/webhooks
Update webhookPATCH /api/v1/merchants/merchant/{merchantId}/webhooks/{webhookId}
Delete webhookDELETE /api/v1/merchants/merchant/{merchantId}/webhooks/{webhookId}
Send test pingPOST /api/v1/merchants/merchant/{merchantId}/webhooks/{webhookId}/ping

Webhook management endpoints require a secret key (sk_*). Public keys (pk_*) will be rejected.

The secret (whsec_...) is returned once, inside webhookSecrets[0].secretKeyPreview, at creation time. Store it immediately — the backend only keeps a hashed copy afterwards.

Next steps

On this page