NextJS Stripe Webhook Raw Body
Asked Answered
W

5

11

Pulling my hair out trying to pass a raw body for Stripe webhook on NextJS!.

Trying lots of solutions from everywhere and I cant seem to make it work.

Opening it up the devs with superpowers (of which I am still acquiring).

Error from Stripe Test:

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

My NextJS webhook endpoint test:

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

export default async function handler(req, res) {

    console.log("Payment intent")

    const 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}`);
    }



}

Have also tried:

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);
Wallaroo answered 15/11, 2021 at 0:59 Comment(2)
Here's an example straight from the Vercel repo: github.com/vercel/nextjs-subscription-payments/blob/main/pages/… – Movement
refer to my answer: https://mcmap.net/q/65997/-stripe-error-no-signatures-found-matching-the-expected-signature-for-payload – Maxfield
F
7

By default, NextJS parses the the request body based upon the incoming Content-Type in the headers. You would want to disable this [0] and then consume it as a stream using buffer.

The below code works 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) {
        // Get 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

Fairleigh answered 15/11, 2021 at 6:18 Comment(10)
Sorry. I actually had the bodyParser set to false. Was not in the code I presented. And I have attempted to use micro buffer too. Still the same outcome. – Wallaroo
So I create the webhook in the Stripe dashboard. It hits my API point. I click on 'Signing Secret' in the webhook to get the secret 'whsec_...' This is all done under Stripe testing. Not Live. So the req.body is at the endpoint, the stripe signatures in the header and my webhook secret but I just cant get passed stripe.webhooks.constructEvent() – Wallaroo
Update question with new code I am trying. Same result. – Wallaroo
i've tried running the second example you have and it works for me. The next thing you would probably want to check is your secret key and webhook. I'd suggest logging them in your console just before the stripe.webhooks.constructEvent method is run to make sure that they match up. i.e. the secret key should look like sk_test_.... and should be from the same Stripe account as where you're retrieving the webhook secret – Fairleigh
I have recreated my hook and I get req.body as undefined. Everything else is fine. – Wallaroo
I have updated the second webhook code with my logging. My secret key is the one I use throughout my app. Never had issues with it. Can create PaymentIntents with it that trigger this webhook. I have triple checked the webhook key (copy and pasted) And I get Stripe signature in the header Yet still not working. And yet you had no problems? – Wallaroo
How are you sending webhooks events to your webhook server? Are you using the Stripe CLI? If you're using the Stripe CLI, you would want to use the CLI webhook secret instead. – Fairleigh
No am I sending a webhook configured in the dashboard - in test mode. – Wallaroo
i'm all out of ideas unfortunately, maybe someone else may have an answer – Fairleigh
Is there a way to do this without the "micro" package? I'm reluctant to pull in another dependency just for this ... – Collop
A
3

To solve this, I disabled the NextJS body parser by exporting the config object in the same api route file:

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

Then I imported the raw-body package so I can convert the request stream into a raw buffer:

var getRawBody = require('raw-body')

Finally I used the raw-body package via 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 worked for me. Hope it can help you out.

Arlberg answered 27/12, 2022 at 7:31 Comment(0)
S
1

I understand that this question may be old, but for anyone who comes across it in the future, I want to share that I have successfully integrated Stripe into my project using webhooks and the new /app router.

I have documented the entire step-by-step tutorial in the README.md file.

Feel free to check out the repository at the following link: https://github.com/BastidaNicolas/nextauth-prisma-stripe

Sams answered 15/6, 2023 at 20:39 Comment(0)
F
0

I gotchu bro, doing this for an hour and finally got it to work, just use this for your endpoint and it will 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;
Fatback answered 15/9, 2023 at 1:56 Comment(0)
S
0

If you are using NextJS 14 you can also do:

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!);
}
Sarette answered 23/12, 2023 at 16:54 Comment(0)

© 2022 - 2025 β€” McMap. All rights reserved.