Skip to content
man glossary/at-least-once-delivery

glossary define at-least-once-delivery

Name

At-Least-Once Delivery

A delivery guarantee under which every message reaches its consumer at least once — never lost, but possibly duplicated — so consumers must be idempotent.

Synopsis

At-least-once semantics, At-least-once guarantee, Delivery semantics

Section

Reliability & scale

Level

expert

▍

Reviewed

Ack first or work first? Compare the guarantees

8 / 8

At-most-once

Ack first, then work: may lose, never repeats

At-least-once

Work first, then ack: may repeat, never loses

  1. At-most-onceBefore processing, or never
    At-least-onceAfter processing succeeds
  2. At-most-onceThe message is gone
    At-least-onceThe message is redelivered
  3. At-most-onceNever
    At-least-oncePossible — expect them
  4. At-most-oncePossible on any failure
    At-least-onceNone while the sender keeps retrying
  5. At-most-onceFire-and-forget analytics beacons
    At-least-onceMeta's webhooks, retried for up to 7 days
  6. At-most-onceTolerate occasional gaps
    At-least-onceBe idempotent: dedupe on a stable key
  7. At-most-onceTyping indicators, live dashboards
    At-least-oncePayments, statuses, inbound messages

Verdict

Exactly-once is not a third switch on the queue. It is at-least-once delivery plus a consumer that makes repeats harmless — a unique key committed with the side effect.

At-most-once and at-least-once differ in one decision: when the consumer acknowledges. Step through the rows to see what each does on a crash, what it costs and where WhatsApp webhooks sit.

Verdict: Exactly-once is not a third switch on the queue. It is at-least-once delivery plus a consumer that makes repeats harmless — a unique key committed with the side effect.

Description

What is at-least-once delivery?

At-least-once delivery is a guarantee about what a messaging system does when something fails: every message will be delivered to its consumer one or more times. Nothing is silently dropped, but duplicates are allowed. It sits between at-most-once delivery (send and forget: never duplicated, sometimes lost) and the much-advertised exactly-once delivery, which across independent systems is achievable only as an end-to-end effect, not as a property of the transport.

The guarantee comes from where the acknowledgement happens. A consumer that acknowledges only after it has finished processing will see the message again if it crashes, times out or loses its connection before the ack lands — the sender cannot tell "processed but ack lost" from "never processed", so it redelivers. Most Message Queue products and log-based streams work this way by default, and so does Meta's webhook delivery: under its Webhook Retry Policy, a notification that does not get a 200 is retried with decreasing frequency for up to seven days, and because retries interleave with new events, webhook event ordering is not guaranteed either.

The practical answer is an idempotent consumer: processing the same message twice has the same effect as processing it once. That is what an Idempotency Key and Webhook Deduplication implement — at-least-once delivery is the system-wide guarantee you design for, and those are the techniques that make it safe. Outbound sends have the same shape: a send to the Cloud API that times out may or may not have been accepted, so retrying it makes your outbound path at-least-once too.

Key takeaways

  • At-least-once delivery means no message is silently lost, but any message may arrive more than once.
  • Meta's WhatsApp webhooks are at-least-once: failed deliveries are retried for up to 7 days, and order is not guaranteed.
  • The consumer acknowledges only after processing, so a crash between doing the work and sending the ack replays the message.
  • Exactly-once is at-least-once delivery plus an idempotent consumer — not a mode a queue can give you on its own.
  • Make side effects idempotent with a unique key, such as the message id plus status, committed in the same transaction as the work.

Usage

At-least-once delivery in code

import pg from "pg";
const pool = new pg.Pool();
// msg = entry[].changes[].value.messages[i] from a WhatsApp webhook
export async function handleInbound(msg) {
const client = await pool.connect();
try {
await client.query("BEGIN");
const { rowCount } = await client.query(
"INSERT INTO processed_events (event_key) VALUES ($1) ON CONFLICT (event_key) DO NOTHING",
["msg:" + msg.id],
);
if (rowCount === 0) { // seen before: a retry or a duplicate
await client.query("ROLLBACK");
return "duplicate"; // still answer 200 so Meta stops retrying
}
await applySideEffects(client, msg); // credit, ticket, reply job...
await client.query("COMMIT"); // key and effect land together
return "processed";
} catch (err) {
await client.query("ROLLBACK");
throw err; // let the queue redeliver
} finally {
client.release();
}
}
// Statuses: sent, delivered and read share one message id, so key on both
export const statusKey = (s) => "status:" + s.id + ":" + s.status;

The dedupe insert and the side effect commit in one transaction, so a crash can never leave one without the other.

Mechanics

How does at-least-once delivery work?

  1. step 1 — The sender hands over a message

    A queue publisher, or Meta's webhook sender, transmits the message and keeps responsibility for it until the consumer confirms. Until then the message is in flight.

  2. step 2 — The consumer processes, then acknowledges

    Your worker does the work — stores the status, calls the CRM, queues a reply — and only then acks: returns HTTP 200 to Meta, commits the offset, or deletes the queue message.

  3. step 3 — A failure between work and ack causes redelivery

    If the process crashes, the ack is lost or a visibility timeout expires first, the sender cannot know the work was done, so it delivers the message again — possibly to another worker, possibly out of order.

  4. step 4 — Deduplicate on a stable key

    Before running side effects, insert the message's identity — the inbound message id, the id plus status for a status webhook, or your own idempotency key — into a table with a unique constraint. A conflict means "already done": acknowledge and skip.

  5. step 5 — Commit the key and the effect together

    Write the dedupe key in the same database transaction as the state change it guards, or use a Transactional Outbox. If they commit separately, a crash between them recreates the duplicate — or the loss — you were avoiding.

Properties

At-least-once delivery: key facts and limits

Guarantee
Delivered one or more times; never silently lost
Duplicates come from
Lost acks, crashes before the ack, timeouts, retries
WhatsApp webhook retries
Decreasing frequency, for up to 7 days
WhatsApp webhook ordering
Not guaranteed
Dedupe key, inbound message
messages[].id (the wamid)
Dedupe key, status
statuses[].id + statuses[].status
Exactly-once in practice
At-least-once + idempotent processing

Caveats

Common mistakes with at-least-once delivery

  • Trusting a broker's exactly-once label

    Exactly-once features in brokers cover reads and writes inside that broker. The moment your consumer calls the Cloud API, a CRM or a payment gateway, a crash can still repeat the call. Make the external effect idempotent.

  • Deduplicating in memory

    An in-process set of seen IDs forgets everything on restart and is not shared between pods — exactly when duplicates arrive. Use a database unique constraint, or a shared store whose expiry outlasts the retry window.

  • Acknowledging before the work is durable

    Returning 200 to Meta and then processing in memory turns at-least-once into at-most-once: a crash loses the event. Persist it to a queue or table first, then acknowledge.

  • Keying statuses on the message ID alone

    One message produces sent, delivered and read events with the same id. Keying only on that ID drops the later statuses as duplicates; key on id plus status.

  • Blindly retrying outbound sends

    A send that timed out may already be on its way to the customer. Before resending a non-idempotent message such as an OTP or a payment link, check your send ledger and the status webhooks for that request.

Example

Example: Double cashback at a Surat wallet app

A payments app in Surat credits cashback when a customer replies YES to an offer on WhatsApp. During a deploy its webhook endpoint returned 502 for four minutes, and some replies had already been processed by a pod that died before it could answer 200. Meta retried those notifications, the new pods processed them again, and 37 customers were credited twice. The fix: the handler now inserts the inbound message id into a processed_events table with a unique constraint, inside the same transaction as the credit. Replays hit the constraint, are acknowledged with 200 and skipped — and a duplicate counter on the dashboard shows how often it happens.

Why it matters

Why does at-least-once delivery matter?

Treating webhooks or queue messages as exactly-once is how customers get two refund confirmations, two OTPs or a cashback credited twice. Treating them as at-most-once is how a delivered status or an inbound complaint disappears during a deploy. Naming the guarantee forces the right question for every handler — what happens if this runs twice? — and answering it once, with a dedupe key and a transaction, is far cheaper than reconciling duplicate side effects after an outage replays a day of events.

Questions

At-Least-Once Delivery FAQs

Are WhatsApp webhooks delivered exactly once?
No. Meta delivers webhooks at least once: a notification that does not get a 200 response is retried for up to seven days, and order is not guaranteed. Deduplicate inbound messages by id and statuses by id plus status.
Is exactly-once delivery possible?
Not as a pure transport guarantee across independent systems, but it is achievable as an effect. Combine at-least-once delivery with idempotent processing — a unique key committed in the same transaction as the side effect — and repeats become harmless.
When is at-most-once delivery acceptable?
When losing an occasional message costs less than handling duplicates. Typing indicators, live dashboard ticks and sampled analytics fit; payments, OTP audit trails, delivery statuses and customer messages do not.
How is at-least-once delivery different from an idempotency key?
At-least-once delivery is the guarantee your system gives; an idempotency key is one technique that makes it safe. The guarantee says duplicates will happen, and the key lets the consumer recognise and ignore them.

Notes

Best practices for at-least-once delivery

  • Write down the delivery guarantee of every hop — webhook, queue, worker, outbound API. The weakest hop is your system's guarantee.
  • Keep dedupe keys at least as long as the longest redelivery window; for WhatsApp webhooks that is seven days.
  • Make status handling order-independent: never move a message backwards from read to delivered, whatever order events arrive in.
  • Count duplicates as a metric; a sudden rise usually means slow acknowledgements or a crashing worker.
  • Cap redeliveries: a message that fails every attempt belongs in a dead-letter queue with its last error, not in an endless retry loop.

See InfiQ pricing

Put At-Least-Once Delivery to work

InfiQ is an official Meta Business Partner. Start a 7-day free trial — no card needed — and see this running on your own number.