Stripe webhooks returning a response of 400

Viewed 1256

I am trying to get stripe webhooks to work on my node.js/express application. For some reason, the stripe cli keeps returning a 400 response. I am new to stripe. is there something I am doing wrong? I followed the docs and the secret key for this webhook is 100% correct and everything is configured properly.

routes/webhook.js

const express = require("express");
const router = express.Router();
const endpointSecret = process.env.WEBHOOK_SECRET;
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);

// Use body-parser to retrieve the raw body as a buffer
const bodyParser = require("body-parser");

// Match the raw body to content type application/json
router.post(
  "/",
  bodyParser.raw({ type: "application/json" }),
  (request, response) => {
    const sig = request.headers["stripe-signature"];

    let event;

    try {
      event = stripe.webhooks.constructEvent(request.body, sig, endpointSecret);
    } catch (err) {
      response.status(400).send(`Webhook Error: ${err.message}`);
    }

    // Handle the event
    switch (event.type) {
      case "payment_intent.succeeded":
        const paymentIntent = event.data.object;
        console.log("PaymentIntent was successful!");
        break;
      case "payment_method.attached":
        const paymentMethod = event.data.object;
        console.log("PaymentMethod was attached to a Customer!");
        break;
      // ... handle other event types
      default:
        // Unexpected event type
        return response.status(400).end();
    }

    // Return a response to acknowledge receipt of the event
    response.json({ received: true });
  }
);
module.exports = router

server.js

const express = require("express");
const router = express.Router();
const connectDB = require("./config/db");
const cors = require("cors");
const app = express();
const env = require("dotenv").config({ path: "./.env" });
console.log(process.env.SECRET_MESSAGE);

//connect DB
connectDB();

//middleware
app.use(express.json({ extended: false }));
app.use(cors());
router.all("*", (_, res) =>
  res.json({ message: "please make a POST request to /stripe/charge" })
);
app.use((_, res, next) => {
  res.header("Access-Control-Allow-Origin", "*");
  res.header(
    "Access-Control-Allow-Headers",
    "Origin, X-Requested-With, Content-Type, Accept"
  );
  next();
});
app.get("/", (req, res) => res.send("API running"));

//routes

app.use("/pay", require("./routes/api/stripe"));
app.use("/pdf", require("./routes/api/pdf"));
app.use("/users", require("./routes/api/users"));
app.use("/auth", require("./routes/api/auth"));
app.use("/webhook", require("./routes/api/auth"));

const PORT = process.env.PORT || 5000;

app.listen(PORT, () => console.log(`listening on port ${PORT}`));

1 Answers

The reason it fails is that your server uses app.use(express.json({ extended: false })); for all routes.

This means that {a: 1, b: 2} can become {b: 2, a: 1} - object property order is not important - when in the webhook route (thus not "raw").

To fix this, conditionally apply the express.json() to non "/webhook" routes:

// server.js
app.use((req, res, next) => {
  if (req.originalUrl.includes("/webhook")) {
    next();
  } else {
    express.json({ limit: "1mb" })(req, res, next);
  }
});
Related