Skip to content

Idempotency

Three endpoints require an Idempotency-Key header: POST /otp/send, POST /messages/send, and POST /devices/{id}/messages. Without it the request is refused with 400 invalid_input.

The Google Business writes (PATCH /gmb/locations/{id}, PUT /gmb/locations/{id}/reviews/{reviewId}/reply and POST /gmb/locations/{id}/refresh) take an optional Idempotency-Key of 8–128 characters, which is stored for 24 hours rather than forever; the same key with a different request is 409 gmb_idempotency_conflict. See Google reviews.

Idempotency-Key: order-8821-shipped

The value is 1–128 characters, and it is yours to choose. Derive it from the business event, not from the attempt: order-8821-shipped is right, retry-3 is not.

You send MauKirim does
A key it has never seen Sends, and remembers the outcome
The same key with the same normalized request Returns the original result. No second message, no second charge. On devices/{id}/messages the response carries "created": false.
The same key with a different request Refuses with 409 batch_idempotency_conflict

“Same request” is decided by a SHA-256 fingerprint of the normalized fields, not by the raw bytes:

Endpoint Fingerprint covers
POST /otp/send purpose, phone
POST /messages/send template id, phone, variable pairs sorted by name; item arrays retain item order while each item’s field pairs are sorted by name
POST /devices/{id}/messages gateway, device, recipient, kind, trimmed text, media key

So changing whitespace or adding a variable that the template does not use still counts as a different request, and changing the recipient definitely does.

This is the whole point. A send can fail after MauKirim accepted it — a timeout, a dropped connection, a worker that restarts mid-dispatch. If you retry with a new key you may send the message twice; if you retry with the same key you either get the original result or an explicit conflict.

Terminal window
# the request that timed out
curl -X POST "$BASE/messages/send" -H "Idempotency-Key: order-8821-shipped" …
# the safe retry: identical body, identical key
curl -X POST "$BASE/messages/send" -H "Idempotency-Key: order-8821-shipped" …

Two consequences worth knowing before you design around it

Section titled “Two consequences worth knowing before you design around it”

1. Keys are reserved for the life of the account. The batch row that holds the outcome is never deleted, and the uniqueness rule is a database index on (account, key). So a key you used a year ago is still reserved: reusing it with a different message is a 409, forever. Use keys that are naturally unique per business event (an order id, an OTP challenge id, a ticket id) rather than a counter you might reset.

2. The message body is purged, the record is not. Send text and media object keys are cleared seven days after an attempt becomes terminal, and queued media expires seven days after upload. The id, the states and the timestamps remain — which is what makes the delivery log and the dashboard useful without keeping your customers’ messages around.

POST /otp/verify, POST /devices/{id}/presence, POST /devices/{id}/chat-presence, PATCH /devices/{id}/profile and the webhook routes do not take an Idempotency-Key. Verification is naturally repeatable (a wrong code simply returns verified: false), presence changes are immediate gateway actions rather than queued messages, and the webhook routes are declarative: setting the same destination twice leaves the same destination.