I’m having an issue with Stripe webhooks. Locally, everything works fine, but on my server, I keep receiving the following error:
No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe?
If a webhook request is being forwarded by a third-party tool, ensure that the exact request body, including JSON formatting and new line style, is preserved.
Learn more about webhook signing and explore webhook integration examples for various frameworks at https://github.com/stripe/stripe-node#webhook-signing
I understand that the data should be passed raw into the route without any modifications. I have verified that the request reaches the correct route since the error only occurs at that endpoint, and I am using the Stripe library for handling webhooks.
Here is the relevant part of my code:
const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const bodyParser = require('body-parser');
const app = express();
app.use((req, res, next) => {
// Check if the request is for the '/webhook' endpoint
console.log(req.originalUrl);
if (req.originalUrl === '/api/webhook') {
// Continue to the next middleware or route handler
next();
} else {
// If not a '/webhook' request, use the express.json() middleware
express.json()(req, res, next);
}
});
app.post('/api/webhook', bodyParser.raw({ type: 'application/json' }), async (req, res) => {
const endpointSecret = process.env.STRIPE_SECRET_KEY;
const sig = req.headers['stripe-signature'];
let event;
try {
if (!endpointSecret) {
return res.status(400).send('Webhook Error: Missing secret key');
}
event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
switch (event.type) {
case 'checkout.session.completed':
const session = event.data.object;
if (!session.metadata) return;
const metaData = JSON.parse(session.metadata.invoiceInfo.toString());
if (session.payment_status === 'paid') {
const redisData = await RedisManager.Get(`${RedisKey.StripPayment}_${session.id}_${metaData.userId}_${metaData.invoiceId}`);
const data = await Unitofwork.invoiceService.GetInvoiceByIdForPayment(redisData?.id);
const comission = data.platformComission;
let credit = ((data.creditPercent || 0) / 100) * comission;
let platformComission = (comission + (Number(data.networkFee) * Number(data.netwrokExchangeRate))) + credit;
let amount = await this.calculatePrice(data.price, data.discount);
const hash = await this.transferSameToken(redisData?.payBy._id, data.reciverAddressWallet, amount, platformComission, true);
if (typeof hash == 'object') {
data.transactionHash = hash.transactionTxn;
data.platformTransactionHash = hash.platformTxn;
data.message = 'invoice.message_successfull';
data.status = InvoiceStatus.Success;
await new CreditUtil().updateUserCredit(data, redisData?.payBy._id);
data.save();
await CpayNotification.sendNotification(data, true);
await RedisManager.Remove(`${RedisKey.StripPayment}_${session.id}_${metaData.userId}_${metaData.invoiceId}`);
}
}
break;
case 'payment_intent.succeeded':
const paymentIntent = event.data.object;
break;
case 'payment_method.attached':
const paymentMethod = event.data.object;
break;
default:
console.log(`Unhandled event type ${event.type}.`);
}
res.status(200).send();
} catch (err) {
console.log(err);
res.status(400).send(`Webhook Error: ${err.message}`);
}
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
Additional Context
- The code works perfectly on my local machine. I suspect that Nginx
- might be altering the request body before it reaches my application
on the server.
How can I ensure that the raw request body from Stripe is passed unchanged to my application? What configurations or settings do I need to adjust in Nginx to solve this issue?