Idempotency

Making retries safe — the Idempotency-Key header and natural deduplication.

Two different mechanisms keep retries from causing damage. They're easy to confuse, so here's which applies where.

1. Natural deduplication on lead creation

POST /api/v1/leads needs no header. A lead is unique per owning agent on phone and email, so pushing the same contact twice returns the existing lead:

{ "created": false, "lead": { "object": "lead", "id": "3f1c2e00-…" } }
  • 201 with created: true — a new lead was inserted.
  • 200 with created: false — an existing lead matched; nothing was modified, except that a supplied external_ref is stamped if the lead didn't have one.

This makes a full re-sync safe: replaying your entire contact list creates only what's missing. It also means you can't use the endpoint to overwrite a lead — use PATCH for that.

2. Idempotency-Key on sends

POST /api/v1/leads/{leadId}/messages reaches a real person, so a retry after a network timeout must not double-message them. Send a unique key with each logical message:

curl -X POST https://replyfirst.ae/api/v1/leads/3f1c2e00-…/messages \
  -H "Authorization: Bearer $RF_ORG_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: crm-reply-8823" \
  -d '{ "text": "Free for a viewing tomorrow at 4pm?" }'

Reuse the same key when retrying the same message. The second call returns 200 with deduplicated: true and sends nothing:

{
  "object": "message_send",
  "lead_id": "3f1c2e00-…",
  "conversation_id": "8c2d41aa-…",
  "channel": "whatsapp",
  "deduplicated": true
}

Keys are up to 255 characters. Scope them to your own system — a database row id or a UUID per outgoing message works well. A key that's too long is ignored rather than rejected, so the send falls back to the behaviour below.

Generate a new key for a genuinely new message. Reusing a key means "this is the same message I already tried to send", and the second message will not go out.

Without a key

If you omit Idempotency-Key, an identical message to the same lead within a 5-minute window is still deduplicated. That covers double-submits and fast retries, while letting you deliberately send "ok" twice an hour apart. It is a safety net, not a substitute for a key.

What is not idempotent

PATCH /api/v1/leads/{leadId} and the handoff actions are naturally idempotent — they set state rather than appending to it. Applying { "ai_paused": true } twice leaves the lead paused; accepting an already-accepted handoff leaves it accepted. No key needed.

Next

On this page