Skip to main content
Ionic delivers events at least once and does not guarantee order. A correct handler is therefore idempotent (safe to run twice on the same event) and never infers state from the order events arrive in. This page shows how.

Deduplicate on the event id

The envelope id (the evt_ value) identifies the event. It is stable across every redelivery of that event, so it’s your deduplication key.
Record event.id in the same transaction as the work it triggers. If the work commits but recording the id doesn’t (or vice versa), a redelivery either double-processes or is wrongly skipped. One transaction keeps “did the work” and “saw the event” in lockstep.
Two different ids travel with a delivery — don’t confuse them. The envelope id (evt_…) identifies the event and is what you dedupe on. The webhook-id header (msg_…) identifies the delivery and is used for signature verification. Ionic also sets the event id as the delivery’s idempotency key, so a republished event collapses to one delivery — but your handler must still dedupe, because retries are by design.

Don’t trust arrival order

Because order isn’t guaranteed, payment_intent.succeeded can arrive before the payment_intent.created that preceded it. Never drive a state machine off “which event came first.” Instead:
  • Read the snapshot. data.object carries the resource’s state at fire time, including its status. Act on that, not on the event name’s position in a sequence.
  • Compare, don’t assume. If you keep your own copy of the resource, use the snapshot’s updated_at (or status) to ignore an event that’s older than what you’ve already recorded.
  • Re-fetch when it matters. For a decision that must reflect the current truth (e.g. “is this subscription active right now”), call GET /v1/<resource>/{id} rather than relying on any single event.

Retries

If your endpoint doesn’t return a 2xx — an error status, a timeout, or an unreachable host — Ionic retries the delivery on an exponential backoff over an extended window. Each retry carries the same webhook-id and the same envelope id, so your dedup logic makes a retry after a partial success a no-op.
A 2xx is an acknowledgment: Ionic stops retrying once it sees one. Return 2xx only after you’ve durably stored the event. If you return 2xx and then crash before doing the work, the event is gone. The safe pattern is store-then-ack: persist the raw event, return 2xx, process asynchronously.

Endpoint disabling and recovery

An endpoint that fails for a sustained period is automatically disabled to stop wasting retries on a dead URL. You can re-enable it once it’s healthy, and recover the events it missed:
  • Re-enable with POST /v1/webhook_endpoints/{id} ({"disabled": false}).
  • Fill the gap by listing resources created during the outage and comparing them with the event IDs your application processed.

A correct handler, end to end