Digital Africa11 min read

Payment webhooks: idempotency to avoid double charges (2026)

Mohamed Bah·Fondateur, Kolonell
June 28, 2026
Share:
Payment webhooks: idempotency to avoid double charges (2026)

Payment webhooks: idempotency to avoid double charges (2026)

Digital Africa

The verdict in three sentences

A payment webhook is never guaranteed unique: the aggregator may resend it 2 to 5 times on a timeout or retry. Without an idempotency key, your system credits the same order several times, ships twice, or skews your accounting. The defense comes down to three reflexes: verify the signature, store the transaction ID as a unique key, and lock the processing.

Why duplicates happen

Aggregators guarantee "at least once" delivery, not "exactly once". If your server responds slowly or returns an error, the aggregator assumes the message failed and replays it. Your code then receives the same payment event twice.

Resend causeRelative frequencyConsequence without idempotency
Your server timeoutFrequentWebhook replayed 1-4 times
Non-200 HTTP responseFrequentAutomatic retry
Unstable networkMediumDuplicated delivery
Aggregator maintenanceRareBatch replay
Application bug (exception)VariableDouble processing

The observed order of magnitude: 1 to 3% of transactions can generate an unhandled duplicate if the integration does not protect processing.

The idempotency pattern, concretely

The principle: each webhook carries a unique transaction ID provided by the aggregator. Before processing, you check whether that ID has already been handled. If yes, you respond 200 and do nothing; if no, you process then record the ID.

StepTechnical actionEffect
1. Verify signatureCompare the HMAC with your secretRejects fake webhooks
2. Read transaction IDExtract the unique key from the payloadIdentifies the event
3. Lock the rowSELECT ... FOR UPDATE in the databasePrevents concurrent processing
4. Check already handledLook up the ID in the payments tableDetects the duplicate
5. Process exactly onceCredit, mark paid, shipSingle action
6. Respond 200 fastAcknowledge to stop retriesCuts the replay loop

Two crucial details: the unique ID must be constrained UNIQUE in the database (the database becomes the last line of defense against duplicates), and the 200 response must be fast to avoid triggering new retries.

Robust integration checklist

Need a professional website?

Kolonell builds websites that attract clients, optimized for the Sénégalese market. Free quote in 2 minutes.

Verify the signature of every webhook (Wave, CinetPay, PayDunya each have their own mechanism). Use the aggregator's transaction ID, never your own order number, as the idempotency key. Constrain that ID as UNIQUE. Lock the transaction during processing. Respond 200 immediately and run heavy tasks in a queue. Log every webhook received, processed or ignored, for audit. Test by deliberately replaying the same webhook several times.

Mini case study

Moussa, a developer for an online shop in Thies processing 3,000 orders/month, sees complaints from customers charged twice. Before the fix, about 2% of transactions, or 60 orders/month, caused double processing, at an average basket of 25,000 FCFA: up to 1,500,000 FCFA of potential disputes and refunds each month. He adds a UNIQUE constraint on the transaction ID and a database lock. After deployment, duplicates drop to 0, the support effort on double-charge disputes vanishes, and customer trust recovers. Cost: one day of development.

FAQ

Why receive the same webhook twice? Because aggregators guarantee "at least once" delivery. On a timeout or an error from your server, they replay the event, sometimes 2 to 5 times.

Which key should I use for idempotency? The transaction ID provided by the aggregator, not your internal order number. Constrain it as UNIQUE in the database to guarantee a single processing.

Is the signature enough to avoid duplicates? No. The signature authenticates the sender but says nothing about whether the event was already handled. You must combine signature and idempotency key.

Why respond 200 quickly? Because a slow or error response triggers a new retry. Acknowledge fast, then process heavy tasks (email, shipping) asynchronously.

How do I test my idempotency? Deliberately replay the same webhook several times in a test environment and verify a single order is credited. That is the minimal test before production.

Let's talk about your project. We integrate Wave, CinetPay and PayDunya with idempotency, signature and retry handled end to end. WhatsApp +221 77 596 93 33.

Tags:#webhook#idempotency#double payment#api integration#cinetpay#paydunya#development#reliability
Share:

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.