Fastify parses JSON automatically for all routes. The fix is addContentTypeParser with parseAs: 'buffer' — registered before any other JSON consumer on the webhook route.
// plugins/webhooks.js
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
const secret = process.env.STRIPE_WEBHOOK_SECRET;
async function webhookPlugin(fastify) {
// Override JSON parsing for this plugin — get raw Buffer
fastify.addContentTypeParser(
'application/json',
{ parseAs: 'buffer' },
(req, body, done) => done(null, body)
);
fastify.post('/api/webhooks/stripe', async (request, reply) => {
const sig = request.headers['stripe-signature'];
if (!sig) {
return reply.code(400).send({ error: 'Missing stripe-signature header' });
}
let event;
try {
// request.body is a Buffer from parseAs: 'buffer'
event = stripe.webhooks.constructEvent(request.body, sig, secret);
} catch (err) {
return reply.code(400).send({ error: \`Webhook Error: \${err.message}\` });
}
switch (event.type) {
case 'checkout.session.completed':
// fulfill order
break;
}
return { received: true };
});
}
export default webhookPlugin;
// server.js — register webhook plugin BEFORE other JSON routes
await fastify.register(import('./plugins/webhooks.js'));
// Other routes that use JSON parsing can come after
await fastify.register(import('./routes/api.js'), { prefix: '/api' });
addContentTypeParser affects all routes in the current plugin scope. Registering it in a dedicated plugin keeps it isolated from your other routes that need normal JSON parsing.Generate the complete Fastify webhook plugin automatically.
Open WebhookFix →