Skip to content

Playbook · 6 steps

Webhook Deduplication

Also called: Webhook dedup · Duplicate webhook handling · Idempotent webhook consumer

6steps

Level
Practitioner
Reading time
About 5 min

Reviewed

Definition
Detecting and discarding repeated WhatsApp webhook deliveries, keyed on the wamid plus status or the event's identity, before any side effect runs.

01 · How it works, in 6 steps

How does webhook deduplication work?

  1. 1

    Derive an event key

    For each item, build its key: msg:<wamid> for inbound messages, st:<wamid>:<status> for statuses, and a field-specific composite for everything else. Compute it from the event, never from the HTTP body as a whole.

  2. 2

    Claim the key atomically

    Use one operation that succeeds for exactly one caller: SET key 1 NX EX <ttl> in Redis, or INSERT … ON CONFLICT DO NOTHING against a unique index. Never check-then-insert in two steps.

  3. 3

    Run side effects only on a fresh claim

    If the claim succeeded, process the event. If the key already existed, acknowledge and skip — the work was done, or is being done, by someone else.

  4. 4

    Keep keys at least seven days

    Retries can arrive up to seven days later, so a 24-hour TTL lets late duplicates through. Keep keys for eight days to leave a margin.

  5. 5

    Recover from half-finished work

    Claim with a short-lived processing state and mark it done at the end. If a worker dies mid-way, the claim expires and the next retry can finish the job instead of being skipped forever.

  6. 6

    Measure the duplicate rate

    Count skipped events per field. A rising rate points to slow acknowledgements upstream, long before customers notice double replies.

02Try it

Hands-on: use the controls to work through it yourself.

Send deliveries through a dedup check

12× slow motionService time S = 90 ms per job on average · 4 workers

Queue diagram: 1 deliveries waiting, 3 of 4 workers busy, 7 done, 1 duplicate, skipped.

Backlog
0.8
deliveries waiting on average
Throughput
30/s
26.4/s done · 3.6/s duplicate, skipped
Average wait
27 ms
39% of deliveries wait at all
Utilisation ρ
0.67
≈ 2.7 of 4 busy
30 deliveries/s
4

ρ = λ × S ÷ c = 30/s × 90 ms ÷ 4 = 0.67

ρ = 0.67 < 1: the workers keep up, so the queue drains between bursts and waits stay short.
Webhook deliveries arrive and workers claim each event key before processing. The side lane shows repeats that were recognised and skipped. Change the arrival rate and worker count to see the effect on the backlog.

Utilisation is below 1: the workers keep up.

03What is webhook deduplication?

Webhook deduplication is recognising a WhatsApp webhook event you have already handled and skipping it before anything happens twice. It is needed because Meta's model is at-least-once delivery, not exactly-once. Under Meta's webhook retry policy, a delivery that times out, gets a non-200 response or loses its connection is retried with decreasing frequency for up to seven days. Meta also notes that retries go to every app subscribed to the WhatsApp Business Account's relevant fields, so one app's failures can create duplicates for another. Even a perfectly healthy endpoint should expect to see the same event more than once.

Deduplication gives every event a stable identity and records it atomically before any side effect runs. The identity depends on the event: for an inbound message it is the WhatsApp message ID (wamid) in messages[].id; for a status it is statuses[].id plus status, because one wamid legitimately produces sent, delivered and read events; for other fields such as template or account updates it is a composite of the field, the entry[].id, the event's own timestamp and its key values. A POST can carry up to 1,000 updates and batching is not guaranteed, so the unit of deduplication is the event inside the payload, never the HTTP request.

It is easy to confuse with neighbouring ideas. An idempotency key protects your outbound requests — making sure a retried send does not message a customer twice — while webhook deduplication protects your inbound processing. And deduplication removes repeats of the same event; putting different events into the right sequence is the separate job of webhook event ordering.

Key takeaways

  • Webhook deduplication discards repeated deliveries of the same WhatsApp event before any side effect runs.
  • Meta delivers webhooks at least once and retries for up to seven days, so duplicates are normal rather than a bug.
  • Key inbound messages on the wamid and statuses on wamid plus status, because sent, delivered and read share one wamid.
  • Claim each key atomically, with SET NX or a unique index, and keep keys for at least seven days.
  • Deduplicate each event inside a payload, never the HTTP request: one POST can batch up to 1,000 updates.

04 · In code

Webhook deduplication in code

const EIGHT_DAYS = 8 * 24 * 60 * 60;
function eventKeys(value) {
const keys = [];
for (const m of value.messages ?? []) keys.push({ key: "msg:" + m.id, event: m });
for (const s of value.statuses ?? []) keys.push({ key: "st:" + s.id + ":" + s.status, event: s });
return keys;
}
export async function handleChange(value) {
for (const { key, event } of eventKeys(value)) {
// Atomic claim: only the first delivery gets "OK"
const fresh = await redis.set(key, "processing", { NX: true, EX: 300 });
if (!fresh) continue; // duplicate, or another worker has it
try {
await applySideEffects(event);
await redis.set(key, "done", { EX: EIGHT_DAYS });
} catch (err) {
await redis.del(key); // let a retry try again
throw err;
}
}
}

Claim with a short TTL while working, then keep the key for eight days once done.

05 · Mistakes to avoid

Common mistakes with webhook deduplication

  • Keying statuses on the wamid alone

    sent, delivered and read share one wamid. Deduplicating on the wamid keeps the first status and silently drops the rest. Include the status in the key.

  • Hashing the whole request body

    A retry can batch events differently, so the same event arrives inside a different body. Deduplicate each event, not each POST.

  • Check-then-insert races

    Two workers both read "not seen" and both process. Use an atomic set-if-absent or a unique constraint so only one claim can win.

  • Keys that expire too soon

    Retries continue for up to seven days. A 24-hour TTL is the most common reason duplicates still appear after an outage.

06Example: Exam-results day at a Kochi ed-tech

A Kochi ed-tech company's webhook endpoint goes down for 40 minutes on exam-results morning, while students are replying to their results messages. When it recovers, Meta's retries arrive mixed with live traffic, and some events that had reached the endpoint just before the crash are delivered again. Its workers claim msg:<wamid> keys in Redis before calling the chatbot, so each student's question is answered once; the dashboard shows about one in ten events skipped as duplicates for the next hour, then the rate falls back to near zero (figures illustrative).

07 · Key facts

Webhook deduplication: key facts and limits

Delivery guarantee
At least once
Retry window
Up to 7 days
Key lifetime
7 days minimum; 8 for margin
Inbound message key
messages[].id (the wamid)
Status key
statuses[].id + status
Updates per request
Up to 1,000; batching not guaranteed

08 · Why it matters

Why does webhook deduplication matter?

Every duplicate that slips through repeats a side effect. The chatbot answers the same question twice — and each answer is a charged message. A CRM opens two tickets, a loyalty programme awards points twice, an analytics pipeline double-counts conversions, a payment confirmation triggers two shipments. After an outage, Meta's retries arrive alongside live traffic and duplicates can briefly make up a large share of what you receive, which is exactly when a team least wants to clean up by hand.

09 · Pro tips

Best practices for webhook deduplication

  • Deduplicate as close to the side effect as possible, so with webhook fan-out every consumer is protected, not only the first.
  • Log skipped duplicates with their key — they are the cleanest evidence of a slow webhook acknowledgement.
  • Pair deduplication with status ranking, so a late but genuine status is merged rather than discarded.

10Webhook Deduplication FAQs

Why am I receiving the same WhatsApp webhook twice?+
Because Meta delivers webhooks at least once and retries any delivery that did not get a 200, for up to seven days. Slow responses, deploys and network blips all cause repeats, and retries also reach every app subscribed to the WABA.
Is the wamid enough to deduplicate?+
For inbound messages, yes. For statuses, add the status, because one outbound message produces several status events that share its wamid.
Can I deduplicate in the HTTP handler?+
You can, but keep the authoritative check next to the side effect. The handler should stay fast and only verify, store and acknowledge; dropping obvious repeats there is an optimisation, not the safeguard.
How is this different from an idempotency key?+
An idempotency key stops your own retried send requests from messaging a customer twice. Webhook deduplication stops Meta's repeated deliveries from triggering your processing twice.

See InfiQ pricing

From definition to your first message

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