Skip to content

Idempotency

Networks fail. Your request can time out after SanadWA already sent the message. If you then simply send the request again, the customer gets the message twice, and you pay twice.

An idempotency key prevents this. It is a unique value that you choose for each message and send in the Idempotency-Key header. If SanadWA sees the same key again, it does not send the message again.

The Idempotency-Key header works on every request that changes something: all POST and DELETE endpoints.

Endpoint Idempotency-Key
POST /campaigns Required. See Campaigns below.
POST /messages/template, POST /messages/text Optional, strongly recommended. Without it, a retry can send a second message.
POST /contacts, POST /contacts/import, POST /tags, DELETE /contacts/{id}, DELETE /tags/{id}, POST /campaigns/{id}/pause, POST /campaigns/{id}/resume Optional.

GET requests do not change anything, so you can repeat them safely without a key.

Some writes are safe to repeat even without a key, because of what they do: writing the same contact again updates the same contact (the phone number identifies it), and creating a tag that already exists answers 409 tag_already_exists. Sends are not: two requests are two messages.

  1. Before the first attempt, create a new random key for this message, for example a UUID.
  2. Store the key with the message in your own database.
  3. Send the request with the header Idempotency-Key: <your key>.
  4. If the request fails with a network error or a timeout, send exactly the same request again, with the same key.

Rules for the key:

  • At most 120 characters. A longer key gets 400 idempotency_key_too_long.
  • Spaces at the start and end are removed.
  • Use a new key for every new request. Never reuse a key for a different request.
  • Keys are separate per API key. The same value used with two different API keys counts as two different keys.
Terminal window
KEY=$(uuidgen) # store this with your message
curl https://api.sanadwa.com/v1/messages/template \
-H "Authorization: Bearer $SANADWA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $KEY" \
-d '{"phone_number_id":"100000000000001","to_phone":"+12025550123","template_name":"order_update","template_language":"en","template_params":["Sara","A-1042"]}'

SanadWA remembers a key for 24 hours after the first request with it (campaigns: see Campaigns).

Situation What the API does
First request with this key Processes the request as normal.
Same key, same request, the first request succeeded Does not process it again. Returns the stored response of the first request, with the same status and body, and the header X-Idempotent-Replay: true.
Same key, a different request, the first request succeeded Does not process it. Returns 409 idempotency_conflict. See What counts as the same request.
Same key while the first request is still running Does not process it. Returns 409 idempotency_in_progress with the header Retry-After: 1. Wait one second and try again with the same key.
Same key, the first request failed (any 4xx or 5xx response) The failed response is not stored, so the API processes the request again. See the next section.

A key identifies one request. Use it again only to retry that same request.

  • On the contacts, tags and campaigns endpoints, the key is bound to the HTTP method, the path and the body. Reusing it for another endpoint, another id (for example DELETE /tags/2 after DELETE /tags/1) or another body answers 409 idempotency_conflict. Retrying through the older /api/public/v1 path counts as the same path.
  • On POST /messages/template and POST /messages/text, only the body is compared.

The body is compared as JSON. Send exactly the same body on every attempt. If you build the JSON again, keep the same fields with the same values in the same order. For the CSV import, “same body” means the same file and the same tags field.

The API stores a response only when it is a success (2xx). After an error response, a retry with the same key is processed again.

For POST /campaigns, retrying with the same key is always safe: it returns the campaign the key already created, or creates it now. The rest of this section is about the two send endpoints. Before you retry a send, look at the status and the code:

Response Was a message sent? What to do
400, 401, 403, 404 No The request is wrong and will fail again. Fix it. A fixed request has a different body, so use a new key.
402 (not enough credits) No Add credits, then retry with the same key and the same body.
429 rate_limited No Wait for the seconds in Retry-After, then retry with the same key.
503 with TEMPLATE_CATEGORY_UNAVAILABLE, TRACKED_TEMPLATE_GUARD_UNAVAILABLE or SUPPRESSION_CHECK_FAILED No Wait a few seconds, then retry with the same key.
send_failed with a 4xx status, except 408 and 429 No. WhatsApp refused the message. Read message and meta_error, fix the cause, then retry.
send_failed with a 5xx, 408 or 429 status Unknown See the next section.
No response (network error or timeout) Unknown Retry with the same key and the same body. The API returns the stored result, or one of the 409 answers above.

Sometimes SanadWA cannot tell whether WhatsApp accepted a message, for example when WhatsApp answers with a server error or does not answer in time. SanadWA then keeps a hold on that idempotency key, so that a retry cannot send a second message.

A retry with the same key then gets 409 with "code": "send_failed" and a message such as This send is already being processed. or This send requires reconciliation before another attempt.

In this case:

  • Do not send the message again with a new key. The first message may have reached the customer.
  • Wait for a message.delivered or message.failed webhook, or check the conversation in the console.
  • If you still do not know the result, open a support ticket with the idempotency key and the time of the request.

If you retry with the same key but a different body after an error, you can also get 409 send_failed with the message Send request changed. Use a new key for a new message.

POST /campaigns requires the Idempotency-Key header. Without it you get 400 idempotency_key_required, and nothing is created.

Campaigns have a stronger protection than other requests. The key is saved with the campaign it created, so:

  • A retry with the same key and the same body returns the same campaign, with 201 and X-Idempotent-Replay: true, for as long as the campaign exists. It does not stop after 24 hours. Within 24 hours you get the first answer again; after that, the campaign as it is now.
  • The key counts for your whole account, not only for one API key.
  • The same key with a different body answers 409 idempotency_conflict.
  • One key never creates or sends a second campaign.

Create one key per campaign, store it before the first attempt, and reuse it on every retry. See Send a campaign to a tag.

The send endpoints also accept a field named idempotency_key in the JSON body. This field has no effect. Only the Idempotency-Key header is used. Send the header.