Skip to content

Idempotency

Idempotency prevents the same event from being processed twice. This is critical for transactional emails -- a user should receive exactly one claim notification per claim event, even if your backend retries the API call.


How It Works

When you include an idempotency_key in your event, Synapse checks whether that key has been seen before. If it has, the event is acknowledged (202 Accepted) but not processed again.

json
{
"event_name": "claim_submitted",
"external_id": "worker_67890",
"attributes": {"reference_number": "WIC-2026-0001"},
"idempotency_key": "claim_submitted_WIC-2026-0001"
}

Key Format

Idempotency keys are stored in Redis with the following format:

idempotency:{tenant_id}:{flow_id}:{contact_id}:{key}
ComponentDescription
tenant_idYour workspace's internal tenant UUID
flow_idThe ID of the matched flow
contact_idThe contact's internal UUID
keyThe idempotency_key you provided
Note

Idempotency is scoped per flow and per contact. The same idempotency_key can trigger different flows for the same contact, or the same flow for different contacts, without conflict.


TTL (Time to Live)

Idempotency keys expire after 7 days by default. After expiry, the same key can be used again and will be processed as a new event.

This covers the vast majority of use cases -- if your backend retries a failed API call, it will do so within seconds or minutes, not days.


Generating Keys

PatternExampleUse Case
Event type + unique IDclaim_submitted_WIC-2026-0001Business events with natural unique identifiers
Event type + user + timestampotp_sent_user_123_1711900800Events that may repeat but should be unique per time window
UUID550e8400-e29b-41d4-a716-446655440000When no natural key exists

Best Practices

  1. Use a natural business key when available. order_completed_ORD-2026-0042 is better than a random UUID because it's meaningful and debuggable.
  2. Include the event type in the key to prevent collisions between different event types sharing the same business ID.
  3. Generate the key on your backend, not in the client. This ensures consistency across retries.
python
# Python example: generating an idempotency key
idempotency_key = f"claim_submitted_{reference_number}"
javascript
// JavaScript example
const idempotencyKey = `claim_submitted_${referenceNumber}`;

Concurrent Event Handling

Synapse handles concurrent identical events safely using a two-layer approach:

Layer 1: Redis Check (Fast Path)

Before processing, the worker checks Redis for the idempotency key. This is a sub-millisecond operation that catches the vast majority of duplicates.

Event A arrives → Redis SET NX idempotency:...:key → Success → Process
Event B arrives → Redis SET NX idempotency:...:key → Exists → Skip

Layer 2: Database Constraint (Safety Net)

The flow_trips table has a unique constraint on (flow_id, contact_id, trigger_event_id). Even if two workers pass the Redis check simultaneously (a rare race condition), the database constraint prevents duplicate trips.

sql
UNIQUE(flow_id, contact_id, trigger_event_id)

If the database constraint catches a duplicate, the worker logs a warning and acknowledges the message without error.

Tip

Always provide an idempotency key for transactional events (claims, orders, payments). For promotional events (newsletter triggers, campaign sends), idempotency may not be necessary since duplicate sends are less harmful.


Duplicate Detection Response

When a duplicate idempotency_key is detected at the API level:

bash
curl -X POST https://synapse-api.pyrx.tech/v1/events \
-H "X-WORKSPACE-ID: ws_k7x9m2p4" \
-H "X-API-KEY: psk_live_..." \
-H "Content-Type: application/json" \
-d '{"event_name": "claim_submitted", "external_id": "worker_67890", "idempotency_key": "claim_submitted_WIC-2026-0001"}'
json
{
"detail": "Event with this idempotency key has already been processed",
"code": "duplicate_event"
}

Status: 409 Conflict

Note

A 409 response means the original event was already accepted. Your backend should treat this as a success -- the email will be (or has been) sent based on the original event.


Events Without Idempotency Keys

If you omit the idempotency_key field, Synapse processes the event unconditionally. The database-level unique constraint on flow_trips still prevents duplicate trips for the same event record, but repeated API calls will create separate event records and potentially trigger separate flow trips.

Warning

For transactional emails (OTP, claim notifications, payment receipts), always include an idempotency key. Without one, network retries or application bugs can cause duplicate emails.