Skip to content

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.

HeaderValue
Content-Typeapplication/json
X-PAY-TimestampUnix epoch seconds at delivery time
X-PAY-SignatureLowercase hex HMAC-SHA256(webhook_secret, "<ts>.<rawBody>")
{
"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 */ }
}
{
"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 */ }
}
eventFires when
payment.succeededBuyer paid; PSP confirmed.
payment.canceledBuyer abandoned, PSP rejected, or admin canceled before completion.
payment.failedPSP-side failure (insufficient funds, fraud rule).
subscription.canceledSubscription canceled by buyer or admin.
subscription.revokedSubscription went delinquent → access revoked (Polar dunning).
refund.succeededRefund was issued and PSP confirmed.
refund.failedRefund failed at the PSP (rare).

The metadata envelope contains:

  1. Whatever your product sent on POST /v1/payments — verbatim. Round-trips so your handler has the context (booking_ref, service_slug, etc.).
  2. PSP-specific enrichment added by the gateway from the underlying inbound event:

For Polar:

KeySourceNotes
polar_subscription_iddata.id (subscription events); data.subscription_id (order events)Stable across renewals. Match subscription.active events on this.
current_period_enddata.current_period_endRFC3339; use as licence/sub expiry.
polar_event_typetype (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.

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).

At-least-once delivery. 5xx triggers exponential backoff retry; 4xx dead-letters; 2xx is done. See Webhooks concept for the full retry policy.