96

I have a stripe webhook that call a Firebase function. In this function I need to verify that this request comes from Stripe servers. Here is the code :

const functions = require('firebase-functions');
const bodyParser = require('body-parser');
const stripe = require("stripe")("sk_test_****");
const endpointSecret = 'whsec_****';
const app = require('express')();

app.use(bodyParser.json({
    verify: function (req, res, buf) {
        var url = req.originalUrl;
        if (url.startsWith('/webhook')) {
            req.rawBody = buf.toString()
        }
    }
}));

app.post('/webhook/example', (req, res) => {
    let sig = req.headers["stripe-signature"];

    try {
        console.log(req.bodyRaw)
        let event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
        console.log(event);
        res.status(200).end()

        // Do something with event
    }
    catch (err) {
        console.log(err);
        res.status(400).end()
    }
});

exports.app = functions.https.onRequest(app);

As mentioned in Stripe Documentation, I have to use raw body to perform this security check.

I have tried with my current code and with :

app.use(require('body-parser').raw({type: '*/*'}));

But I always get this error :

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
Zat42
  • 2,471
  • 4
  • 22
  • 36

20 Answers20

75

Cloud Functions automatically parses body content of known types. If you're getting JSON, then it's already parsed and available to you in req.body. You shouldn't need to add other body parsing middleware.

If you need to process the raw data, you should use req.rawBody, but I don't think you'll need to do that here.

Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
  • 27
    I just had to use `req.rawBody` in `constructEvent()`. Stupid mistake, thank you. – Zat42 Dec 23 '18 at 13:22
  • 1
    Facing the same issue in Java, any suggestions, please – Anil kumar Jul 01 '20 at 18:17
  • 13
    To get `req.rawBody` defined, I had to do `app.use(express.json({verify: (req,res,buf) => { req.rawBody = buf }}));` instead of only `app.use(express.json());`. Alternatively I think I could have just accessed `req.body` before `app.use(express.json())`, but this way I can access the raw body anywhere. – sammy Jul 20 '20 at 00:32
  • 1
    sorry, req.rawBody was good. I mistakenly set down arrow. now my vote is locked I can't switch to up arrow. – pref Dec 20 '20 at 19:39
  • @pref That's odd. You should be able to change your vote at any time. – Doug Stevenson Dec 21 '20 at 21:58
  • when I click on Up arrow it says: "You last voted on this answer Dec 20 ... Your vote is now locked in unless this answer is edited" !! It seems there is a time window after initial vote to update it and it can't change after unless the original text is updated. – pref Dec 23 '20 at 21:07
  • @sammy I had to do this also for my app to work – KingJoeffrey Jun 01 '22 at 00:50
  • `req.rawBody` worked for me – Timothy C. Apr 27 '23 at 11:54
40

Here is what is working for me:

add this line:

app.use('/api/subs/stripe-webhook', bodyParser.raw({type: "*/*"}))

(The first argument specifies which route we should use the raw body parser on. See the app.use() reference doc.)

just before this line:

app.use(bodyParser.json());

(it doesn't affect all your operation, just this: '/api/subs/stripe-webhook')

Note: If you are using Express 4.16+ you can replace bodyParser by express:

app.use('/api/subs/stripe-webhook', express.raw({type: "*/*"}));
app.use(express.json());

Then:

const endpointSecret = 'whsec_........'

const stripeWebhook = async (req, res) => {
    const sig = req.headers['stripe-signature'];

    let eventSecure = {}
    try {
        eventSecure = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
        //console.log('eventSecure :', eventSecure);
    }
    catch (err) {
        console.log('err.message :', err.message);
        res.status(400).send(`Webhook Secure Error: ${err.message}`)
        return
    }
    res.status(200).send({ received: true });
}
MatiasG
  • 1,140
  • 9
  • 20
  • 3
    This is great! I didn't know you could specify an exact path in `app.use()`. Much more elegant than the other answers that use `verify` to check for a special situation (and convert the raw body themselves rather than rely on Express.js's built-in feature for handling raw stuff). – Kayce Basques Aug 16 '21 at 00:29
35

2 things to note:

  1. Make sure you're using the correct webhook secret. It's unique per webhook url!

enter image description here

  1. if you're using stripe CLI, change the stripe secret to cli

enter image description here


If using Firebase cloud functions:

pass req.rawBody instead of req.body to constructEvent

const event = stripe.webhooks.constructEvent(
        req.rawBody,
        sig,
        STRIPE_WEBHOOK_SECRET
      );

If using Next.js Vercel function:

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

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const sig = req.headers["stripe-signature"]!;

  const body = await buffer(req);
  const event = stripeObj.webhooks.constructEvent(body, sig, key);

  console.log("webhook verified");
  switch (event.type) {
    case "checkout.session.completed":

    default:
      console.error(`Unhandled event type ${event.type}`);
  }

  return res.status(200).json({});
}

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

check for other servers: https://github.com/stripe/stripe-node/tree/master/examples/webhook-signing

GorvGoyl
  • 42,508
  • 29
  • 229
  • 225
24

Here is code which is working for me:

app.use(bodyParser.json({
  verify: function (req, res, buf) {
    var url = req.originalUrl;
    if (url.startsWith('/stripe')) {
       req.rawBody = buf.toString();
    }
  }
}));

And then pass the req.rawBody for verification

stripe.checkWebHook(req.rawBody, signature);

Reference: https://github.com/stripe/stripe-node/issues/341

Pedro
  • 3,511
  • 2
  • 26
  • 31
Nitin Kumar
  • 348
  • 5
  • 9
17

2021 - Solution

I faced that error, and after a lot research I could not figure out the problem easily, but finally I could do it based in my architecture below:

//App.js

this.server.use((req, res, next) => {
  if (req.originalUrl.startsWith('/webhook')) {
    next();
  } else {
    express.json()(req, res, next);
  }
});
//routes.js

routes.post(
  '/webhook-payment-intent-update',
  bodyParser.raw({ type: 'application/json' }),

  //your stripe logic (Im using a controller, but wherever)
  (req, res) => {
    stripe.webhooks.constructEvent(...)
  }
)

Two big warnings to pay attention:

  • Make sure to send the req.headers['stripe-signature']
  • Make sure that your endpointSecret is right, if not it will still saying the same error

Tips:

  • Test it locally by installing the Stripe CLI: https://stripe.com/docs/webhooks/test

  • Verify your key on stripe dashboard or you can also make sure if you have the right key by verifying you stripe log as below:

webhook secret key example

I hope it helps you. :)

Fábio BC Souza
  • 1,170
  • 19
  • 22
3
// Use JSON parser for all non-webhook routes
app.use(
  bodyParser.json({
    verify: (req, res, buf) => {
      const url = req.originalUrl;
      if (url.startsWith('/api/stripe/webhook')) {
        req.rawBody = buf.toString();
      }
    }
  })
);

The above code will look fine for the above answers. But even I was made one mistake. After put the same thing I got the same error.

Finally, I've figured it out if you're configured body-parser below the rawBody code then it'll work.

Like this

// Use JSON parser for all non-webhook routes
app.use(
  bodyParser.json({
    verify: (req, res, buf) => {
      const url = req.originalUrl;
      if (url.startsWith('/api/stripe/webhook')) {
        req.rawBody = buf.toString();
      }
    }
  })
);
// Setup express response and body parser configurations
app.use(express.json());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

Hopefully, it'll help someone.

Mohamed Jakkariya
  • 1,257
  • 1
  • 13
  • 16
  • 2
    After spending whole day trying several solutions this one finally worked for me as of today - 6th July 2021. There is small change though , body-parser is deprecated, `express.json({...})` and `express.raw({...})` can be directly used. – mukund Jul 06 '21 at 18:25
  • That's correct We can use it directly instead of body-parser. Thanks! – Mohamed Jakkariya Jul 07 '21 at 14:21
3

It is late but will help others

Github answer

    const payload = req.body
    const sig = req.headers['stripe-signature']
    const payloadString = JSON.stringify(payload, null, 2);
    const secret = 'webhook_secret';
    const header = stripe.webhooks.generateTestHeaderString({
            payload: payloadString,
            secret,
    });
    
     let event;
     try {
          event = stripe.webhooks.constructEvent(payloadString, header, secret);
    
     } catch (err) {
            console.log(`Webhook Error: ${err.message}`)
            return res.status(400).send(`Webhook Error: ${err.message}`);
     }

       switch (event.type) {
           case 'checkout.session.completed': {
       ......
    enter code here
GorvGoyl
  • 42,508
  • 29
  • 229
  • 225
Muhammad Shahzad
  • 9,340
  • 21
  • 86
  • 130
2

If you are trying to add a stripe webhook into your NextJS API Route, here's how to do so (ref):

import { buffer } from "micro";
import { NextApiRequest, NextApiResponse } from "next";

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

const handler = async (req: NextApiRequest, res: NextApiResponse) => {
  const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
  const signature = req.headers["stripe-signature"];
  const signingSecret = process.env.STRIPE_WEBHOOK_SECRET || '';
  const reqBuffer = await buffer(req);

  let event;

  try {
    event = stripe.webhooks.constructEvent(reqBuffer, signature, signingSecret);
  } catch (error: any) {
    console.log(error);
    return res.status(400).send(`Webhook error: ${error?.message}`);
  }

  console.log({ event });

  res.send({ received: true });
};

export default handler;

This is using buffer from the micro library, in combination with the modifying the default API request to use request's rawbody. In some frameworks (like NextJs), rawBody doesn't come OOTB, hence the workaround of retrieving the rawbody by reqBuffer, which is needed in the stripe.webhooks.constructEvent event.

Menelaos Kotsollaris
  • 5,776
  • 9
  • 54
  • 68
1

I was able to obtain data from one webhook but not from a second one: the problem was that the secret key I used was the same as the one used for the first webhook, but I found out that every webhook has a different key, that's way I got that same message.

marcolav
  • 405
  • 1
  • 6
  • 17
1

AWS API Gateway + Lambda (Express.js CRUD) I'm using this for Stripe webhook endpoint and it works for me:

app.use(require('body-parser').text({ type: "*/*" }));
1

I spent countless hours checking the following:

  1. Correct webhook endpoint secret. Note: It starts with whsec_.....

You can find it here by clicking on 'Reveal' under Signing secret of your webhook. enter image description here

  1. Make sure your framework doesn't manipulate the raw body.

You can find code examples for your framework here. In my case, I was using Express. I made sure that I'm correctly parsing the raw body in the /webhook controller.

app.post('/webhook', express.raw({type: 'application/json'}), (request, response) => {}

But, it didn't work!!.

I was getting the same error again and again.

This was due to the fact that I used app.use(express.json()) previously in the server.js file. This converted any raw body to json way before express.raw({type: 'application/json'}) was called in the controller.

Here's the fix:

app.use((req, res, next) => {
    if (req.originalUrl === "/api/payment/webhook") {
        next(); // Do nothing with the body because I need it in a raw state.
    } else {
        express.json()(req, res, next); // ONLY do express.json() if the received request is NOT a WebHook from Stripe.
    }
});

Still, it didn't work for me!!

The reason was, I used https://api.myCompany.com/api/payment/webhook/ in the webhook url on Stipe dashboard while disabling express.json on req.originalUrl === "/api/payment/webhook". Note that leading slash in webhook url.

In short, you need to make sure, you've entered the correct webhook endpoint in Stripe dashboard.

This is very basic, but it gets ignored. I updated the webhook endpoint as https://api.myCompany.com/api/payment/webhook and it was resolved.

A leading slash can do a lot!

Amit
  • 1,018
  • 1
  • 8
  • 23
0

This happened to me when sending a test webhook from the Stripe dashboard after I had renamed a firebase cloud function. All my other functions were working fine. Solved by re-setting in the terminal firebase functions:config:set stripe.webhook_signature="Your webhook signing secret" (if you're using that) and redeploying the functions firebase deploy --only functions

On a second occasion I solved the problem by rolling the stripe signature in the stripe dashboard.

Mark
  • 370
  • 5
  • 10
0

Please use this script

app.use(
  bodyParser.json({
    verify: (req, res, buf) => {
      req.rawBody = buf;
    },
  })
);
  • How is this better than https://stackoverflow.com/a/54956700/6541288 or https://stackoverflow.com/a/53899407/6541288 ? – λuser Mar 14 '21 at 11:28
0

My fave was combining two of above great answers.

Then you can use req.rawbody when you construct the event.

Replace "webhook" with whatever route you wish you have a raw body for.

app.use(
  "/webhook",
  express.json({
    verify: (req, res, buf) => {
      req.rawBody = buf.toString();
    },
  })
);

BEFORE

app.use(express.json());

Works well if you are using routes and controllers.

Oscar Ekstrand
  • 581
  • 1
  • 4
  • 13
0

To use raw body in express with a specific endpoint in a seperated middleware, my solution is just enabling router to use express.raw for the webhook endpoint. -node.js v12 -express.js v4.17.1

export const handleBodyRequestParsing = (router: Router): void => {
  router.use('/your_webhook_endpoint', express.raw({ type: '*/*' }))
  router.use(express.json({ limit: '100mb' }))
  router.use(express.urlencoded({ extended: true }))
}
Barbaros
  • 11
  • 2
0

Here is the Quick Tip which may save your hours !

If you are adding express payment to your exciting express app sometimes you may already pass your request as json in the beginning of application by using express middleware app.use(json()); or any other middleware (Bodyparser for example).

If you are doing that then change that to omit your webhook url

Exmaple: Assume your payment webhook url is /paments/webhhok

app.use((req, res, next) => {
  if (req.originalUrl.includes("/payments/webhook")) {
    next();
  } else {
    express.json()(req, res, next);
  }
});
0

When using Stripe in Express, if you have the following line in your code;
app.use(express.json()); it is going to prevent you from providing the raw body to the Stripe even when you explicitly set "bodyParser.raw", which will throw an error. This was the reason my code failed. Finally sorted it out.

johnnn
  • 71
  • 1
  • 4
0

I tried all the solutions above and no one worked, and figured out that the only solution was not to use express at all for this endpoint. you just have to create another http function

export const webhook = functions.https.onRequest(async (req, res) => {
  try {
    const sig = req.headers['stripe-signature']
    const endpointSecret = 'web_secret'

    const event = stripe.webhooks.constructEvent(
      req.rawBody,
      sig,
      endpointSecret
    )
    console.log(event.data.object)

    res.status(200).send(event.data.object)
  } catch (err) {
    console.error('ocorreu um erro', err)

    res.status(400).send(`Webhook Error: ${err.message}`)
  }
})
izaac mendes
  • 434
  • 1
  • 4
  • 9
0

This worked for me

this.app.use(
  express.json({
    verify: function (req, res, buf) {
      //@ts-ignore
      var url = req.originalUrl;
      if (url.includes("/webhook")) {
        //@ts-ignore
        req.rawBody = buf.toString();
      }
    },
  })
);
this.app.use(express.json());

Then implement this https://stripe.com/docs/webhooks/signatures#verify-official-libraries

Joshua Oluikpe
  • 513
  • 6
  • 9
0

I saw a better solution by just not reinventing the wheel and the reason why is a properly called rawBody where you can see the dev doc in the code that is "Whether to register the raw request body on the request. Use req.rawBody." so by just setting this to true like:

const app = await NestFactory.create(AppModule, {
    ....
    rawBody: true,
    ...
  });

so that later in any controller you will see the rawBody property included in the request. So in the code you can pass this to the stripe api like:

const rawBody = req.rawBody; // you will have access to the raw body to send thie to stripe
this.stripe.webhooks.constructEvent(
        rawBody,
        ...here the signature...,
        ...here the secret...,
      );

I hope this save hours or days for some devs :)

Ismael Terreno
  • 1,021
  • 8
  • 5