Idempotent Notification Requests
Safely retry a notification send without creating a duplicate.
Scope
Idempotency support exists on exactly one endpoint today:
POST /v1/apps/{app_id}/notifications. No other endpoint (Templates,
Segments, Webhooks, Devices, Apps) reads or honors an Idempotency-Key
header - sending one anywhere else is simply ignored.
How it works
Pass an Idempotency-Key header with any value unique to that logical send
(a UUID is a good default):
curl -X POST https://your-instance/v1/apps/{app_id}/notifications \
-H "Authorization: Bearer $REST_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"target_type":"device_ids","target_value":["dev_123"],"ad_hoc_title":"Hi","ad_hoc_body":"Hello"}'-
First call with a given key: the notification is created normally, returns
201 Created. The key, scoped to the App, is stored (Redis) along with a hash of the request body. -
Repeat call with the same key and the same body: no new notification is created - the original notification is returned instead, with
200 OK(not201). Safe to retry after a timeout or dropped connection. -
Repeat call with the same key but a different body: rejected with
409 Conflict:{ "error": { "code": "idempotency_conflict", "message": "..." } }This catches the case where a client accidentally reuses a key for a different send - it is treated as a bug in the caller, not silently processed.
-
No
Idempotency-Keyheader sent at all: idempotency is skipped entirely; every call creates a new notification. The header is optional - use it whenever a retry is possible (network timeout, ambiguous response), skip it for genuinely one-off sends you never intend to retry.
Key lifetime
A key is valid for 24 hours after the first successful call. After that window, reusing the same key value starts a brand-new send (treated as if it were never used). Don't rely on keys as long-term deduplication beyond one day.
What "success" means for storage
The key is only marked as used after the notification and its per-device delivery records are created successfully. If the request fails partway through, the key is not consumed - retrying with the same key attempts a fresh creation rather than replaying a failed attempt.
Practical guidance
- Generate one fresh key per logical send (e.g. per user action, per scheduled job run) - never reuse a key across sends that are meant to be distinct.
- Reuse the exact same key only when retrying a request you're not sure reached the server (timeout, connection reset) - that's the case this feature exists for.
- This mechanism is specific to Notifications; there is no equivalent guarantee yet for Template, Segment, or Webhook writes.