Payload schema

The exact JSON body every webhook delivery carries — rendered from the canonical payload snapshot.

Every webhook delivery carries the same JSON payload, regardless of event type. The event type is in the status field and is also mirrored in the X-Webhook-Event header.

Fields#

FieldTypeDescriptionExample
invoice_idstring (UUID)Primary identifier of the invoice.a1b2c3d4-…
merchant_idstring (UUID)Merchant that owns the invoice.11111111-…
external_idstring | nullMerchant-supplied idempotency key.order-2026-0117-001
descriptionstring | nullFree-form description copied from the create-invoice request.Order #1234
metadataobject | nullArbitrary JSON metadata from the create-invoice request. Use this to reconcile with your own records.{"order_id":"1234","sku":"ABC"}
statusstring (enum)Current invoice status. See event types table for the state machine.confirmed
fiat_amountstring (decimal)Requested fiat amount as a fixed-decimal string.49.99
fiat_currencystring (ISO 4217)Fiat currency code, uppercase.USD
crypto_amountstring (decimal)Computed crypto amount at invoice-create time, rounded to the currency's payment precision.0.00082145
crypto_currencystringCryptocurrency code, uppercase.BTC
crypto_networkstringNetwork name, lowercase.bitcoin
deposit_addressstringAddress the buyer must fund.bc1q…
exchange_ratestring (decimal)Crypto per one fiat unit at invoice-create time.0.0000164
created_atstring (ISO 8601) | nullInvoice creation timestamp (UTC).2026-04-17T12:00:00Z
expires_atstring (ISO 8601) | nullInvoice expiry timestamp (UTC).2026-04-17T12:30:00Z
confirmed_atstring (ISO 8601) | nullConfirmation timestamp if the invoice reached a terminal paid state.2026-04-17T12:14:03Z

Example#

{
  "invoice_id": "3f1a9b0e-2c7b-4a1c-9d2a-1f0e8a7b6c5d",
  "merchant_id": "11111111-2222-3333-4444-555555555555",
  "external_id": "order-2026-0117-001",
  "description": "Order #1234 — 2 t-shirts",
  "metadata": { "order_id": "1234", "sku": "ABC" },
  "status": "confirmed",
  "fiat_amount": "49.99",
  "fiat_currency": "USD",
  "crypto_amount": "0.00082145",
  "crypto_currency": "BTC",
  "crypto_network": "bitcoin",
  "deposit_address": "bc1qx0...",
  "exchange_rate": "0.0000164",
  "created_at": "2026-04-17T12:00:00Z",
  "expires_at": "2026-04-17T12:30:00Z",
  "confirmed_at": "2026-04-17T12:14:03Z"
}

Merchant-supplied fields#

Three fields are passthrough from your original POST /api/invoices request:

  • external_id — use this for idempotency on your side. If you create an invoice twice with the same external_id, you get the same invoice back.
  • description — free-form; show to operators.
  • metadata — arbitrary JSON. Put your internal order ID, customer reference, SKU, whatever you need for reconciliation. See Reconcile with metadata.

These fields make the webhook self-contained: a receiver can reconcile against its own records without a second round-trip to the platform.

Headers#

Every delivery carries:

  • Content-Type: application/json
  • X-Webhook-ID — UUID per delivery attempt (stable across retries of the same event).
  • X-Webhook-Timestamp — Unix epoch seconds at dispatch.
  • X-Webhook-Signaturesha256=<hex> HMAC. See Signature scheme.
  • X-Webhook-Event — the event type string (e.g. invoice.confirmed).

Stability#

The payload schema is versioned. Adding a field is a backward-compatible change and does not bump the major version. Removing or renaming a field will always bump the major version and be announced in the changelog with a deprecation window.