· security / webhooks / fintech

The webhook that lied about a payment

By Esteban Selaya

A security researcher once got a paid subscription for free by telling the app he'd paid. The app took his word for it.

One JSON message, one free subscription

Jack Cable is a bug bounty hacker who went on to advise CISA, the US government's cybersecurity agency. Before that, he was a student poking at a subscription site when he noticed the address where it received messages from Stripe, its payment processor. When a customer pays, Stripe sends the site an automated message saying so. These messages are called webhooks, and they arrive at a plain web address that anyone on the internet can send requests to. That's not a flaw. It's how webhooks work.

So Cable sent his own message: a few lines of text shaped like a Stripe payment notice. Status: success. His unpaid account flipped to a paid subscription. The site never checked whether the message actually came from Stripe. It trusted anything that looked right. Cable wrote the pattern up in "Bypassing Payments Using Webhooks" and pointed at the reason it's so common: the sample code most developers copy parses the message and acts on it. Checking the sender was barely a footnote.

This bug is still shipping. A security advisory published this year (CVE-2026-41432) describes the same failure in a commercial AI platform: the signature check could be switched off, and a forged webhook could grant unlimited paid credits.

A webhook is a stranger knocking

Your payment processor and your bank-data provider both talk to your product through webhooks, and every one of those messages arrives from the open internet with a claim attached.

"Payment received" is a claim, not a fact, until you've checked who said it.

Every vendor gives you a way to check. The trap is that nothing breaks when you skip it. The product works identically right up until someone hostile notices.

The three checks I run on every webhook

The finance dashboard I run in production receives webhooks from Plaid, the service that connects it to bank accounts. It's bank data, so every incoming message is treated as suspect until it passes three checks.

First, the signature. Plaid signs each webhook, and I verify that signature against Plaid's published keys. No valid signature, no processing. The message gets dropped before any code acts on it.

Second, the signature has to match the raw bytes that actually arrived, not a cleaned-up copy. This is the subtle one. Most code parses the incoming message first and works with the parsed version, but parsing and re-serializing can change the bytes. A check done against the tidied copy can pass while the original was tampered with. I keep the raw body and verify the fingerprint against exactly that.

Third, freshness. A webhook older than five minutes gets rejected, even with a valid signature. Without that window, an attacker who captured a legitimately signed message could replay it later and have it accepted again.

Under all three sits one backstop: the database refuses duplicates. Each webhook carries an identifier, and the database will only ever accept it once, so a message delivered twice can't be processed twice. Building this cost about a day when I wrote the integration. The checks run in milliseconds, on every message, forever.

Why this gap ships so often

Nobody skips these checks on purpose. The happy path works without them and the demo looks identical. AI coding tools make it worse in a quiet way: they reproduce the same sample code everyone else copies. Parse the message, act on it. If a contractor or an AI assistant built your payment or bank-data integration, the odds are good it works beautifully and verifies nothing. The gap is invisible from the outside, which is exactly why it's worth having someone look from the inside.

If your product accepts webhooks from Stripe, Plaid, or anything else that moves money or customer data, and you can't point to where the sender gets verified, the AI Readiness Assessment is a one-week review that finds this class of gap before an attacker does.

Sources

GET THE NEXT ONE

I write these when I ship something worth writing about.

Leave your email and new posts land in your inbox. Nothing else does.

No spam. Unsubscribe with one click, any time.

Start a project

Have a project that needs senior engineering judgment?