Skip to main content
Networks fail. If your request to POST /v1/events times out, you can’t tell whether Relay stored the event. Send an Idempotency-Key header and retry with the same key: Relay stores the event once, whatever happens to the responses.
Sequence diagram between your app and the Relay API: your app sends POST /v1/events with Idempotency-Key invoice-inv_123-paid; Relay stores event evt_2Lk9xQ and remembers the key and response for 24 hours, but the 202 is lost and the request times out; your app retries with the same key; the key matches, so Relay stores no new event and returns the first 202 with Idempotent-Replayed: true.

The first response is lost, so your app retries with the same key. Relay stores nothing new and replays the first response.

How keys are matched

Use a key that names the thing that happened, such as invoice-inv_123-paid, rather than a random value made per attempt. Then a retry from a different process, or after a deploy, still matches.

Duplicates on the receiving side

Relay delivers at least once. A delivery can arrive twice, for example when your endpoint processed it but its 200 was lost on the way back. Every event has a stable id (evt_...), and every delivery carries it in the Relay-Event-Id header. Record the ids you have processed and skip any you have already seen.
Sequence diagram between Relay and your webhook endpoint: Relay delivers invoice.paid with Relay-Event-Id evt_2Lk9xQ; the endpoint handles it and records the id, but its 200 is lost; Relay retries with the same event id; the endpoint finds evt_2Lk9xQ already recorded, skips the work and returns 200; the delivery is marked delivered.

Your endpoint's 200 is lost, so Relay delivers the event again. The endpoint has already recorded the event id, skips the work and answers 200.

Answer 200 for a duplicate. An error makes Relay retry it again.