stripe-node: NextJS - Error: construction of webhook event returned an error: No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe? https://github.com/stripe/stripe-node#webhook-signing

Hi

I am pulling my hair out trying to pass raw JSON to NextJS 12 endpoint.

I have tried sooo many solutions and I just cant get it to work.

Please help as I have wasted half a day on this and I am at a loss.

Create test webhook in stripe and passing in Secret Signing key from webhook test dashboard “whsec …” I get a signature from the test webhook I trigger in the dashboard but I am constantly getting:

Error: construction of webhook event returned an error: No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe? https://github.com/stripe/stripe-node#webhook-signing

The code below is from Reddit, where some had a similar issue.

I have tried all of these solutions here: https://github.com/vercel/next.js/discussions/13405

Seems to be an issue in a lot of forums.

Please help.

Exhausted!

import getRawBody from "raw-body";

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

const webhookSecret = process.env.STRIPE_WEBHOOK_ENDPOINT_SECRET;

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

const webhookHandler = async (req, res) => {
  if (req.method === "POST") {
    const rawBody = await getRawBody(req);

    // Extract Stripe signature from header
    const signature = req.headers["stripe-signature"];
    console.log(rawBody)
    console.log(signature)
    console.log(webhookSecret)

    let event;

    // Attempt to reconstruct and validate passed event
    try {
      event = stripe.webhooks.constructEvent(rawBody, signature, webhookSecret);
    } catch (err) {
      res
        .status(400)
        .send(
          `Error: construction of webhook event returned an error: ${err.message}`,
        );
      return;
    }

    console.log("success!")

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

    res.setHeader("Allow", "POST");
    res.status(405).end("Method Not Allowed");
    
  }
};

export default webhookHandler;

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 20 (1 by maintainers)

Most upvoted comments

Thanks to the Stripe team, who pointed me to some code, this worked for me:

async function buffer(readable) {
  const chunks = [];
  for await (const chunk of readable) {
    chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
  }
  return Buffer.concat(chunks);
}


const webhookHandler = async (req, res) => {
  if (req.method === 'POST') {
    const buf = await buffer(req);
   .....

i have tried all of your suggestions above, but still nothings works. i’m really disappointed by this.

Thanks all this helped me, want to share with everybody many mistakes I made while testing this for first time :

1- disable body-parser

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

2- after using micro.buffer, pay attention to rawBody.toString() !! const rawBody = await buffer(req);. and event = stripe.webhooks.constructEvent( ***rawBody.toString()***, .signtaure..., ..secret. ) 3- use correct webhook signing secret ( secret generated by CLI vs secret generated by Dashboard) in my case I was using this command

$ stripe listen --forward-to http://localhost:3000/api1/stripe/hooks > Ready! You are using Stripe API Version [2020-03-02].
 Your -->>> webhook signing secret is whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (^C to quit)

but another mistake I made, that I was using webhook test secret generated by dashboard not the one generated by CLI 😦

Here is my code after getting correct secret


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

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

export default async function handler(req, res) {
  if (req.method === 'POST') {
    let event;

    try {
      // 1. Retrieve the event by verifying the signature using the raw body and secret
      const rawBody = await buffer(req);
      const signature = req.headers['stripe-signature'];

      event = stripe.webhooks.constructEvent(
        rawBody.toString(),
        signature,
        process.env.STRIPE_WEBHOOK_SECRET
      );
    } catch (err) {
      console.log(`❌ Error message: ${err.message}`);
      res.status(400).send(`Webhook Error: ${err.message}`);
      return;
    }

    // Successfully constructed event
    console.log('✅ Success:', event.id);

    // 2. Handle event type (add business logic here)
    if (event.type === 'checkout.session.completed') {
      console.log(`💰  Payment received!`);
    } else {
      console.warn(`🤷‍♀️ Unhandled event type: ${event.type}`);
    }

    // 3. Return a response to acknowledge receipt of the event.
    res.json({ received: true });
  } else {
    res.setHeader('Allow', 'POST');
    res.status(405).end('Method Not Allowed');
  }
}

@wicka-aus Adding to the recommendation above, there’s also https://github.com/stripe/stripe-node/issues/341 which has numerous solutions for this problem that depend on your exact stack/version of frameworks/etc.

Github issues should be limited to bugs with the library itself and the issue right now is in your code and how to access the real raw body JSON. Whenever your code/framework/helpers change/touch the body, it will prevent webhook signature from working since we need the same exact payload we used to generate the signature when we want to verify it. I’m going to close this out but you can ask for help to our support team here or on our Discord server for developers here

@wicka-aus So what does the complete webhook file look like? What imports? Thanks. Edit: Nevermind got it to work was missing export const config = { api: { bodyParser: false, }, }; in your example

Hey everyone!

I am sure, that I have the right solution. You don’t need any packages at all. The Problem was to create a working buffer out of the request.

You have use req.text() and transform that into a Buffer with Buffer.from().

Here is a full example:

import { type NextRequest, NextResponse } from 'next/server';
import Stripe from 'stripe';

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

export async function POST(req: NextRequest) {
  const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || '', {
    apiVersion: '2022-11-15',
  });
  const sig = req.headers.get('stripe-signature') || '';
  const signingSecret = process.env.STRIPE_WEBHOOK_SIG || '';

  // Read the request body as text
  const reqText = await req.text();
  // Convert the text to a buffer
  const reqBuffer = Buffer.from(reqText);

  let event;

  try {
    event = stripe.webhooks.constructEvent(reqBuffer, sig, signingSecret);
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
  } catch (err: any) {
    return new NextResponse(`Webhook Error: ${err.message}`, {
      status: 400,
    });
  }

  // Handle the event just an example!
  switch (event.type) {
    case 'product.created':
      const productCreated = event.data.object;
      // Then define and call a function to handle the event product.created
      break;
    case 'product.deleted':
      const productDeleted = event.data.object;
      // Then define and call a function to handle the event product.deleted
      break;
    case 'product.updated':
      const productUpdated = event.data.object;
      // Then define and call a function to handle the event product.updated
      break;
    // ... handle other event types
    default:
      console.log(`Unhandled event type ${event.type}`);
  }

  return NextResponse.json({ received: true });
}

Hey everyone!

I am sure, that I have the right solution. You don’t need any packages at all. The Problem was to create a working buffer out of the request.

You have use req.text() and transform that into a Buffer with Buffer.from().

Here is a full example:

import { type NextRequest, NextResponse } from 'next/server';
import Stripe from 'stripe';

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

export async function POST(req: NextRequest) {
  const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || '', {
    apiVersion: '2022-11-15',
  });
  const sig = req.headers.get('stripe-signature') || '';
  const signingSecret = process.env.STRIPE_WEBHOOK_SIG || '';

  // Read the request body as text
  const reqText = await req.text();
  // Convert the text to a buffer
  const reqBuffer = Buffer.from(reqText);

  let event;

  try {
    event = stripe.webhooks.constructEvent(reqBuffer, sig, signingSecret);
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
  } catch (err: any) {
    return new NextResponse(`Webhook Error: ${err.message}`, {
      status: 400,
    });
  }

  // Handle the event just an example!
  switch (event.type) {
    case 'product.created':
      const productCreated = event.data.object;
      // Then define and call a function to handle the event product.created
      break;
    case 'product.deleted':
      const productDeleted = event.data.object;
      // Then define and call a function to handle the event product.deleted
      break;
    case 'product.updated':
      const productUpdated = event.data.object;
      // Then define and call a function to handle the event product.updated
      break;
    // ... handle other event types
    default:
      console.log(`Unhandled event type ${event.type}`);
  }

  return NextResponse.json({ received: true });
}

AMAZING

I mistakenly used the webhook id and not the Signing Secret for process.env.STRIPE_WEBHOOK_SECRET.

You will find the secret on the webhook detail page Screenshot 2023-05-22 093719