Digital Africa11 min read

Reliable Payment Webhooks: Idempotency and Signature Verification

Mohamed Bah·Fondateur, Kolonell
August 20, 2026
Share:
Reliable Payment Webhooks: Idempotency and Signature Verification

Reliable Payment Webhooks: Idempotency and Signature Verification

Digital Africa

The verdict in three sentences

A payment webhook is not a single guaranteed call: it is retried 3 to 10 times over 24 to 72 hours, out of order, sometimes after your server times out. Without an idempotency key, HMAC signature verification and replay protection, you book double charges and lose orders. The golden rule: verify the signature, dedupe by event id, return 200 fast, process asynchronously, and reconcile with the status API as the source of truth.

Why webhooks arrive twice

Providers treat a webhook as delivered only if you return 200 quickly. The slightest delay triggers a retry. Here are typical 2026 policies (orders of magnitude, verify against each provider's docs).

ProviderRetry countRetry windowExpected timeoutSignature
Paystackup to ~5~72 h< 10 sx-paystack-signature (HMAC SHA512)
Wave3 to 1024-48 h< 10 stoken / signature
CinetPay3 to 8~48 h< 15 sverification token
Flutterwaveup to ~5~24 h< 10 sverif-hash header
Stripeup to ~15 over 72 h72 h< 10 sStripe-Signature

Bottom line: ALWAYS design your endpoint as if it will receive each event several times. Idempotency is not optional.

The 5 most common bugs

BugConsequenceFix
No signature verificationFake webhook -> fake paid orderVerify HMAC before any processing
Heavy synchronous processingTimeout -> retry -> double chargeReturn 200 fast, process in async queue
No deduplicationSame event processed twiceStore event id, ignore if already seen
Trusting the payload amountForged amount acceptedReconcile via the status API
Returning 500 on business errorEndless provider retriesReturn 200 + log to reprocess yourself

These five mistakes account for nearly all the payment incidents we audit at Kolonell.

The robust 6-step pattern

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 (HMAC) with the provider secret, before reading anything else. Invalid signature -> 401.
  • Extract the event id and check the database whether it was already processed. Already seen -> return 200 and exit.
  • Store the raw event (event id, payload, date) in a log table.
  • Return 200 immediately, before any heavy business processing.
  • Process asynchronously (queue): update the order, send the invoice, notify.
  • Reconcile against the provider getStatus API as the source of truth before marking "paid".

Mini case study

Ibrahim sells event tickets online in Abidjan. One Friday night his server is under load: the Wave webhook takes 12 seconds to respond, the timeout is 10 s. Wave retries the event 4 times. Without idempotency, Ibrahim issues 4 tickets for a single 15,000 FCFA payment; the customer is charged only once, but Ibrahim sends 4 valid tickets, 3 of which get used fraudulently: a 45,000 FCFA loss on one order. After the fix (dedupe by event id + immediate 200 + async processing), the same scenario issues a single ticket regardless of the retry count. Over a weekend of 400 tickets, the incident would have cost several hundred thousand FCFA.

FAQ

Why verify the signature if the webhook URL is secret? Because a URL is never truly secret: it leaks in logs, proxies, network captures. The HMAC signature proves the message really comes from the provider and was not tampered with. Without it, anyone knowing the URL can fake a payment.

What does idempotency mean concretely? It means processing the same event once or ten times produces exactly the same result. In practice: store the provider event id and ignore any already-recorded event. An order flips to "paid" only once, even after 10 retries.

Should I trust the amount in the webhook payload? No. The payload may be incomplete or, in a breach, forged. Always reconcile the amount and status via the provider status API before fulfilling the order.

Why return 200 before processing? Because the provider expects a fast acknowledgment (often < 10 s). If your business logic is slow, you exceed the timeout and trigger retries. Acknowledge first, then process in an async queue.

How do I test all this without real payments? Use the provider sandbox and its event replay feature. Send the same event twice to verify deduplication, send an invalid signature to verify the 401 rejection. Kolonell ships every integration with this test battery.

Let's talk about your project. We audit your webhooks or ship an idempotent, signed payment integration, tested in sandbox. WhatsApp +221 77 596 93 33.

Tags:#webhooks#idempotency#HMAC signature#payments#Wave#Paystack#reliability#backend
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.