Quick Answer
Your AI webhook handler assumes the payload arrives in exactly the shape it expects. But what happens when the provider adds a field? Or drops an old one? Or an attacker just sends you garbage? Without validation, your code either fails silently or falls over. api-doctor catches this before you ship.
The problem: webhooks change shape when you least expect it
Your agent writes a handler that trusts event.data.object.customer and event.data.object.amount to always be there with the right types. Then Stripe sends a connected-account charge where customer is null. Now your code calls updatePaymentStatus(null, 100), your database logs a row with no customer ID, and your accounting is quietly wrong. You won't notice until you sit down to reconcile payments and the numbers refuse to line up.
Stripe actually warns you about this: customer can be null for connected-account charges and transfers, so validate required fields before you use them. The AI never made it to that paragraph.
And that's the pattern. The model has seen mountains of webhook handlers, but far less of the defensive code that checks payloads first. So it writes the happy path and leaves validation on the cutting-room floor.
How api-doctor catches this
✗ Stripe: 1 issue
stripe-webhook-payload-not-validated
src/webhooks.ts:12
Webhook handler does not validate event payload schema. Missing fields will cause silent failures.
Severity: warning
Reliability
How to fix it: validate webhook payloads
Reach for a schema validation library (zod, joi, yup) and validate the incoming webhook before you touch a single field.
Using Zod:
import { z } from 'zod';
const StripeChargeSchema = z.object({
type: z.literal('charge.succeeded'),
data: z.object({
object: z.object({
id: z.string(),
customer: z.string().nullable(),
amount: z.number(),
currency: z.string(),
}),
}),
});
app.post('/stripe-webhook', (req, res) => {
try {
const validEvent = StripeChargeSchema.parse(req.body);
if (validEvent.data.object.customer) {
updatePaymentStatus(
validEvent.data.object.customer,
validEvent.data.object.amount
);
} else {
console.log('Skipping charge with no customer:', validEvent.data.object.id);
}
} catch (err) {
console.error('Invalid webhook payload:', err.message);
return res.sendStatus(400);
}
res.sendStatus(200);
});
Now the shape gets checked up front, missing fields are handled gracefully, unexpected data can't crash you, and when validation does fail you get a clear line in the logs telling you why.
Frequently asked questions
Q: Is validation really necessary? Yes. Even if the provider's payload is always perfect, an attacker's won't be. Validate everything that crosses your boundary.
Q: Which schema library should I reach for? Zod, Joi, or Yup all work. Zod has TypeScript inference baked in, which makes it the path of least resistance for Next.js and TypeScript projects.
Q: What if the provider changes their webhook schema? Your validation flags it on the spot. You get loud errors in your logs instead of silent data corruption, you update the schema, and you carry on.
Q: Should I validate in my agent code or in my handler? In your handler. It's the one place you can actually guarantee the check runs. Agent-generated code might include validation or might not, but your handler is the last line of defense either way.