The risk of unverified webhooks
When integrating with Paystack, Flutterwave, or Stripe, the payment flow is asynchronous. The user completes the transaction on the provider's page, and the provider sends an HTTP POST request (the webhook) to your server to confirm the status.
If your server processes that payload without cryptographically verifying its origin, anyone who discovers the endpoint URL can send a POST request formatted to look like a successful payment. This bypasses the actual payment gateway entirely.
Signature Forgery
Attackers send payloads to your webhook endpoint without the correct HMAC signature. If your application doesn't validate the signature, the fake payment is processed.
Replay Attacks
An attacker intercepts a legitimate, signed webhook and resends it multiple times. If your system lacks idempotency, it credits the user repeatedly.
Timing Attacks
Attackers analyze the response time of your signature verification logic to guess the expected signature byte by byte. Use constant-time string comparison to prevent this.
Mandatory mitigation: HMAC signature verification
Payment providers publish a signing method for their webhooks. Many use an HMAC over the raw body and place the result in a request header, such as `x-paystack-signature`. Use the provider's current method and compare signatures without leaking timing data.
- Capture the raw body: Do not parse the JSON before verifying the signature. The HMAC is calculated on the raw, stringified payload. Any formatting changes will invalidate the hash.
- Calculate your own hash: Use your environment's secret key and the raw payload to generate an HMAC-SHA256 (or provider-specified) hash.
- Use constant-time comparison: Compare the header signature with your calculated hash using a secure compare function (like `crypto.timingSafeEqual` in Node.js) to prevent timing attacks.
Are your payment webhooks vulnerable to forgery or replay attacks?
Discuss webhook testingDefending against replay attacks
Even with perfect signature validation, a valid webhook can be intercepted and resent. To prevent a single ₦10,000 payment from crediting a wallet ten times, you must implement idempotency.
Use the unique transaction reference or event ID provided in the webhook payload as an idempotency key. Before processing the event, check your database or a distributed cache (like Redis). If the key has already been processed, respond with a 200 OK but skip the business logic.
How we test webhook security
During an API security assessment, our engineers actively attempt to break webhook integrations using several techniques:
- Sending payloads with missing signature headers.
- Sending payloads with invalid signatures or mismatched algorithms.
- Modifying the payload body (e.g., changing the amount) while keeping a valid signature from a previous request.
- Replaying legitimate webhook payloads.
- Testing for Server-Side Request Forgery (SSRF) if the webhook URL is dynamically constructed or if the application fetches remote resources based on the payload.
Verification as a middleware, not a suggestion
A common vulnerability pattern we see in Nigerian fintechs is placing the signature verification logic inside the controller, where it occasionally gets bypassed by new routes or refactoring. Signature validation should be implemented as a strict middleware that drops invalid requests before they ever reach your business logic.
Related reading
Blog: Payment gateway penetration testing · BOLA in payment APIs · Fintech API Security: 10 Steps
Services: API Security Testing · Penetration Testing
Industries: Payment Gateways
Frequently asked questions
Why can't I just trust the IP address sending the webhook?
No. An allowlist can narrow the source network, but it does not prove who created the payload. Verify the provider's cryptographic signature against the raw request body and reject failed checks.
What happens if a webhook is processed twice?
Without idempotency keys, processing a 'payment_successful' webhook twice could result in crediting a user's wallet twice for a single payment. Idempotency ensures duplicate notifications are safely ignored.
How do attackers discover my webhook endpoint?
Webhook endpoints are often exposed in client-side code, guessed through directory enumeration, or discovered if an attacker registers a test account on your platform and observes the callback structure.