Idempotency
هذا المحتوى غير متوفر بلغتك بعد.
Networks time out after the work has already happened. Your job queue sees no response, retries, and a second WhatsApp message arrives at a real customer: they see a duplicate, and you are billed for a second conversation.
Send an Idempotency-Key and that cannot happen.
curl https://k-message.kerneltics.com/v1/messages \ -H "Authorization: Bearer km_live_YOUR_KEY" \ -H "Idempotency-Key: order-1001-shipped" \ -H "Content-Type: application/json" \ -d '{"to":"+966500000000","type":"text","text":{"body":"Your order has shipped."}}'The guarantee
Section titled “The guarantee”For a given organization and key, the first request executes and every later
one replays its stored response. The replay carries Idempotency-Replayed: true
and the original status code and body, so your code does not need a special
case for it.
Results are kept for 24 hours, which covers any sane retry policy including a queue that backs off overnight.
Choosing a key
Section titled “Choosing a key”Derive it from the thing that happened, not from the attempt:
// Good: the same key on every retry of the same event.const key = `order-${order.id}-shipped`
// Useless: a fresh key each attempt, so every retry sends again.const key = crypto.randomUUID()A UUID is fine if you generate it once, store it with the job, and reuse it across retries. What must not happen is a new value per attempt.
Keys may be up to 255 characters.
Reusing a key with a different body
Section titled “Reusing a key with a different body”Rejected, with 409:
{ "error": { "type": "invalid_request_error", "code": "idempotency_key_reused", "message": "This Idempotency-Key was already used with a different request body. Use a new key for a new request." }}Executing it would return two different results under one key, which defeats
the entire point. It almost always means the key is derived from something too
coarse, such as an order id where the same order sends several different
messages. Include what the message is: order-1001-shipped, not order-1001.
Concurrent retries
Section titled “Concurrent retries”If a second request arrives while the first is still running, it gets 409
with Retry-After: 2 rather than being allowed to race it. Exactly one
executes. Wait and retry, and you will get the first one’s result.
What is not cached
Section titled “What is not cached”A 5xx is a failure, not a decision, so it is not stored. Retrying after one
attempts the request again, which is what you want: caching a transient outage
would make it permanent for that key for a day.
A 4xx is stored. It is a decision about that exact payload and will not
change on retry.
Endpoints that accept it
Section titled “Endpoints that accept it”POST /v1/messagesPOST /v1/contactsPOST /v1/campaignsPOST /v1/campaigns/{id}/recipientsPOST /v1/campaigns/{id}/startPOST /v1/webhooks
Requests without the header run normally. The header is offered, not imposed.