The verdict in three sentences
A payment webhook can be delivered several times — it is an at-least-once guarantee, not exactly-once. Without an idempotency key and a deduplication table, 2% to 5% of duplicates turn into double charges and customer disputes. The fix rests on three elements: unique key, transactional lock, and always responding 200.
Why duplicates are inevitable
Mobile money operators replay a notification until they receive a clear acknowledgement. If your server responds slowly, returns a 500, or times out, the operator treats delivery as failed and retries. Your code must therefore handle every event as if it could arrive multiple times.
| Webhook behaviour 2026 | Typical value | Consequence without idempotency |
|---|---|---|
| Observed duplicate rate | 2–5% | Double charge |
| Operator retries | up to 5x | Up to 5 debits |
| Delay between retries | 1 → 30 min | Deferred duplicates |
| Expected response | HTTP 200 | 500/timeout = replay |
| Recommended dedup window | 24h | Duplicates outside window |
The golden rule: respond 200 only once the event is recorded, never before. A premature 200 loses the event if the process crashes.
The idempotency pattern
Each event carries a stable identifier supplied by the operator (transaction id or event id). We use it as the primary key in a deduplication table. On receipt: attempt to insert the key; if it already exists, ignore it and respond 200 without reprocessing.
| Pattern element | Role | Implementation detail |
|---|---|---|
| Idempotency key | Identify the event | operator transaction id |
| Dedup table | Remember what's processed | UNIQUE constraint on key |
| Transactional lock | Prevent concurrency | SELECT ... FOR UPDATE |
| Audit log | Traceability | timestamp + raw payload |
| 200 response | Stop replays | after commit only |
| Retention window | Cleanup | purge after 24–72h |
Need a professional website?
Kolonell builds websites that attract clients, optimized for the Sénégalese market. Free quote in 2 minutes.
Two identical webhooks received in parallel are the real trap: without a lock or a database uniqueness constraint, both pass the "does it already exist?" check before either writes. The database UNIQUE constraint is your last line of defence.
Mini case study
Ibrahim runs an online grocery in Durban with 1,500 payments a month. With 4% unmanaged duplicates, that is 60 double charges monthly. At an average basket of 12,000 FCFA, that means 720,000 FCFA to refund each month, plus dispute costs and lost trust. After implementing idempotency, duplicates drop to zero: one day of integration work pays for itself in under a week.
FAQ
Is a database UNIQUE constraint enough? It is essential but not sufficient alone: combine it with transactional processing to cleanly handle concurrent webhooks and guarantee a consistent state.
What should I return if the event is already processed? HTTP 200. The operator understands delivery succeeded and stops replaying. Returning an error would needlessly restart the cycle.
How long should I keep the keys? A window of 24 to 72 hours covers nearly all replays. Beyond that, purge them so the table does not grow indefinitely.
Should I verify the webhook signature? Yes, always. Idempotency prevents duplicates; signature verification prevents malicious fake events. The two are complementary.
Let's talk about your project. We secure your payment webhooks with idempotency, locking, and an audit log, tested against real replays. WhatsApp +221 77 596 93 33.
Mohamed Bah
Fondateur, Kolonell
Passionate about digital and entrepreneurship in Africa, Mohamed has been helping Sénégalese businesses with their digital transformation since 2020. Founder of Kolonell, he believes every SME deserves a professional and accessible online présence.

