skip to Main Content

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


  1. 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)

    app.use(bodyParser.json({
    // Because Stripe needs the raw body, we compute it but only when hitting the Stripe callback URL.
    verify: function(req,res,buf) {
        var url = req.originalUrl;
        if (url.startsWith('/stripe-webhooks')) {
            req.rawBody = buf.toString()
        }
    }}));
    

    or avoid bodyParser and strictly following Stripe provided sample code.

    Login or Signup to reply.
  2. You don’t need stripe.webhooks.constructEvent you can directly use req.body.event like below

    app.post('/webhook', async (req,res) => {
      
      //your code
    
      const event =req.body;
      
      if(event.type === 'checkout.session.completed'){
          //logic
      }
    
    });
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search