Skip to main content
This page covers payout callbacks. Payin callbacks use the same URL, the same headers and the same signature scheme — so the verification code below works unchanged — but they carry a different body and, unlike payouts, they are retried. Read that page too if you take payments.

When we send a callback

We send a callback whenever a live transaction reaches one of these final states: We do not send callbacks for in-progress states (pending, pending_approval, processing) — poll getPayoutStatus to observe those.
Payout callbacks are not sent for sandbox transactions. Payin callbacks are — so sandbox is where you can exercise your handler before real money moves.

Registering a URL

Set your Callback URL in Developer Tools. Requirements:
  • https:// only
  • Must resolve to a public IP (private, loopback, link-local, and cloud-metadata addresses are rejected — at save time and again at delivery time)

The request we send

Verify the signature

Recompute the HMAC and compare in constant time. Reject anything stale or mismatched.
Sign over the raw bytes you received, not a re-serialized JSON object. Re-serializing changes whitespace/ordering and the signature won’t match.

Idempotency

Every callback carries a unique x-pontis-event-id. We do not currently retry, but you should still:
  • Store recent event IDs (e.g. last 24 hours)
  • Reject duplicates silently with a 200 OK
  • Always respond with 2xx even on duplicates — non-2xx is logged as a delivery failure

Delivery failures

If your endpoint returns non-2xx or times out (10 s), the failure is logged on our side and the callback is not retried. Fall back to polling getPayoutStatus if you suspect a missed callback.

Local testing

While you’re developing, point the callback URL at any publicly reachable HTTPS endpoint — a request-bin service or an HTTPS tunnel in front of your local server both work. We do not accept private IPs or localhost URLs — the address must resolve to a public endpoint over HTTPS.