Stripe webhook error: No signatures found matching the expected signature for payload
Asked Answered
S

7

10

I am using the code provide by Stripe to test a webhook. The Stripe secret and the endpoint secret have been triple checked.

Stripe version: 6.19 Body-Parser: 1.19

When I test webhook on the Stripe dashboard I get the result: (Test webhook error: 400) No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe?

Any help would be appreciated.

var bodyParser - require('body-parser');


// Using Express
const app = require('express')();

app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());


// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
const stripe = require('stripe')('sk_test_VPw...');

// Find your endpoint's secret in your Dashboard's webhook settings
const endpointSecret = 'whsec_...';


// Use body-parser to retrieve the raw body as a buffer
const bodyParser = require('body-parser');

// Match the raw body to content type application/json
app.post('/webhook', bodyParser.raw({type: 'application/json'}), (request, response) => {
  const sig = request.headers['stripe-signature'];

  let event;

  try {
    event = stripe.webhooks.constructEvent(request.body, sig, endpointSecret); //NOT WORKING!
  } catch (err) {
    return response.status(400).send(`Webhook Error: ${err.message}`);
  }

  // Handle the checkout.session.completed event
  if (event.type === 'checkout.session.completed') {
    const session = event.data.object;

    // Fulfill the purchase...
    handleCheckoutSession(session);
  }

  // Return a response to acknowledge receipt of the event
  response.json({received: true});
});
Stretto answered 29/6, 2019 at 8:38 Comment(1)
Possible duplicate of Stripe Error: No signatures found matching the expected signature for payloadAutolysin
D
34

Usually this is due to something on your side parsing or modifying the raw request string before the signature is checked(so the signature is computed against a modified string, not the exact one Stripe sent). In this case it looks like the JSON express middleware is doing that: app.use(express.json());.

Stripe has an example of using a raw bodyParser middleware on the webhook endpoint instead so that your code gets the raw string that's required :

// Use JSON parser for all non-webhook routes
app.use((req, res, next) => {
  if (req.originalUrl === '/webhook') {
    next();
  } else {
    express.json()(req, res, next);
  }
});

// Stripe requires the raw body to construct the event
app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
  const sig = req.headers['stripe-signature'];

  let event;

  try {
    event = stripe.webhooks.constructEvent(req.body, sig, webhookSecret);
  } catch (err) {
    // On error, log and return the error message
    console.log(`❌ Error message: ${err.message}`);
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

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

  // Return a response to acknowledge receipt of the event
  res.json({received: true});
});
Dominy answered 1/7, 2019 at 11:12 Comment(2)
Thank you! This was it for me. I was using the same express app for the webhook, but that app was using the json middleware described above.Stumpage
In my case the focus was in using a correct middleware like : // Use JSON parser for all non-webhook routes app.use((req, res, next) => { if (req.originalUrl === '/webhook') { next(); } else { express.json()(req, res, next); } });. Thanks for the tip!Brenza
V
13

One liner plus no deprecated bodyParser. Make sure to define your endpoint's parser before the generic one, aka express.json().

app.use('/stripe/webhook', express.raw({type: "*/*"}))
app.use(express.json())
Vanesavanessa answered 14/5, 2021 at 8:50 Comment(4)
After struggling it with for 2 days, you saved me. Thank you.Gaslit
Thank you, this was exactly what I needed. (and doesn't use the deprecated bodyParser)Rahm
This fixed my issues still as of April 20, 2022. I instead used app.use(express.raw({type: "/})). because im using controllers, routes and express router. But it still works. THank YOU!Kym
In my case I had to do it like this, app.use("/stripe/webhook", express.raw({ type: "*/*" })); app.use(bodyParser.json());Myranda
M
13

In addition to everything, check whsec_

enter image description here

Merriment answered 8/5, 2022 at 23:6 Comment(5)
I didn't realize the signing secret was unique per endpoint until this prompted me to check it. Thx so much!Ribosome
this is the cause for my case, my customer config the .env on the on production and they used the webhook id instead of the signing secretJuicy
this worked for me. The stripe docs are confusing because it seems like you only need the one whsec_ code for all your webhooks, but the whsec_ that appears in the code examples is for your local dev only.Polyphonic
This saved the day, literally!Tedi
This shitty page is not visible in Stripe's developer dashboard. You can see this undocumented key if you click "Add endpoint" on dashboard.stripe.com/test/webhooks in the code on the right pane. What a nightmare, no other tutorial mention this.Tenderfoot
S
2

How to get both parsed body and raw body in Express:

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

Thanks to: https://flaviocopes.com/express-get-raw-body/

Schnapps answered 15/3, 2021 at 18:27 Comment(0)
R
2

For those working with NextJS. Here is a solution I bumped on Reddit by one @ u/SiMFiCysed https://www.reddit.com/user/SiMFiCysed/

Rudolf answered 22/3, 2021 at 3:6 Comment(0)
C
1

For anyone using Next.js you should check stripe documentation for your local webhook secret and this is my code for anyone that will need it or you could use the example from stripe github nextjs example

// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next'
import NextCors from 'nextjs-cors';
import { config as localConfig } from '../../config'
import Stripe from 'stripe';

type Data = {
  name?: string,
  error?: string,
  message?: string,
  status?: string,
}

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

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse<Data>
) {

  await NextCors(req, res, {
    // Options
    methods: ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE'],
    origin: '*',
  });

  const stripeInstance = new Stripe(localConfig.stripeSecretKey, { apiVersion: '2022-11-15' });
  const endpointSecret = localConfig.stripeEndpointSecret

  if (req.method === 'POST') {

    // let event: Stripe.Event;
    let event = req.body
    // console.log(event)

    // const clientReferenceId = req.body.data.object.client_reference_id
    // console.log("clientReferenceId: ", clientReferenceId)



    // Only verify the event if you have an endpoint secret defined.
    // Otherwise use the basic event deserialized with JSON.parse
    if (endpointSecret) {
      // Get the signature sent by Stripe
      const signature = req.headers['stripe-signature'] as string;
      // console.log("signature: ", signature)
      try {

        const body = await buffer(req);

        event = stripeInstance.webhooks.constructEvent(
          body,
          signature,
          endpointSecret
        );
      } catch (err: any) {
        console.log(`⚠️  Webhook signature verification failed.`, err.message);
        // res.status(400).json({ error: err.message })
      }
    }

    // Handle the event
    switch (event.type) {
      case 'payment_intent.succeeded':
        const paymentIntent = event.data.object;
        console.log(`PaymentIntent for ${paymentIntent.amount} was successful!`);
        // Then define and call a method to handle the successful payment intent.
        // handlePaymentIntentSucceeded(paymentIntent);
        break;
      case 'payment_method.attached':
        const paymentMethod = event.data.object;
        // Then define and call a method to handle the successful attachment of a PaymentMethod.
        // handlePaymentMethodAttached(paymentMethod);
        break;
      case 'checkout.session.async_payment_failed':
        const checkoutSessionAsyncPaymentFailed = event.data.object;
        // Then define and call a function to handle the event checkout.session.async_payment_failed
        break;
      case 'checkout.session.async_payment_succeeded':
        const checkoutSessionAsyncPaymentSucceeded = event.data.object;
        // Then define and call a function to handle the event checkout.session.async_payment_succeeded
        break;
      case 'checkout.session.completed':
        const checkoutSessionCompleted = event.data.object;
        // Then define and call a function to handle the event checkout.session.completed
        break;
      case 'checkout.session.expired':
        const checkoutSessionExpired = event.data.object;
        // Then define and call a function to handle the event checkout.session.expired
        break;
      default:
        // Unexpected event type
        console.log(`Unhandled event type ${event.type}.`);
    }


    res.status(200).json({ status: "success", message: "Webhook received" })
  } else {
    res.status(405).json({ status: "error", message: "Method not allowd" })
  }
}

const buffer = (req: NextApiRequest) => {
  return new Promise<Buffer>((resolve, reject) => {
    const chunks: Buffer[] = [];

    req.on('data', (chunk: Buffer) => {
      chunks.push(chunk);
    });

    req.on('end', () => {
      resolve(Buffer.concat(chunks));
    });

    req.on('error', reject);
  });
};
Cither answered 24/3, 2023 at 19:8 Comment(0)
S
0

One other thing that could be going wrong (which was giving me the error) is that the production webhook key is being used with a test request, or vice versa.

Strobila answered 15/8, 2022 at 12:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.