Idempotency
Network failures are inevitable. The Mojeeb Public API uses an Idempotency-Key header to make retries safe — the same key plus the same body returns the original response, never sends twice.
How do I make my retries safe?
Send any client-chosen string up to 255 characters in the Idempotency-Key header on every POST that creates or sends something:
curl -X POST https://api.mojeeb.app/v1/whatsapp/templates \
-H "Authorization: Bearer mk_live_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-12345-confirmation" \
-d '{
"agent_id": "12345678-1234-1234-1234-123456789012",
"from": "+15557654321",
"to": "+15551234567",
"template": { "name": "order_shipped", "language": "en" }
}'
The header is optional but strongly recommended for any send. Without it, a network retry can produce a duplicate message.
What happens on retry?
The behavior depends on whether the original request finished and whether the body matches:
| Scenario | Response | HTTP |
|---|---|---|
| First send with this key | Normal response | 202 |
| Same key + same body, original still in-flight | idempotency_key_in_progress (with Retry-After) | 409 |
| Same key + same body, original completed | Replays original response, Idempotent-Replayed: true header | Original status |
| Same key + different body | idempotency_key_in_use_with_different_params | 422 |
| Different key | Process as new request | — |
How long are responses cached?
24 hours. After that, the same idempotency key is treated as new. This means:
- A retry within 24h of the original always replays
- A retry after 24h is a fresh send (and may produce a duplicate if the original succeeded)
For most workflows 24h is more than enough — pick stable keys tied to your business operation, not random UUIDs that change between attempts.
How do I pick good idempotency keys?
A good key is stable, unique, and tied to a single business operation.
Good:
order-12345-confirmationsignup-456-welcomeappointment-reminder-2026-04-30-user-789
Bad:
- Timestamps (each retry gets a different one — defeats the point)
- Random UUIDs generated at request time (same problem)
- Generic strings like
retry-1(collisions across operations) - Keys reused across different message bodies (triggers
422mismatch)
If you don't have a natural stable identifier, generate one once at the start of your operation and reuse it for every retry of that specific send.
What does a mismatch look like?
When the same key is reused with a different body, the API rejects the request and gives you the correlation id of the original send so you can find it in your own logs:
{
"error": {
"type": "idempotency_error",
"code": "idempotency_key_in_use_with_different_params",
"message": "The idempotency key was used previously with a different request body. Use a different key for this operation.",
"correlation_id": "req_01KQE...",
"original_correlation_id": "req_01KQE..."
}
}
original_correlation_id is the trace id of the request that originally claimed the key. Search your application logs for that id to see what was sent.
What does an in-progress retry look like?
If you retry while the original send is still being processed (rare, usually only inside a few seconds):
{
"error": {
"type": "idempotency_error",
"code": "idempotency_key_in_progress",
"message": "A request with this idempotency key is currently being processed. Retry shortly.",
"correlation_id": "req_01KQE..."
}
}
The response also carries a Retry-After header in seconds. Wait that long, then retry the same key + same body — you'll either get a 202 (if the original is now complete) or another 409 (if it's still in flight).
Common questions
Does the API hash my body to detect mismatches?
Yes. The first request with a given key stores a SHA-256 hash of the canonicalized body. Subsequent requests recompute the hash and compare. Field order doesn't matter; we sort keys before hashing — {"a":1,"b":2} and {"b":2,"a":1} are the same logical body.
What about idempotency on GET requests?
GET endpoints are inherently idempotent — calling them multiple times has no side effect. The Idempotency-Key header is ignored on GET; you don't need to send it.
Do I need idempotency keys for status lookups?
No. GET /v1/whatsapp/messages/{id} is read-only.
What if the cached response was an error?
If the original request failed with a 4xx and your retry uses the same key + same body, the same 4xx response replays. This is intentional — the failure is part of the cached outcome, so your code handles it identically to the first attempt. To force a fresh send, use a different key.
Are responses byte-identical on replay?
The replay matches the original status code and the same logical content. Field order and the presence of optional null fields may vary marginally between the live response and the replay — branch on the id and status values, not byte-level equality.