Outbound webhook payload
The gateway delivers a signed POST to your app’s webhook_url whenever a payment or refund changes state. Verification scheme + retry behavior covered in Verifying webhooks and Webhooks concept.
Headers
Section titled “Headers”| Header | Value |
|---|---|
Content-Type | application/json |
X-PAY-Timestamp | Unix epoch seconds at delivery time |
X-PAY-Signature | Lowercase hex HMAC-SHA256(webhook_secret, "<ts>.<rawBody>") |
Payload shape (payment events)
Section titled “Payload shape (payment events)”{ "event": "payment.succeeded", "payment_id": "abc123…", "status": "succeeded", "amount": 2900, "currency": "USD", "method": "polar", "provider": "polar", "provider_ref": "polar_chk_…", "timestamp": "2026-05-09T12:34:56Z", "metadata": { /* see below */ }}Payload shape (refund events)
Section titled “Payload shape (refund events)”{ "event": "refund.succeeded", "refund_id": "def456…", "payment_id": "abc123…", "amount": 1500, "currency": "USD", "provider": "polar", "provider_ref": "<PSP refund id>", "reason": "customer requested", "timestamp": "2026-05-09T13:00:00Z", "metadata": { /* original payment metadata, unenriched */ }}Event types
Section titled “Event types”event | Fires when |
|---|---|
payment.succeeded | Buyer paid; PSP confirmed. |
payment.canceled | Buyer abandoned, PSP rejected, or admin canceled before completion. |
payment.failed | PSP-side failure (insufficient funds, fraud rule). |
subscription.canceled | Subscription canceled by buyer or admin. |
subscription.revoked | Subscription went delinquent → access revoked (Polar dunning). |
refund.succeeded | Refund was issued and PSP confirmed. |
refund.failed | Refund failed at the PSP (rare). |
Metadata enrichment
Section titled “Metadata enrichment”The metadata envelope contains:
- Whatever your product sent on
POST /v1/payments— verbatim. Round-trips so your handler has the context (booking_ref,service_slug, etc.). - PSP-specific enrichment added by the gateway from the underlying inbound event:
For Polar:
| Key | Source | Notes |
|---|---|---|
polar_subscription_id | data.id (subscription events); data.subscription_id (order events) | Stable across renewals. Match subscription.active events on this. |
current_period_end | data.current_period_end | RFC3339; use as licence/sub expiry. |
polar_event_type | type (raw) | Original Polar event name for debugging. |
For NICEPAY / PayPal: no enrichment fields added today. If your product needs specific fields, surface the gap and we’ll wire it.
Idempotency
Section titled “Idempotency”Same (payment_id, event) may be delivered twice if your endpoint times out and the gateway retries. Dedupe on that tuple — don’t dedupe on a delivery id (none of those exist in the payload).
Reliability
Section titled “Reliability”At-least-once delivery. 5xx triggers exponential backoff retry; 4xx dead-letters; 2xx is done. See Webhooks concept for the full retry policy.