Fix for Stripe webhook signature validation failures in the payments app. Use when: "Stripe webhook signature verification failed", "Invalid signature", "No signatures found matching the expected signature", webhook 400 errors. Only loads when working in apps/payments/.
Stripe webhook signature validation fails with "No signatures found matching the expected signature" even when the webhook secret is correct.
Stripe webhook signature verification failedNo signatures found matching the expected signature/webhooks/stripe endpointapps/payments/ directoryThe most common cause is body parsing middleware modifying the request body before signature verification.
// WRONG: Body already parsed
app.use(express.json());
app.post('/webhooks/stripe', stripeWebhook); // ❌ Body is already parsed
// CORRECT: Use raw body for webhook endpoint
app.post('/webhooks/stripe',
express.raw({ type: 'application/json' }), // ✅ Raw body
stripeWebhook
);
app.use(express.json()); // Parse JSON for other routes
const stripeWebhook = (req: Request, res: Response) => {
const sig = req.headers['stripe-signature'];
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
try {
// req.body must be the raw buffer, not parsed JSON
const event = stripe.webhooks.constructEvent(
req.body, // Raw buffer
sig,
webhookSecret
);
// Handle event...
} catch (err) {
console.error('Webhook signature verification failed:', err.message);
return res.status(400).send(`Webhook Error: ${err.message}`);
}
};
For NestJS, use rawBody option:
// main.ts
const app = await NestFactory.create(AppModule, {
rawBody: true, // Enable raw body access
});
// webhook.controller.ts
@Post('stripe')
async handleStripeWebhook(
@Headers('stripe-signature') signature: string,
@Req() req: RawBodyRequest<Request>,
) {
const event = this.stripe.webhooks.constructEvent(
req.rawBody, // Use rawBody, not body
signature,
this.webhookSecret,
);
}
apps/payments/stripe listen --forward-to localhost:3000/webhooks/stripe