Skip to main content
By the end of this guide you’ll have a running endpoint that receives a real event from Ionic and verifies its signature. Everything here is in test mode — use a sk_v1_test_… key.

1. Stand up a receiver

A webhook endpoint is an HTTPS URL that accepts a POST. Start with a handler that captures the raw body (the signature covers the exact bytes, so a body that’s been parsed and re-serialized by framework middleware will not verify) and acknowledges quickly. On Node, webhooks.unwrap() from the server SDK checks the signature and replay window, then returns a typed event:
Express
Not on Node? The signature scheme is standard HMAC-SHA256 over {id}.{timestamp}.{body}Verify signatures has the full manual implementation in Node, Python, and Go.
Developing locally? Endpoints must be HTTPS, so localhost alone won’t work. Put an HTTPS tunnel in front of your server and register the tunnel’s public URL in the next step.

2. Register the endpoint

Tell Ionic where to deliver and which events you want. For this walkthrough, subscribe to payment_intent.created.
The response includes a secret that starts with whsec_. This is the only time it’s shown. Save it where your server can read it:

3. Verify every delivery

Your handler already does: unwrap recomputes the signature over the raw body with constant-time comparison, rejects deliveries older than the replay window, and throws on anything that doesn’t match. Don’t skip verification: an unverified endpoint will accept forged events from anyone who learns your URL. If you’re implementing it yourself, Verify signatures walks through the same checks step by step.

4. Trigger an event

Create a payment intent. This needs only an amount and currency — no card — and emits payment_intent.created.
Within a moment your endpoint receives the event and logs:

5. You’re receiving webhooks

That’s the whole loop: register, verify, receive. Because unwrap returns a typed event, handling a specific type narrows the payload for you:
From here:

Event catalog

Subscribe to the events you actually handle — payment_intent.succeeded, refund.succeeded, and the rest.

Idempotency & retries

Make your handler safe against duplicate and out-of-order deliveries before you ship.

Developing locally

Ionic can’t deliver webhooks to localhost. Two ways to work while local:
  • Read the current status. Fetch the resource directly when you need an answer — for example GET /v1/checkout/sessions/{id} returns payment_status on demand. This works for most local testing.
  • Tunnel your receiver. Expose your local port (for example ngrok http 3000), register the tunnel URL as a webhook endpoint, and update the endpoint’s url whenever the tunnel address changes.
In production, fulfill from a verified webhook so paid orders do not depend on the buyer returning to your site.