The verdict in three sentences
A webhook is never guaranteed once: Paystack can notify you of the same payment 2 to 5 times, and 3 to 8 % of events arrive as duplicates. Without a guardrail, your system fulfills the order twice or doubles revenue. The defense rests on three bricks: HMAC signature verification, an idempotency key, and a processed-events table.
Why webhooks arrive twice
A webhook is an HTTP call the provider retries until it gets a fast 200 OK. If your server responds in 6 seconds, the provider may already have retried. Result: the same event processed several times.
| Parameter | 2026 order of magnitude | Consequence |
|---|---|---|
| Duplicate event rate | 3-8 % | Double fulfillment if unhandled |
| Webhook delivery delay | 2-30 s | Provider-side timeout |
| Signature validity window | 5 min | Rejected if clock skewed |
| Provider retry count | 5 attempts | Over ~24h |
| Expected response time | < 3 s | Otherwise retry triggered |
The golden rule: respond 200 immediately after persisting the event, then run business logic asynchronously. Never do fulfillment, email and SMS before responding.
The idempotency model
Each event carries a unique identifier (Paystack reference / id). Before any processing, attempt an insert into a processed_events table. If the row already exists, ignore and return 200.
| Step | Action | If duplicate |
|---|---|---|
| 1. Signature | Verify HMAC SHA-512 | Reject 401 |
| 2. Idempotency | INSERT event_id (unique) | Conflict -> ignore |
| 3. Persistence | Store raw payload | Already stored |
| 4. Ack | Respond 200 OK | Respond 200 OK |
| 5. Business | Fulfill / notify (async) | Do not replay |
The unique constraint on event_id in the database does all the work: two concurrent webhooks for the same payment trigger a single successful insert. That is more reliable than an application-level if exists, which is vulnerable to races.
Mini case study
Tunde runs checkout for a ticketing platform in Lagos. On a concert night he collects 1,200 payments via Paystack. The spike saturates his server: 7 s response time, so the provider retries. He receives 1,356 webhooks, i.e. 156 duplicates (13 %) that night.
Without idempotency he would have issued 156 phantom tickets and overshot his capacity by 13 %. With the processed_events table, the 156 duplicates hit the unique constraint and are ignored: 1,200 tickets, zero doubles. Cost avoided: 156 seats double-sold to real customers, roughly NGN 780,000 of seat conflicts avoided at NGN 5,000 per seat.
Need a professional website?
Kolonell builds websites that attract clients, optimized for the Sénégalese market. Free quote in 2 minutes.
FAQ
What exactly does the HMAC signature verify?
It proves the webhook truly comes from Paystack and not an attacker faking a payment. You recompute an HMAC SHA-512 of the payload with the shared secret and compare; the signature expires after 5 minutes to block replay.
Should idempotency live in the database or the cache?
In the database, via a unique constraint: a Redis-style cache can drop and reopen the door to duplicates. The database guarantees atomicity even under two concurrent webhooks a millisecond apart.
How long should I keep processed events?
Keep at least 90 days of event IDs to cover late retries, which can happen up to 24h later but sometimes longer during provider incidents. Archive afterward so the hot table doesn't bloat.
What if no webhook arrives?
Never rely on the webhook alone: add status polling every 5 to 10 seconds during the payment window. About 1 to 2 % of client-confirmed payments emit no usable webhook.
Should retry be exponential?
Yes: space your own retries (10s, 30s, 2min, 10min, 1h) over 5 attempts and 24h. A fixed, too-fast interval amplifies load without improving the success rate.
Let's talk about your project. We set up an idempotent, signed webhook endpoint load-tested for your Paystack payments. 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.
