Stripe - Webhook payload must be provided as a string or a Buffer
Asked Answered
K

10

18

I am trying to code a subscription system with stripe. My following code is mostly copied from the stripe docs, but it always shows me this error:

Webhook signature verification failed. Webhook payload must b
e 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.
Learn more about webhook signing and explore webhook integration 
examples for various frameworks at https://github.com/stripe/stripe-node#webhook-signing

This is my code written in javascript:

app.post(
  '/webhook',
  express.raw({ type: 'application/json' }),
  (request, response) => {
    let event = request.body;

    const endpointSecret = 'whsec_.....';

    if (endpointSecret) {

      const signature = request.headers['stripe-signature'];
      try {
        event = stripe.webhooks.constructEvent(
          request.body,
          signature,
          endpointSecret
        );
      } catch (err) {
        console.log(`⚠️  Webhook signature verification failed.`, err.message);
        return response.sendStatus(400);
      }
    }
    let subscription;
    let status;

    switch (event.type) {
      case 'customer.subscription.created':
        subscription = event.data.object;
        status = subscription.status;
        console.log(`Subscription status is ${status}.`);

        break;
      default:
        console.log(`Unhandled event type ${event.type}.`);
    }
    response.send();
  }
);

Does anyone know what I am doing wrong? I have literally been searching the internet for an hour now and cant find anything. Thanks in advance!

Kirsch answered 18/5, 2023 at 13:14 Comment(0)
S
28

It's because app.use(express.json()); code. Try to move webhook route before app.use(express.json());.

// webhook route
app.post(
  '/webhook',
  express.raw({ type: 'application/json' }),
  (request, response) => {
    let event = request.body;

    const endpointSecret = 'whsec_.....';

    if (endpointSecret) {

      const signature = request.headers['stripe-signature'];
      try {
        event = stripe.webhooks.constructEvent(
          request.body,
          signature,
          endpointSecret
        );
      } catch (err) {
        console.log(`⚠️  Webhook signature verification failed.`, err.message);
        return response.sendStatus(400);
      }
    }
    let subscription;
    let status;

    switch (event.type) {
      case 'customer.subscription.created':
        subscription = event.data.object;
        status = subscription.status;
        console.log(`Subscription status is ${status}.`);

        break;
      default:
        console.log(`Unhandled event type ${event.type}.`);
    }
    response.send();
  }
);

app.use(express.json());
Sain answered 19/5, 2023 at 10:6 Comment(1)
moving hook callback before using app.use(express.json()) worked for meSewell
M
13

Referencing the Stripe quickstart server.js code (https://stripe.com/docs/billing/quickstart#provision-access-webhooks), adding the following simple line, placed before app.use(express.json()); solved the issue for me.

app.use('/webhook', express.raw({ type: 'application/json' }));

This is telling Express to do something different specifically for the 'webhook' endpoint. And the given 'express.raw...' parameter is what Stripe was already calling as part of their route method for /webhook.

You won't need to rearrange the ordering of your routes/endpoint code or anything.

Memorable answered 4/8, 2023 at 14:57 Comment(1)
I was struggling with this error for so long, thanks a ton.Inadvisable
R
3

I had the same issue when I was trying to implement the webhook in my react application and I found this solution:

in your server.js file use this code :

app.use(
    bodyParser.json({
        verify: function(req, res, buf) {
            req.rawBody = buf;
        }
    })
);

and you must use it before the app.use(bodyParser.json());

and the in your stripe-file

use this line of code:

event = stripe.webhooks.constructEvent(request.rawBody, sig, endpointSecret);

instead of this line:

event = stripe.webhooks.constructEvent(request.body, sig, endpointSecret);
Refill answered 11/6, 2023 at 19:49 Comment(1)
The rawBody property in Express was once available, but removed since version 1.5.1. To get the raw request body, you have to put in some middleware before using the bodyParser.Suzansuzann
M
1

Using req.rawBody solved it for me

const event = stripe.webhooks.constructEvent(
  req.rawBody,
  sig,
  endpointSecret.value(),
);
Mettle answered 19/2 at 18:38 Comment(1)
That worked for me as well but for some reason the header has 2 values in it. 1 of them is v1 and the other is a t? idk what that means lolMoreen
C
0

It seems like something in your app is parsing the incoming request to a different format than what the function expects (i.e. JSON). Do you have a middleware or parser configured in your app globally? If so, you'd likely want to disable it Or just add an exception where it doesn't touch the incoming request body that's going to /webhook route.

Cleareyed answered 18/5, 2023 at 13:24 Comment(1)
I have the exact same modules and code like in this example: https://stripe.com/docs/billing/quickstart#provision-access-webhooksKirsch
H
0

In your main file, if you're using express.json. Change it to this and add your webhook endpoint. express.json works same as and is an alternative to bodyparser.

app.use(express.json({
verify: (req, res, buf) => {
  if (req.originalUrl.startsWith('/stripe/webhook')) {
    req.rawBody = buf.toString();
  }
},
 }));

Then in your webhook function.Change it to this.

const signature = req.headers['stripe-signature'];
const rawBody = req.rawBody;

  let event;
  try {
    event = stripe.webhooks.constructEvent(rawBody, signature, process.env.WEBHOOKSECRET);
  } catch (err) {
    console.error('Webhook signature verification failed:', err);
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }
Hoke answered 15/3 at 16:17 Comment(0)
E
0

The main issue arises when we utilize the express.json() middleware on the server. This middleware is essential for extracting data, such as IDs or any other information we wish to store.

The optimal solution to this problem is as follows:

  • Create a separate router file specifically for the webhook endpoint. Avoid consolidating all Stripe-related code within the webhook file, as it requires data extraction from JSON files.

  • Declare this file in your server or wherever your main file resides, just before invoking or passing the express.json middleware.

By implementing this approach, the data won't pass through the middleware, ensuring that it remains in its raw form. Consequently, you can utilize the same endpoint without any confusion.

In the image below image, the stripeRouter contains all the endpoints related to Stripe such as creating a customer, checkout session, and payment intent. On the other hand, in the stripeWebhook router, only the webhook endpoint is kept just before passing it to the JSON middleware. This separation ensures a cleaner organization and better handling of data flow.

enter image description here

Euglena answered 3/4 at 6:52 Comment(1)
Code snippets should be added as text within ``` blocks, not as screenshots, because images are not accessibly, searchable, copyable, or editableAvignon
P
0

This worked for me by putting app.post above the app.use(express.json());

I'm using node.js, express and want to put info from stripe into MySQL storage. Hope this helps someone.

import express from 'express';
import cors from 'cors';
import dotenv from 'dotenv';
import stripe from 'stripe';
import { db } from './db.js';

dotenv.config();
const app = express();
const stripeInstance = new stripe(process.env.STRIPE_SECRET_KEY, { apiVersion: '2024-04-10' });

app.use(cors());

// Route for creating a checkout session
app.post('/create-checkout-session', async (req, res) => {
  try {
    const session = await stripeInstance.checkout.sessions.create({
      payment_method_types: ['card'],
      line_items: [{
        price_data: {
          currency: 'hkd',
          product_data: {
            name: 'Team Building Event 101',
          },
          unit_amount: 100000, // $1000 HKD in cents
        },
        quantity: 1,
      }],
      mode: 'payment',
      success_url: 'http://localhost:3000/success',
      cancel_url: 'http://localhost:3000/cancel',
    });

    res.json({ id: session.id });
  } catch (error) {
    console.error(error);
    res.status(500).json({ error: 'An error occurred while creating the checkout session.' });
  }
});

app.post(
  '/api/webhook',
  express.raw({ type: 'application/json' }),
  (request, response) => {
    console.log('Request headers:', request.headers);
    console.log('Request body:', request.body);

    let event = request.body;
    const signature = request.headers['stripe-signature'];

    // Verify the webhook signature
    try {
      event = stripeInstance.webhooks.constructEvent(
        request.body,
        signature,
        process.env.STRIPE_WEBHOOK_SECRET
      );
    } catch (err) {
      console.log(`⚠️  Webhook signature verification failed.`, err.message);
      return response.sendStatus(400);
    }

    // Handle the event based on its type
    switch (event.type) {
      case 'customer.subscription.created':
        const subscription = event.data.object;
        const status = subscription.status;
        console.log(`Subscription status is ${status}.`);
        break;

      case 'checkout.session.completed':
        const session = event.data.object;

        // Extract the Stripe customer ID and email from the session
        const stripeCustomerId = session.customer;
        const customerEmail = session.customer_details.email; // Assuming the email is stored in customer_details.email

        // Prepare the query to insert the Stripe customer ID and email into the database
        const query = `
          INSERT INTO customers (stripe_customer_id, email)
          VALUES (?, ?)
        `;
        const values = [stripeCustomerId, customerEmail];

        // Execute the query
        db.query(query, values, (err, results) => {
          if (err) {
            console.error('Failed to insert customer:', err);
            return response.status(500).send('Failed to insert customer');
          }
          console.log('Customer inserted successfully:', results.insertId);
          response.send();
        });
        break;

        case 'charge.succeeded':
          const chargeSucceeded = event.data.object;
          console.log(`Charge ${chargeSucceeded.id} succeeded.`);
          response.send();
          break;
      
        case 'charge.updated':
          const chargeUpdated = event.data.object;
          console.log(`Charge ${chargeUpdated.id} was updated.`);
          response.send();
          break;
        
      default:
        console.log(`Received unexpected event type: ${event.type}`);
        response.status(400).send(`Unhandled event type: ${event.type}`);
    }
  }
);


app.use(express.json());//Keep below app.post

// Start the server
const PORT = process.env.PORT || 3001;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
Phenice answered 12/6 at 5:50 Comment(0)
E
0

In my case my express router was tempered with many other express middlewares, so I just registered a new express router in my app

const app = express();

const appRouter = express.Router();
const stripeRouter = express.Router();

app.use('/', appRouter);
app.use('/stripe', stripeRouter);
.
.
.
...
stripeRouter.post('/webhook', express.raw({ type: "application/json" }), StripeWebhookClient.executeFunction);
...
.
.
.
Eclat answered 15/7 at 9:21 Comment(0)
V
-2

As stated in the error you should stringify the payload when calling stripe.webhooks.constructEvent.

example:

const sig = request.headers['stripe-signature'];
const body = JSON.stringify(request.body, null, 2);
let event = stripe.webhooks.constructEvent(body, sig, 'your webhook secret');

refer to the example provided by stripe https://github.com/stripe/stripe-node#webhook-signing

Vedette answered 30/8, 2023 at 10:59 Comment(1)
The error says you need to compare the raw request body. There is no guarantee that the result from using JSON.stringify(), will exactly match the original request body sent by Stripe, which is critical when comparing hmac hashes. Do not do this.Lilianaliliane

© 2022 - 2024 — McMap. All rights reserved.