The verdict in three sentences
A mobile money webhook is never delivered exactly once: M-Pesa Daraja can send up to 4 callbacks and Safaricom expects a fast acknowledgement. Without protection you credit the same order multiple times and the observed duplicate rate climbs to 0.3-1.2%. The fix has three parts: an idempotency key, a dedup table with a TTL, and an HTTP 200 response in under a few seconds.
Why operators resend
The webhook protocol is built for reliability: if the operator gets no quick acknowledgement, it assumes failure and retries. Your server must therefore process each event at most once, even if it arrives four times. That is the definition of idempotency.
| Provider | Max attempts | Retry window | Expected timeout |
|---|---|---|---|
| M-Pesa Daraja | 4 | ~variable | < 5 s |
| Airtel Money | 4-5 | 24 h | < 3 s |
| Flutterwave | 3+ | ~24 h | < 15 s |
| Paystack | 3+ | ~72 h | < 2 s |
| MTN MoMo | 5 | 24 h | < 3 s |
The dedup table: the core of the design
Store a unique identifier per event (the operator TransID or CheckoutRequestID). Before crediting, check whether it exists. If it does, return 200 and do nothing. If not, insert it in the same SQL transaction as the credit.
| Field | Role | Typical value |
|---|---|---|
event_id | Unique key (PK) | operator TransID |
order_id | Linked order | internal ref |
status | processed / pending | processed |
received_at | Timestamp | now() |
ttl | Dedup expiry | 24-72 h |
A 24 to 72-hour TTL is enough: no operator retries beyond that. Purge old rows to keep the table lean.
Respond fast, process later
The classic trap: doing all the work (credit, email, SMS) before responding, which blows past the timeout and triggers a retry. Best practice: validate, deduplicate, return 200, then process the rest on a background queue. This cuts observed duplicates by more than 90%.
Mini case study
Need a professional website?
Kolonell builds websites that attract clients, optimized for the Sénégalese market. Free quote in 2 minutes.
James, who runs a restaurant in Nairobi, receives 1,400 M-Pesa payments a month. Without deduplication, at a 0.8% duplicate rate, that is 11 orders credited twice, on an average basket of KES 1,600, costing about KES 17,600 a month in refunds, disputes and staff time. Adding a dedup table with an idempotency key brings the rate to ~0% and recovers roughly KES 211,000 a year. The build takes less than a day.
FAQ
What is an idempotency key in practice?
It is a unique, stable identifier for a given operation (often the operator transaction ID). It guarantees that processing the same event twice produces exactly the same result as processing it once.
How many times does Daraja resend a callback?
In 2026 the order of magnitude is up to 4 callbacks while your server fails to acknowledge quickly. Other operators like MTN MoMo can go up to 5 within 24 hours.
What TTL should the dedup table use?
Between 24 and 72 hours. That is longer than the retry window of every common operator, so you stay covered without letting the table grow forever.
Why respond within a couple of seconds?
Beyond that, the operator treats the call as failed and retries, multiplying duplicates. Acknowledge first, then handle the credit, email and SMS on a queue.
Let's talk about your project. We make your M-Pesa and card webhooks idempotent, with a tested dedup table and queue. 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.
