DEV Community

Kaibalya Kar
Kaibalya Kar

Posted on

Debugging Stripe Webhooks in Node.js: The “Payload Must Be a String or Buffer” Error

When integrating Stripe into a Node.js application, webhooks play a critical role in handling real-time events like payments, subscriptions, and invoices. However, I recently ran into a tricky issue while verifying Stripe webhooks in my backend. The error looked something like this:

❌ Webhook signature verification failed: Webhook payload must be provided as a string or a Buffer instance representing the _raw_ request body. Payload was provided as a parsed JavaScript object instead.
Enter fullscreen mode Exit fullscreen mode

At first glance, it seemed like a simple mismatch, but the root cause was a little deeper. Here’s how I solved it step by step.

The Problem

Stripe verifies webhooks by checking a signature against the raw request body. However, most Express apps use body parsers (express.json() or body-parser) which automatically parse the incoming JSON request into a JavaScript object.

This means that by the time the request reaches Stripe’s constructEvent method, the raw body is no longer available, leading to the above error.

The Fix

The fix is to tell Express not to parse the body for Stripe webhook routes. Instead, we need the raw body as a Buffer.

Here’s the corrected setup:

import express from "express";
import Stripe from "stripe";
import bodyParser from "body-parser";

const app = express();
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY_TEST);

// ✅ Use raw body only for the webhook endpoint
app.post(
  "/webhook",
  bodyParser.raw({ type: "application/json" }),
  (req, res) => {
    const sig = req.headers["stripe-signature"];
    let event;

    try {
      event = stripe.webhooks.constructEvent(
        req.body, // raw body (Buffer)
        sig,
        process.env.STRIPE_WEBHOOK_SECRET
      );
      console.log("✅ Webhook verified:", event.type);
    } catch (err) {
      console.error("❌ Webhook signature verification failed:", err.message);
      return res.sendStatus(400);
    }

    // Handle event
    if (event.type === "payment_intent.succeeded") {
      console.log("💰 Payment received!");
    }

    res.json({ received: true });
  }
);

// Normal JSON parsing for other routes
app.use(express.json());

app.listen(5000, () => console.log("Server running on port 5000"));

Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  • Don’t parse Stripe webhook requests with express.json() or body-- parser.json().

  • Use bodyParser.raw({ type: "application/json" }) for the webhook route.

  • Always test your webhook integration using the Stripe CLI:

stripe listen --forward-to localhost:5000/webhook

Enter fullscreen mode Exit fullscreen mode
  • If you see this error, it almost always means the body was already parsed into an object before reaching Stripe’s verifier.

Conclusion

Debugging this issue was a great reminder that not all requests should be parsed the same way. Stripe needs the original, untouched request body to verify signatures properly. Once I separated the raw body parsing logic only for the webhook endpoint, everything worked flawlessly.

If you’re integrating Stripe into your Node.js backend and run into this error, just remember: webhooks need raw body, not JSON!

Top comments (0)