Skip to main content

Idempotency

Every send endpoint accepts an Idempotency-Key header. It exists for one situation: your request timed out, or the connection dropped, and you do not know whether the message went out. Retrying blindly risks sending twice; not retrying risks not sending at all. An idempotency key removes the guess.

Sending the same request twice — for example because a network timeout made you retry — could deliver the same message twice. To make a retry safe, send an Idempotency-Key header whose value is unique to that request (for example order-1042-receipt):

  • The first request with a given key is processed normally and its response is stored.
  • Any later request with the same key (within 24 hours) is not reprocessed — it returns the original response unchanged, plus an Idempotent-Replayed: true response header. The side effect happens only once.
  • After 24 hours the key expires and may be reused.

The key can be any unique string up to 256 characters. A UUID works, but a value derived from the entity the request is about — like order-1042-receipt — is easier to regenerate identically on a retry. Keys are scoped per endpoint, so the same value can be reused independently across the send-email, send-SMS, and send-event endpoints without colliding.

Reusing a key with a different payload

Reuse a key with a different request body and the request is rejected with 409 invalid_idempotent_request, which protects you from receiving the wrong cached response. Use a fresh key for a genuinely different request.

Idempotency error responses

StatusnameWhen
400invalid_idempotency_keyThe key is empty or longer than 256 characters.
409invalid_idempotent_requestThe key was already used with a different request body.
409concurrent_idempotent_requestsAn earlier request with the same key is still being processed — retry shortly.

Which endpoints support it

EndpointSuggested key
POST /email/sendThe thing the email is about — order-1042-receipt
POST /sms/sendotp-{user_id}-{attempt}
POST /whatsapp/sendbooking-{booking_id}-confirmation
POST /push/sendshipment-{shipment_id}-out-for-delivery
POST /events/sendYour own event ID from the source system

Keys are scoped per endpoint, so the same string can be used independently on /email/send and /sms/send without colliding.

Choosing a key

The best key is one your retry can regenerate identically without having stored anything. A key derived from the entity the request is about — an order ID, a booking reference, a shipment number — survives a process restart mid-retry. A random UUID only works if you persist it before the first attempt.

Avoid keys derived from the current time: a retry a second later produces a different key and defeats the mechanism entirely.

Idempotency is not deduplication

The 24-hour window protects retries of one logical request. It is not a general "never send this person the same thing twice" guard — after 24 hours the key expires and a request carrying it is processed as new. For genuine deduplication across longer spans, key on your own side.

Creation endpoints such as POST /contacts do not use idempotency keys; they are protected by uniqueness constraints instead, and a duplicate returns 409.