IOSOR Learn

Inbound SMS webhooks: retries, event order, and idempotency on receive

A build guide for B2B teams handling inbound SMS: why retries happen, why event order is not guaranteed, and how to make your receive endpoint idempotent instead of duplicating conversations and STOP handling.

Every inbound message handler eventually meets the same three surprises: the same webhook fires twice, a "delivered" event arrives after the "failed" one it superseded, and a customer's STOP reply gets processed twice because two servers picked up the same retry. None of these are bugs in the platform sending you the webhook — they are the normal behavior of any at-least-once delivery system, and your receive endpoint has to be built for that reality from day one.

IOSOR delivers inbound SMS, STOP/HELP keywords, and delivery events as white-label prepaid webhooks — the retry and ordering behavior below is what any serious B2B integration should assume regardless of which platform sits behind it.

Why webhooks retry at all

A webhook platforms cannot know for certain that your endpoint processed a delivery. Your server might return a 200 after committing to a database that then rolls back; a load balancer might drop the response on the way back even though your handler succeeded; a deploy might restart your process mid-request. Because "silently lose the event" is worse than "occasionally send it twice," every serious webhook system chooses at-least-once delivery: it retries on timeout, on 5xx, and often on ambiguous network errors, accepting that duplicates will happen.

The three failure modes you must design for

Failure mode What happens What breaks if you ignore it
Duplicate delivery Same event ID arrives 2+ times Double-counted replies, doubled STOP processing, duplicate conversation threads
Out-of-order events A later-timestamped event arrives before an earlier one A "delivered" status gets overwritten back to "sent"
Partial/ambiguous failure Your handler processed the event but the ack was lost platforms retries something you already did

Idempotency: the one property that fixes all three

An idempotent receive endpoint produces the same end state no matter how many times the same event is delivered. The mechanism is simple and well-understood: every inbound event carries a unique event ID; before processing, you check whether you've already recorded that ID; if you have, you return success immediately without reprocessing. 1.

Event ordering: why "last write wins" is dangerous

Webhook events for the same message are not guaranteed to arrive in the order they occurred.

Red flags

  • No unique event ID in the webhook payload, or your integration ignores the one that's there
  • Status updates applied with a plain overwrite and no timestamp comparison
  • STOP handling that isn't behind the same dedupe logic as regular inbound messages
  • Webhook handler doing synchronous downstream calls (email, CRM, agent routing) before acknowledging
  • No logs showing how many duplicate event IDs arrived last month — meaning nobody is watching

Start with IOSOR

Pull last week’s inbound webhook logs and count event IDs that arrived more than once. Replay one duplicate and one out-of-order pair (failed, then delivered). The receiver must keep one effect: one inbox row, one STOP write, one wallet touch. Last-write-wins that undoes STOP fails this job. This is receive-side idempotency and retry order, not signature validation and not a gateway lock before the queue.

Related: inbound auto-reply loops · Buffer Inbound Webhook Processing Against Carrier Latency Spikes · Prepaid hold before first debit.

IOSOR takeaway

Inbound webhooks retry. Idempotency on receive is the only safe answer; order is not a promise.

Do: key the event and ignore the twin. Don't: apply last-write-wins to STOP or debit the same event twice.

Was this guide helpful?

Related guides