Five Paystack and Flutterwave Mistakes That Leave Orders Unpaid or Money Missing
When a customer says "I paid but my wallet is empty", the money is rarely lost. Usually it is sitting at the payment company, and your system never got the message, or got it and did the wrong thing with it.
This article is about design, not debugging one error. If you already have a specific symptom, our other posts cover them: a Paystack webhook that is not firing, a Flutterwave webhook that never arrives, and a failed payment where money was deducted. Here we go one level up: five mistakes that cause the symptoms in the first place.
First, the rules the two companies publish, because everything below depends on them.
| Paystack | Flutterwave | |
|---|---|---|
| How you prove the message is real | x-paystack-signature header: an HMAC SHA512 of the raw body, signed with your secret key |
verif-hash header that must match the secret hash you set on the dashboard |
| What counts as success for them | You return a 200 | You return a 200 (any other code, even a redirect, counts as a failure) |
| Retries in live mode | Every 3 minutes for the first 4 tries, then hourly for up to 72 hours | You must switch retries on; then 3 retries, 30 minutes apart |
| Timeout | 30 seconds in test mode | 60 seconds |
These come from each company's own documentation. They can change, so check the current pages before you rely on them.
Mistake 1: Doing the slow work before you answer
What goes wrong. The webhook arrives. Your code credits the wallet, sends an email, calls another API, updates a spreadsheet, and only then returns 200. If any step is slow, the payment company times out and counts it as a failure. It then sends the same event again, and again.
The fix. Do the minimum, return 200 fast, and do the rest after. Both companies say this in their docs: acknowledge first, then do the long work, or put the work in a queue.
Mistake 2: Treating a retry as a new payment
What goes wrong. Retries are normal, and Flutterwave says plainly that it may send the same event more than once. If your code adds money every time a message arrives, one payment can credit a wallet three times.
The fix. Make the handler idempotent. Give every payment a unique reference, store it, and check it before you credit. If the reference was already processed, return 200 and do nothing. A database rule that makes the reference unique is the safest guard, because it still works when two copies arrive at the same moment.
Mistake 3: Trusting the message without checking it
What goes wrong. Your webhook address is public. Anyone can send a fake "payment successful" message to it.
The fix. Check the proof every time, before anything else. For Paystack, compute the HMAC SHA512 of the raw body and compare it with the header. For Flutterwave, compare the verif-hash header with your secret hash. Use a safe comparison, not a plain equals sign.
Two details cause many "signature failed" tickets:
- You must hash the raw body, exactly as received. If your framework parses the JSON first and you hash the re-written version, the signature will not match.
- Do not trust the amount in the message alone. Flutterwave advises you to re-query its API to confirm the transaction before you give value. That is a good rule for both companies.
Mistake 4: Not checking the amount and the account
What goes wrong. A customer is meant to pay ₦5,000 but pays ₦500, or pays against the wrong reference. A system that only looks at the word "success" credits the full order.
The fix. After the signature check, compare three things against your own record: the reference, the amount (in the right unit, kobo or naira) and the currency. If the amount is lower, do not release the goods. Credit what was actually paid, or hold it for review. This also protects you from the case where one customer reuses another customer's payment link.
For wallet funding, a good rule is simple: credit the wallet with the amount that actually arrived, not the amount the customer said they would pay.
Mistake 5: Having no way to catch what the webhook missed
What goes wrong. Servers restart. Deploys happen. A bad deploy returns errors for ten minutes. Paystack's retries run for up to 72 hours, but Flutterwave's stop after three tries, and a payment can still fall between the cracks.
The fix. Do not depend on the webhook alone. Add a second path:
- Verify by reference. When a customer comes back from the payment page, your page asks the payment company directly: "What happened to reference X?" If it was paid, credit it, using the same idempotent code as the webhook.
- A reconciliation job. Every so often, list payments that are still "pending" on your side and ask the payment company about each one. Anything that was paid gets credited. Anything older than your limit gets marked failed.
- A visible record. Keep a table of every payment with its status, so a person can look at one customer and see what happened.
This is how the XpiriaTech VTU platform treats funding: it checks the signature, records each payment once by its reference, and has a verify-by-reference check for the times a message is late, so a delayed webhook does not leave a customer's money stuck.
A quick test plan
Before you go live, try each of these on purpose:
- Send the same webhook twice. The wallet should rise once.
- Send a webhook with a wrong signature. It should be refused.
- Send a webhook with a lower amount than the order. It should not complete the order.
- Turn your server off for ten minutes while a test payment completes. After you turn it on, the payment should still be credited, either by a retry or by your check.
- Make the credit step slow. The webhook should still return 200 quickly.
If all five pass, you have avoided most payment support tickets.
Where we can help
We build payment integrations and the backend behind them. If you are collecting money in Nigeria and want a second pair of eyes on your webhook handling, or someone to build it properly, see our full-stack and backend development service or ask for a code audit.
Software engineer with over 9 years of experience in software development and digital technology. B.Sc. in Industrial Chemistry from Adekunle Ajasin University. Has worked across web development, mobile applications, e-commerce platforms, VTU and fintech solutions, API integrations and custom software.




Comments
No comments yet. Be the first to share your thoughts.