Webhook, RawBody and Stripe

Hi,

I’m following the tutorial about Stripe and Netlify (https://www.netlify.com/blog/2020/04/13/learn-how-to-accept-money-on-jamstack-sites-in-38-minutes/), all works very well but the last part about webhooks is not so clear. I have some trouble with ‘body’ and Stripe signature. Reading the docs I need to pass the ‘rawBody’ and not the ‘body’. Any ideas?

hey, @Lorenzo!

Thor and I ended up moving the webhooks setup to its own post, which you can read here: https://www.netlify.com/blog/2020/04/22/automate-order-fulfillment-w/stripe-webhooks-netlify-functions/

let me know if that clears up your questions. if not, I’m here to help!

2 Likes

[UPDATE]
Sorry Jason, I’m near to the solution, first of all I miss the step to insert the right Webhook secret key (the local one) in Netlify. I let you know… thanks

Hi Jason,

first of all thanks for all your videos on Gatsby and tutorial like the one I’m following.
I’m not a professional developer so I sure miss something.

I’ve implemented your webhook solution both on firebase functions and netlify with two different Stripe Webhooks (and of course two differente endpoint). With Firebase I can test everything without any errors. This is an extract of the functions, based on your code, deployed on firebase. Here, I can take the request and the rawBody:

    export const handlePurchase = functions.https.onRequest( (req, res) => {
        const sig = req.headers["stripe-signature"] as string;
        let event;
        try {
            event = stripe.webhooks.constructEvent(req.rawBody, sig, endpointSecret);
            if (event.type === 'checkout.session.completed') {
                const eventObject = event.data.object;
    ...

with Netlify I’ve a problem both in local and in live version (I set all my keys in ENV and for local env I use stripe and netlify client for run both server and the webhook listener).

I can’t test the endpoint from Stripe, and in local I get this error, in “ntl dev terminal”:

Request from ::1: POST /.netlify/functions/handle-purchase
Stripe webhook failed with 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
Response with status 400 in 11 ms.

and in the terminal with stripe listen I got:

2020-05-27 19:11:07  <--  [400] POST http://localhost:8888/.netlify/functions/handle-purchase [evt_1GnSmTL1itY7e9WJytWrD7jm]

This is the full code of handle-purchase.js:

const stripe = require('stripe')('process.env.STRIPE_SECRET_KEY');

exports.handler = async ({ body, headers }) => {
  try {
    const stripeEvent = stripe.webhooks.constructEvent(
      body,
      headers['stripe-signature'],
      process.env.STRIPE_WEBHOOK_SECRET
    );

    if (stripeEvent.type === 'checkout.session.completed') {
      const eventObject = stripeEvent.data.object;
      const items = JSON.parse(eventObject.metadata.items);
      const shippingDetails = eventObject.shipping;

      const purchase = { items, shippingDetails };
      console.log(`📦 Fulfill purchase:`, JSON.stringify(purchase, null, 2));
    }

    return {
      statusCode: 200,
      body: JSON.stringify({ received: true }),
    };
  } catch (err) {
    console.log(`Stripe webhook failed with ${err}`);

    return {
      statusCode: 400,
      body: `Webhook Error: ${err.message}`,
    };
  }
};

Maybe I miss something for Netlify. Thanks for your comprehension about my poor English.

Lorenzo

hey there! it looks like the local Stripe webhook secret doesn’t match what’s coming in from the webhook

when you told the Stripe CLI to listen, do you update your env vars in Netlify to use the value it gave back as STRIPE_WEBHOOK_SECRET?

if yes, can you please share the content of body and headers for a test webhook so we can see what’s coming back?

thanks!

Hi @jlengstorf,
There’s something I must be misunderstanding… You’re saying we need to set our STRIPE_WEBHOOK_SECRET variable to the signing secret outputted by stripe listen --forward-to localhost:8888/.netlify/some-path

This doesn’t make sense to me because, if we need to do that to test our integration, it means we need to put our live deployment on hold during development. Do you understand what I mean?

if you’re using netlify dev, you can add a .env file locally that will override env vars on. your local machine — this way you can keep the production env var set to your production webhook secret, but test locally with a different webhook secret!

if you set a .env file up, you should see output in the CLI that it’s been found and that env vars are being overridden

does that help?

That doesn’t work for some reason.
The cli tool is not getting the stripe env variables from my local .env file. It does pick up a variable from my local .env file that is not in my Netlify env variable settings.

◈ Injected build setting env var: STRIPE_WEBHOOK_SECRET
◈ Adding the following env variables from .env: FROM_EMAIL_ADDRESS

Is there something I need to specify in netlify.toml to make the cli pick up local .env variable? Right now my netlify.toml file looks like this:

[build]
  publish = "__sapper__/export"
  command = "cd functions && npm i && cd .. && npm run export"
  functions = "functions"

environment = { NODE_VERSION = "12.16.2", AWS_LAMBDA_JS_RUNTIME = "nodejs12.x" }
[dev]
  autoLaunch = false

If you want to use the same function for test mode and live mode, I’d recommend having two env vars, e.g. STRIPE_WEBHOOK_SECRET_TEST and STRIPE_WEBHOOK_SECRET_LIVE. When setting up your live mode webhook, attach a livemode get param to your webhook url, e.g. https://my.netlify.app/.netlify/functions/webhooks?livemode and then in your function, check whether livemode is defined (event.queryStringParameters.livemode) and based on that decide which env var to use.

2 Likes

That’s a good strategy.
Even if I can’t attach params to the URL of webhooks coming from Stripe, I could have a .env variable that’s only present in live mode.
Or an environment variable equal to “LIVE” in live mode and “STAGING” in local. …you get the idea!