The GramPay-Event header always carries the event name, but Phoenix currently uses two JSON body shapes: an invoice envelope and a flat billing payload.
Invoice-event envelope
{
"update_id": 1024,
"update_type": "invoice_paid",
"request_date": "2026-07-17T09:12:33Z",
"payload": { "public_id": "GU91XHr8AUNQ8l9r", "status": "paid" }
}
payload is the same invoice object returned by the API. Supported invoice events are:
| Event | Sent when |
|---|---|
invoice_paid | A blockchain payment is confirmed |
invoice_expired | expires_in has elapsed |
invoice_cancelled | The seller cancels the invoice |
For invoice_paid, use payload.payload to correlate your order; paid_token, paid_source, paid_at and tx_hash describe the payment. Never correlate by amount or time alone.
Billing-event body
Billing events do not use update_id, update_type, request_date or nested payload. Phoenix takes the notification metadata and adds these top-level fields:
{
"type": "billing.account_unserviceable",
"account_id": "<uuid>",
"reason": "processing_credit_depleted"
}
Exact metadata depends on the event. Supported names are billing.balance_topup_applied, billing.package_activated, billing.package_queued, billing.current_package_expiring_soon, billing.current_package_expired, billing.next_package_activated, billing.current_package_low_payments, billing.processing_credit_low, billing.processing_credit_depleted, billing.account_unserviceable and billing.account_service_restored.
At minimum, alert on billing.account_unserviceable and clear the alert on billing.account_service_restored, because the former means createInvoice returns 402.
Test event
webhook_test uses the invoice-style envelope with a payload containing the app UUID, name and test message. Return 2xx after signature verification without running fulfilment logic.
Dispatch safely
Dispatch by GramPay-Event or by update_type for invoice/test bodies and type for billing bodies. Unknown types should be logged and acknowledged. All handlers must be idempotent.