Magnus
Magnus

Reputation: 435

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

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

Upvotes: 11

Views: 20144

Answers (7)

Dragos Catalin
Dragos Catalin

Reputation: 103

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

Upvotes: 1

Byron Broughten
Byron Broughten

Reputation: 371

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.

Upvotes: 0

Viktor Bogutskii
Viktor Bogutskii

Reputation: 930

In addition to everything, check whsec_

enter image description here

Upvotes: 15

karllekko
karllekko

Reputation: 7198

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

Upvotes: 35

Bugzilla
Bugzilla

Reputation: 2506

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())

Upvotes: 14

Samson Ssali
Samson Ssali

Reputation: 29

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

Upvotes: 2

Michal J&#225;na
Michal J&#225;na

Reputation: 31

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/

Upvotes: 2

Related Questions