I’ve been trying to implement Stripe webhooks in my Express.js application, but I’m facing issues with webhook signature verification. The error message I’m receiving is:
⚠️ Webhook signature verification failed. Webhook payload must be provided as a string or a Buffer (https://nodejs.org/api/buffer.html) instance representing the _raw_ request body.Payload was provided as a parsed JavaScript object instead. Signature verification is impossible without access to the original signed material.
Here’s my current setup:
index.js
require("dotenv").config();
const express = require("express");
const bodyParser = require("body-parser");
const app = express();
const port = 8080;
const cors = require("cors");
const mongoose = require("mongoose");
app.use(cors());
app.get("/", (req, res) => {
res.send("Hello, World!");
});
// Use these parsers for other routes
app.use(express.json({ limit: "50mb" }));
app.use(express.urlencoded({ limit: "50mb", extended: true }));
const dbURI = process.env.MONGODB_CONNECTION;
const searchRouter = require("./routes/searchRoutes");
const phoneRouter = require("./routes/phone");
const paymentRouter = require("./routes/payment");
const linkRouter = require("./routes/link");
const pieceRouter = require("./routes/piece");
const userRouter = require("./routes/user");
const emailRouter = require("./routes/email");
const autocompleteRouter = require("./routes/autocomplete");
const webhookRouter = require("./routes/webhook");
app.use("/", searchRouter);
app.use("/phone", phoneRouter);
app.use("/payment", paymentRouter);
app.use("/link", linkRouter);
app.use("/piece", pieceRouter);
app.use("/user", userRouter);
app.use("/email", emailRouter);
app.use("/autocomplete", autocompleteRouter);
app.use("/webhook", webhookRouter);
mongoose
.connect(dbURI)
.then(() => {
console.log("MongoDB connected");
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}/`);
});
})
.catch((err) => console.log(err));
webhook.js
require("dotenv").config();
const express = require("express");
const router = express.Router();
const stripe = require("stripe")(process.env.STRIPE_SECRET_TEST_KEY);
const endpointSecret = process.env.STRIPE_TEST_WEBHOOK_KEY;
router.post("/", async (req, res) => {
const sig = req.headers["stripe-signature"];
console.log("Webhook received!");
let event;
try {
event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
} catch (err) {
console.log(`⚠️ Webhook signature verification failed.`, err.message);
return res.sendStatus(400);
}
if (event.type === "checkout.session.completed") {
const session = event.data.object;
try {
const paymentIntent = await stripe.paymentIntents.retrieve(
session.payment_intent,
{
expand: ["charges"],
}
);
const charge = paymentIntent.charges.data[0];
const transferId = charge.transfer;
const taxAmount = session.total_details.amount_tax;
await stripe.transfers.createReversal(transferId, {
amount: taxAmount,
});
console.log(`Successfully reversed tax amount: ${taxAmount}`);
} catch (error) {
console.error("Error creating transfer reversal:", error);
}
}
res.sendStatus(200);
});
module.exports = router;
What am I missing here? How can I properly handle the raw request body for Stripe webhook signature verification?
I’ve tried a number of middleware and looked at a few articles so far but none of them seem to work for me. I don’t know if this matters but I have been using ngrok to expose the local endpoint to test on.
Already checked these articles:
Stripe – Webhook payload must be provided as a string or a Buffer
https://github.com/stripe/stripe-node/issues/341
https://github.com/stripe/stripe-node#webhook-signing
Stripe Webhook 400 error. Raw request body issues
2
Answers
You have bodyParser and that’s problematic with Express and Stripe Webhook Signature. Take a look at this issue and try the suggested workaround (assuming you rename your endpoint to
/stripe-webhooks
)or avoid bodyParser and strictly following Stripe provided sample code.
You don’t need
stripe.webhooks.constructEvent
you can directly usereq.body.event
like below