Extracting Raw Body from Stripe Webhook in NextJS

I'm feeling frustrated trying to get a raw body passed for the Stripe webhook in NextJS!

Despite trying numerous solutions from various sources, I just can't seem to get it to work.

Calling upon the developers with special powers (of which I am still honing).

An error message received from Stripe Test:

No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe?

This is my test of the NextJS webhook endpoint:

import { buffer } from 'micro';
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

export default async function handler(req, res) {

    console.log("Payment intent")

    let event = req.body

    console.log(event)

    if (process.env.STRIPE_WEBHOOK_ENDPOINT_SECRET) {
        // Get the signature sent by Stripe
        const signature = req.headers['stripe-signature'];
        console.log(signature)
        try {
          event = stripe.webhooks.constructEvent(
            req.body,
            signature,
            process.env.STRIPE_WEBHOOK_ENDPOINT_SECRET
          );
        } catch (err) {
          console.log(`⚠️  Webhook signature verification failed.`, err.message);
          return res.sendStatus(400);
        }
      }

    console.log(event.type)

    // Handle the event
    switch (event.type) {
        case 'payment_intent.succeeded':
            console.log("Success!")
            break;
        default:
            console.log(`Unhandled event type ${event.type}`);
    }



}

I have also experimented with this setup:

import { buffer } from 'micro';
import Cors from 'micro-cors';

const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);

const webhookSecret = process.env.STRIPE_WEBHOOK_ENDPOINT_SECRET_NEW;

// Stripe requires the raw body to construct the event.
export const config = {
  api: {
    bodyParser: false,
  },
};

const cors = Cors({
  allowMethods: ['POST', 'HEAD'],
});

const webhookHandler = async (req, res) => {

  if (req.method === 'POST') {
    const buf = await buffer(req);
    console.log(buf.toString())
    const sig = req.headers['stripe-signature'];
    console.log(process.env.STRIPE_SECRET_KEY)
    console.log(webhookSecret)
    console.log(sig)
    let event;

    try {
      event = stripe.webhooks.constructEvent(
        buf.toString(),
        sig,
        webhookSecret
      );
    } catch (err) {
      console.log(`❌ Error message: ${err.message}`);
      res.status(400).send(`Webhook Error: ${err.message}`);
      return;
    }


    if (event.type === 'payment_intent.succeeded') {
      const paymentIntent = event.data.object;
      console.log(`💰 PaymentIntent status: ${paymentIntent.status}`);
    } else if (event.type === 'payment_intent.payment_failed') {
      const paymentIntent = event.data.object;
      console.log(
        `❌ Payment failed: ${paymentIntent.last_payment_error?.message}`
      );
    } else if (event.type === 'charge.succeeded') {
      const charge = event.data.object;
      console.log(`💵 Charge id: ${charge.id}`);
    } else {
      console.warn(`🤷‍♀️ Unhandled event type: ${event.type}`);
    }

    res.json({ received: true });
  } else {
    res.setHeader('Allow', 'POST');
    res.status(405).end('Method Not Allowed');
  }
};

export default cors(webhookHandler);

Answer №1

By default, NextJS automatically parses the request body based on the Content-Type in the headers. To change this behavior [0] and handle it as a stream using buffer.

This code snippet has been effective for me :

export const config = {
  api: {
    bodyParser: false,
  },
};

import { buffer } from 'micro';
const stripe = require('stripe')(process.env.STRIPE_WEBHOOK_ENDPOINT_SECRET);


export default async function handler(req, res) {

    let event;

    if (process.env.STRIPE_WEBHOOK_ENDPOINT_SECRET) {
        // Retrieve the signature sent by Stripe
        const signature = req.headers['stripe-signature'];
        const buf = await buffer(req);

        try {
          event = stripe.webhooks.constructEvent(
            buf,
            signature,
            process.env.STRIPE_WEBHOOK_ENDPOINT_SECRET
          );
...

[0] https://nextjs.org/docs/api-routes/api-middlewares#custom-config

Answer №2

In order to resolve this issue, I made the decision to deactivate the NextJS body parser by exporting the configuration object within the same api route file:

export const config = {
  api: {
    bodyParser: false,
  },
};

Subsequently, I included the raw-body package to enable me to transform the request stream into a raw buffer:

var getRawBody = require('raw-body')

Lastly, I utilized the raw-body package through a promise interface:

if (endpointSecret) {
  const signature = request.headers['stripe-signature'];
  console.log("sig", signature);
  getRawBody(request)
    .then(function (buf) {
      rawBody = buf;
      event = stripe.webhooks.constructEvent(
        rawBody,
        signature,
        endpointSecret
      );
      let subscription;
      let status;
      // Handle the event
      switch (event.type) {
        case 'customer.subscription.trial_will_end':
          subscription = event.data.object;
        
        default:
          // Unexpected event type
          console.log(`Unhandled event type ${event.type}.`);
      }
      // Return a 200 response to acknowledge receipt of the event
      return response.status(200);
    })
  .catch(function (err) {
    console.log(`⚠️  Webhook signature verification failed.`, err.message);
    return response.status(500);
  })
} else {
  console.log("Missing endpoint secret");
  return response.status(500);
}

This approach proved successful for me. Hopefully, it can be beneficial to you as well.

Answer №3

While this question may be dated, I wanted to mention that I have successfully incorporated Stripe into my project utilizing webhooks and the new /app router.

The complete step-by-step guide is detailed in the README.md file.

For those interested, the repository can be accessed at the following link: https://github.com/BastidaNicolas/nextauth-prisma-stripe

Answer №4

Hey there, friend! After working on this for an hour, I finally cracked it. Here's the endpoint code you need to log the event:

import { buffer } from "micro";
import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
});
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;

const handler = async (req, res) => {
  if (req.method === "POST") {
    console.log("req", req, "time to buffer");
    const buf = await buffer(req);
    const sig = req.headers["stripe-signature"];

    let event;
    console.log("buf", buf);

    try {
      event = stripe.webhooks.constructEvent(buf, sig, webhookSecret);
      console.log("Success:", event);
      res.status(200).send("Success");
      return;
    } catch (err) {
      res.status(400).send(`Webhook Error: ${err.message}`);
      return;
    }
  } else {
    res.setHeader("Allow", "POST");
    res.status(405).end("Method Not Allowed");
  }
};
export const config = {
  api: {
    bodyParser: false,
  },
};

export default handler;

Answer №5

If you're on NextJS 14, another way to accomplish this is:

import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET!, {
    apiVersion: '2023-10-16',
});

export async function POST(request: Request) {
    const stripeSignature = request.headers.get('stripe-signature')!;
    const rawBody = await request.text();

    let event = stripe.webhooks.constructEvent(rawBody, stripeSignature, process.env.STRIPE_WEBHOOK_SECRET!);
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

React-router: Issue with ProtectedRoute authentication mechanism not functioning as expected

I'm currently working on setting up protected routes that can only be accessed after the user has logged in. There are two main routes to consider: /login, which is the Login component (public), and /, which is the Dashboard component (protected). Wh ...

Tips for handling two JSON array objects with JavaScript

This particular function public function groups_priviledges($user_id = NULL){ $data['groups'] = $this->priviledges_model->admin_groups(); $data['member'] = $this->priviledges_model->empAdminGroup($user_id); echo ...

retrieving a URL with the help of $.getJSON and effectively parsing its contents

I seem to be struggling with a coding issue and I can't quite figure out what's wrong. My code fetches a URL that returns JSON, but the function is not returning the expected string: function getit() { var ws_url = 'example.com/test.js&ap ...

Steps to successfully implement onClick functionality in html within C# server side code

I'm having trouble with my onClick function. When I click, nothing happens and there are no errors to help me diagnose the issue. var data = new FormData(); data.append("cart_list", new_cart); $.ajax({ url: "@Url.Action ...

Singling out a particular navigation tab

When attempting to link specific table IDs so that the corresponding tab is active when opened (i.e. www.gohome.com/html#profile), I am facing an issue where the active tab remains unchanged. Even after specifically calling out tab IDs, there seems to be n ...

The Mean Stack by Bitnami

After setting up the Mean Stack Manager, I encountered an issue. When running a command in the node terminal like console.log("Hello World"), it runs smoothly. However, if I place a Javascript sample file in any folder within the mean stack install directo ...

The Jade variable assignment variable is altered by the Ajax result

Seeking a solution to update a Jade variable assigned with the results of an ajax post in order for the page's Jade loop to utilize the new data without re-rendering the entire page. route.js router.post('/initial', function(req, res) { ...

The function JSON.parse(data) may result in an undefined value being returned

data = { "users": [ [{ "value": "01", "text": "ABC XYZ" }], [{ "value": "02", "text": "XYZ ABC" }] ] } var jsonData = JSON.parse(data); for (var i = 0; i < jsonData.users.length; i++) { var userlist = json ...

Using MUI ClickAwayListener to automatically close the modal upon clicking in React.Js

In order for the modal to work correctly, it should be visible when the 'More' button is clicked and should close when either the More button or any other part of the screen is clicked (excluding the modal itself). I've attempted different m ...

Is it possible to show more than five buttons in an Amazon Lex-v2 response display?

Here is the sessionState object I am working with: { "sessionAttributes": {}, "dialogAction": { "type": "ElicitSlot", "slotToElicit": "flowName" }, "intent": { "name": &q ...

How can I generate a .json file that includes PHP variables?

How can I create a customized JSON file called "file.json.php" using PHP variables such as $_POST['foo']? This file will produce different results based on the value of the post variable passed through an ajax call. What settings do I need to imp ...

Display or conceal various objects using a single button in HTML

I've been working on creating a chatbot widget for my website, but I've run into an issue. The "Chat with us" button only shows the bot and not the close button as well. Here's what I've attempted: <input id="chat" type="button" on ...

Error: Nextjs is unable to read the property 'map' because it is undefined

I am struggling with an error message that says "Cannot read property 'map' of undefined" in my React code. Can someone please help me troubleshoot this issue? I am a beginner and any assistance would be greatly appreciated. {rooms && rooms.lengt ...

AngularJS: engaging search experience

I'm just starting to learn AngularJS, and I have a project where I need to create an interactive search feature. So far, this is what I have: article/views/articles.html <form class="form-inline"> <div class="form-group"> < ...

Need to know how to retrieve the li element in a ul that does not have an index of 2? I am aware of how to obtain the index greater than or less

I'm looking to hide all the li elements except for the one with a specific index. I've written some code to achieve this, but I'm wondering if there's a simpler way using jQuery. While jQuery provides methods like eq, gt, and lt, there ...

Real-time collaborative Whiteboard using WebSocket technology (socket.io)

I am currently working on developing a collaborative online whiteboard application using HTML5 canvas, Node.js, and Websockets (Socket.io). While my progress is going well, I am facing some challenges when it comes to drawing circles. I have been successfu ...

Encountering a TypeError stating "getStaticPaths is not a function" when integrating framer-motion with Next.js

I'm encountering a problem with framer-motion. I'm experimenting with it, but I keep running into the following error: Error [TypeError]: getStaticPaths is not a function at buildStaticPaths (/Users/x/coding/projects/personal/website/node_mod ...

Problem encountered while revalidating Next.js and Sanity documents through a webhook

I'm currently developing a Next.js 13 project that utilizes Sanity as its CMS and is deployed on Vercel. To ensure that components and pages are revalidated whenever a new testimonial is added, updated, or removed in Sanity, I have configured a webhoo ...

There is an error in Node.js where it is unable to read the property 'path' of an undefined object

Encountering an issue with my local host while developing a React and Node application. The error message I am receiving is: UNHANDLED REJECTION TypeError: Cannot read property 'path' of undefined Despite having all npm modules installed, this ...

Is it possible to set all UI forms to a readonly/disable mode?

We have a specific requirement where, if the user's access level is set to "READ ONLY", all form input elements should be made readonly. Our coding approach involves using a template HTML that contains widgets which are referenced in the correspondin ...