highAPI behaviour
Webhook Accepted Without Checking Its Signature
Events posted to the webhook are trusted without proof they came from the provider.
Why it matters
Webhook URLs are guessable and the payload format is public. Without the signature check, a request that claims "payment succeeded" is treated as real. Every provider signs its events and ships a one-line verify helper for exactly this reason.
What it looks like
This is the shape of code that triggers the rule. AI tools produce it because it works, and nothing tells them it is unsafe.
Stripe webhook handled without verification
app.post('/webhook/stripe', express.json(), async (req, res) => {
const event = req.body;
if (event.type === 'checkout.session.completed') await fulfil(event.data.object);
res.json({ received: true });
});The smallest fix
minimal patch
// Stripe example. Use the raw body, not parsed JSON:
export async function POST(req: Request) {
const sig = req.headers.get('stripe-signature')!;
const raw = await req.text();
let event;
try {
event = stripe.webhooks.constructEvent(raw, sig, process.env.STRIPE_WEBHOOK_SECRET!);
} catch {
return new Response('Invalid signature', { status: 400 });
}
// handle event.type ...
}The better fix
If you have a few more minutes, this is the approach that holds up as the app grows.
safe alternative
// Also record processed event ids so a replayed event is ignored, and
// only act on the fields inside the verified event, never on values
// from the query string.Let your AI tool fix it
When the scanner finds this in your project, it fills in the file and line for you. This is the prompt it gives you to paste into Claude Code, Cursor or whatever you use.
The webhook handler in [the file] does not verify the provider's signature. Read the raw request body, verify it with the provider's helper (for Stripe: stripe.webhooks.constructEvent with STRIPE_WEBHOOK_SECRET; for others, an HMAC compare using crypto.timingSafeEqual), return 400 when it fails, and only then process the event. Store handled event ids to ignore replays.
How to check the fix worked
1. Post a hand-made event to the URL with no signature header. Expect 400 and no side effects. 2. Use the provider's CLI or test mode to send a real event. Expect it to be processed once. 3. Send the same real event twice. Expect the second to be ignored.