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.
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.objectcarries the resource’s state at fire time, including itsstatus. 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(orstatus) 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 a2xx — 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.
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.

