123eworld Knowledge Hub → SMS API → Page 299

SMS API Idempotency: Preventing Duplicate Messages and Duplicate API Requests

A practical developer reference designed to solve real implementation and production problems around sms api idempotency: preventing duplicate messages and duplicate api requests.

What idempotency means

Idempotency means repeating the same logical API operation does not unintentionally create another business action. It is especially important for SMS because a network timeout can leave the client uncertain about whether the provider accepted a message.

Idempotency keys

A client can supply an idempotency key for a logical send operation. The platform stores the key with the resulting message identity and returns the same result when the request is repeated within the defined retention window.

Scope

Keys should normally be scoped to the authenticated tenant or application. A key from one customer must never affect another customer's message.

Request fingerprint

The platform may bind the key to important request fields. If the same key is reused with materially different content, return a conflict rather than silently sending a different message.

Persistence

Idempotency records need durable storage. An in-memory cache alone can fail after a process restart and allow duplicate submissions.

Concurrency

Two identical requests can arrive at almost the same time. Use an atomic insert or equivalent locking strategy so only one request creates the logical message.

Retry versus duplicate

A retry of the same logical operation should reuse the original message ID. A deliberate new notification should use a new idempotency key.

Retention

Retain keys long enough to cover realistic client retry windows, while documenting expiry. Expired keys can legitimately create a new operation.

Batch idempotency

Bulk APIs need item-level and batch-level semantics. A failed batch retry should not resend items that were already accepted.

Webhooks

Idempotency also applies to incoming events. Event IDs should be stored before non-repeatable processing.

Testing

Test concurrent requests, network timeout after acceptance, key reuse with changed content and expired keys.

Reference model

Authenticate → validate key → atomic lookup/create → return existing or create new message → process asynchronously.

Security and privacy

Treat phone numbers, message content, credentials and delivery data as sensitive operational information. Avoid unnecessary logging and ensure tenant authorization is applied before data access.

Production reliability

Design for timeouts, duplicates, retries, provider failures and delayed events. A messaging platform is asynchronous infrastructure, so success-path testing alone is insufficient.

Developer-first principle

The public API should hide unnecessary telecom complexity while exposing enough structured information for developers to build correct integrations.

Related 123eworld guides

Explore the 123eworld SMS & WhatsApp Knowledge Hub for related developer, API, routing and production guides.

Atomic idempotency creation

The most important implementation detail is atomicity. Two requests with the same key can arrive concurrently. If both processes first check for the key and then create a message, both can succeed. Use a database uniqueness constraint or an atomic compare-and-create operation. The winning request creates the logical message; the other receives the stored result.

What to store

An idempotency record should contain tenant or application scope, key, request fingerprint, logical message ID, creation time, final or current API result and expiry. Do not store unnecessary message content in a separate idempotency table if the message record already contains it securely.

Changed request conflict

If a client reuses a key with different recipient, sender or message content, return a conflict. Otherwise a developer could accidentally send a new notification while believing the old operation was being retried. The error should identify that the key is already associated with a different request without exposing sensitive details.

Timeout after acceptance

This is the classic case: the client sends a request, the platform creates the message, but the network fails before the response reaches the client. The client retries with the same key. The platform finds the existing record and returns the original message ID instead of creating another SMS. This is why idempotency belongs at the logical API boundary.

Batch semantics

For bulk requests, use a batch ID for the overall operation and item-level keys or stable client references for individual messages. A retry of the batch should reconcile accepted items instead of resubmitting everything. This requires the batch processor to persist item state before provider submission.

Retention and limits

Idempotency retention should match realistic client retry behaviour and operational recovery windows. After expiry, reuse of the same key can legitimately create a new operation. Document the retention period clearly so developers do not depend on indefinite protection.

Database constraint

Create a unique constraint across tenant scope and idempotency key. Do not rely only on application code checking whether a key exists. The database must enforce uniqueness under concurrency. When a duplicate-key race occurs, load the existing record and return its original logical message result.

Idempotency and billing

A repeated request with the same key should not create another billable logical message. Provider attempts and retries belong to the original message lifecycle. This distinction is important for customer trust and invoice reconciliation.

Idempotency and webhooks

Incoming webhook events have their own event IDs and should be deduplicated independently from outbound API idempotency keys. One protects message creation; the other protects event processing. Mixing the two concepts can produce subtle bugs.

Operational visibility

Expose whether a send request created a new message or reused an existing idempotency record in a safe response field or diagnostic header. This helps developers understand why a retry returned the same message ID.

Idempotency across microservices

If the API gateway, message service and queue processor each create their own identifiers without a shared logical operation ID, duplicate protection becomes fragmented. Create the logical message identity at the boundary and propagate it through every internal service.

Idempotency response semantics

A repeated request should return the same logical message ID and an indication that the operation was previously accepted. Avoid returning a new request ID as though a new message was created. A new HTTP request can have a new request ID while still referring to the same logical operation.

Failure recovery

If the idempotency record exists but the original operation is incomplete, the retry should return the current state or safely resume processing rather than create another message. This requires the record to be linked to durable message state.

Idempotency across retries

A network client can retry because of a timeout, while the server can retry internally because of a provider failure. These are different retry layers, but both must remain attached to the same logical message. Client idempotency prevents duplicate logical messages; internal retry policy manages provider attempts.

Conflict response

When an idempotency key is reused with different parameters, return a conflict response with a stable error code. The response should tell the developer that the key is already associated with another request, without exposing the original message content or recipient.

Data retention

Keep idempotency records for the documented retention period and purge them safely afterward. If a customer needs a longer business guarantee, the platform should offer a separate client reference or message lookup mechanism rather than indefinite idempotency storage.

Idempotency with delayed responses

If the original request remains in progress, a repeated idempotent request should return a state that tells the client the operation is still being processed. Do not create a second message simply because the first request has not completed synchronously.

Auditability

Record when an idempotency key was first seen, which message ID it created and when it was reused. This information can resolve duplicate-send disputes quickly while keeping the logical message lifecycle intact.

Client documentation

Tell developers exactly when to generate an idempotency key, how long to retain it and what to do when a request times out. Give examples for both a successful first request and a repeated request. This prevents developers from inventing incompatible duplicate-protection schemes.

Final idempotency principle

Create one logical message for one logical send operation. Every retry, provider attempt and webhook event should remain traceable to that identity.

Production implementation detail

A practical idempotency implementation should also survive database failover and service restarts. The idempotency record and logical message record must be durable before the API reports acceptance. If the service crashes after provider submission but before the database update, reconciliation must determine the original attempt rather than creating another logical message. This is why idempotency is closely connected to durable state and provider reconciliation, not merely an API header.