June 16, 2026 · Payments · Backend
Verifying payment webhooks properly: the check most integrations skip
A webhook endpoint that trusts its own payload is a fraud vector wearing a checkout flow. Here's what signature verification actually needs to do, and where I've seen it done wrong.
02 From building Animal MartA payment webhook tells your server "this order was paid" — and if your endpoint believes that message just because it arrived at the right URL with the right shape, you've built a system where anyone who can guess your webhook URL can mark any order as paid. This is not a hypothetical; it's the most common gap I see reviewing checkout integrations.
On Animal Mart, five payment methods go live through one webhook surface, each signed differently. The fix is the same shape everywhere: HMAC-SHA256 verification, done correctly, on every single request before any order state changes.
What "done correctly" actually means
- Verify the signature against the raw request body, not a re-serialized version of the parsed JSON — re-serialization can silently change the bytes and invalidate a signature that was actually valid
- Use a constant-time comparison for the signature check, not `===` — a naive string comparison leaks timing information an attacker can use to guess the correct signature byte by byte
- Reject the request outright on a signature mismatch — don't log a warning and process it anyway, which defeats the entire point
- Store and check idempotency keys, so a retried webhook (which providers send deliberately) can't double-fulfil an order
The failure mode that's easy to miss
The subtle bug isn't usually "no verification at all" — it's verification that exists but checks the wrong thing, like validating a signature against a payload your framework already reparsed and reformatted. If your web framework parses the body into an object before your handler sees it, you may already have lost the exact bytes the signature was computed over. The fix is almost always to read the raw body explicitly, before any JSON parsing touches it.
None of this is exotic — it's the same handful of checks regardless of which payment provider you're integrating. The cost of skipping them is a fraud path that looks, from your database's point of view, exactly like a legitimate paid order.