37

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
Maxim
  • 3,172
  • 9
  • 21
Zat42
  • 1,652
  • 4
  • 19
  • 32

8 Answers8

33

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
  • 236,239
  • 27
  • 275
  • 302
  • 14
    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
  • 4
    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
  • 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
18

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,026
  • 2
  • 22
  • 31
Nitin Kumar
  • 280
  • 4
  • 9
10

Here is what is working for me:

add this line:

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

just before this line:

app.use(bodyParser.json());

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

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
  • 498
  • 4
  • 14
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
  • 265
  • 1
  • 4
  • 13
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
  • 111
  • 6
0

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
  • 674
  • 8
  • 15
0
// 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.

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