Netlify functions not executing Stripe webhook events consistently

Site ID: cranky-goldberg-9f2b8c

I have a function that receives Webhooks from stripe. The issue I am having is that whenever stripe sends webhook events, my server less functions will receive them (as shown in function logs) but will not run the function itself everytime. Sometimes it’ll execute the function other times it will not. But Stripe indicates, and the logs shown, that they are hitting my netlify endpoint.

With respect to the logs below, the first 3 events are sent from stripe and are received by my netlify webhook but they don’t do anything. When I manually resend the the same event, only does my server less function execute and the document is successfully updated. How do I fix this?

10:47:59 AM 88cff16d Duration: 4.07 ms Memory Usage: 84 MB Init Duration: 516.41 ms
10:48:29 AM 64b34b7f Duration: 19.04 ms Memory Usage: 85 MB
10:48:30 AM eccf6d0f Duration: 427.83 ms Memory Usage: 87 MB
10:50:19 AM 04adf230 Duration: 394.53 ms Memory Usage: 85 MB Init Duration: 555.85 ms
10:50:55 AM ffd46863 INFO Document successfully updated!
10:50:55 AM ffd46863 Duration: 633.82 ms Memory Usage: 98 MB
10:51:59 AM ffd46863 INFO Document successfully updated!
10:51:59 AM e11f5d66 INFO 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? GitHub - stripe/stripe-node: Node.js library for the Stripe API.
10:51:59 AM e11f5d66 Duration: 71.41 ms Memory Usage: 102 MB

I really need help with this, I have my project setup for netlify and would prefer not to switch my project over to firebase if at all possible.

Hi @OtterlySori

Have you checked the validity of the data Stripe is sending using a tool such as Request Bin or Request Catcher? Have you tried logging out the payload and checking it in the functions log?

The message from your log indicates you are perhaps not passing the information received to the function correctly. Are you reading it correctly, or modifying in somehow?

@coelmay

Yes I have. I have tested numerous times in dev (over a 100 while trying to debug) and while it’s online. When the user makes a purchase I see the events logged in functions log and in I see them in Stripe. However, nothing happens. I have to manually resend the stripe event and it’ll run fine. In dev It worked nearly everytime, but online it’s not working at all today.

Moreover, my code is near identical to the stripe webhook post by netlify: Learn How to Accept Money on Jamstack Sites in 38 Minutes

“The message from your log indicates you are perhaps not passing the information received to the function correctly.” What indicates this?

The line I previously quoted…

Which asks

Oh, I misread I apologize. I am actually not sure why this showed up unless I manually sent too many stripe webhook events. This shouldn’t be the issue though because, albeit for that log you quoted, when I send manually Webhooks after a customer made a purchase it’ll usually always execute successfully and update my Firestore. Then other times it won’t. I haven’t modified the function between these two cases either. But it doesn’t not execute successfully when stripe sends Webhooks after a purchase, only when I manually resend the webhook.

@coelmay

const firebase = require('firebase')
require("firebase/firestore");
const stripe = require('stripe')(process.env.GATSBY_STRIPE_SECRET_KEY);


if (!firebase.apps.length) {
  firebase.initializeApp({
    apiKey: process.env.GATSBY_FIREBASE_API_KEY,
    authDomain: process.env.GATSBY_FIREBASE_AUTH_DOMAIN,
    projectId: process.env.GATSBY_FIREBASE_PROJECT_ID,
    storageBucket: process.env.GATSBY_FIREBASE_STORAGE_BUCKET,
    messagingSenderId: process.env.GATSBY_FIREBASE_MESSAGING_SENDER_ID,
    appId: process.env.GATSBY_FIREBASE_APP_ID
  });
} else {
  firebase.app(); // if already initialized, use that one
}

exports.handler = async ({ body, headers }) => {
  
  try {
    // check the webhook to make sure it’s valid
    const stripeEvent = stripe.webhooks.constructEvent(
      body,
      headers['stripe-signature'],
      process.env.GATSBY_STRIPE_WEBHOOK_SECRET
    );

    // only do stuff if this is a successful Stripe Checkout purchase
    if (stripeEvent.type === 'checkout.session.completed') {
      const eventObject = stripeEvent.data.object;

      // Retrieve session with expanded line items
      const session = await stripe.checkout.sessions.retrieve(eventObject.id, { expand: ['line_items'] });

      // Get user ID
      const uid = session.client_reference_id

      // Get product name
      const productID = session.line_items.data[0].price.product
      const product = await stripe.products.retrieve(productID)

      var docRef = firebase.firestore().collection('study_guide_customers').doc(uid)

      if (product.name === 'Standard') {
        firebase.firestore().collection('study_guide_customers').doc(uid)
          .update({
            stripe_customer_id: session.customer,
            standard_payment_intent: session.payment_intent,
            standard_purchased: true
          })
          .then(() => {
              console.log("Document successfully updated!");
          })
          .catch((error) => {
              // The document probably doesn't exist.
              console.error("Error updating document: ", error);
          });
      }

      if (product.name === 'Premium') {
        firebase.firestore().collection('study_guide_customers').doc(uid)
          .update({
            stripe_customer_id: session.customer,
            premium_payment_intent: session.payment_intent,
            premium_purchased: true
          })
          .then(() => {
              console.log("Document successfully updated!");
          })
          .catch((error) => {
              // The document probably doesn't exist.
              console.error("Error updating document: ", error);
          });
      }
    }

    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}`,
    };
  }
};

So you have confirmed in both cases (Stripe sending, and you manually sending) that the data sent/received is exactly the same?

I have used the same Stripe payment function from the post you linked. I didn’t, however, implement the webhook.

Yes. When I manually resend an event, it resends the event that was sent before.

Stripe is sending 3 webhook events for each step of checkout and my function only does something for the event ‘checkout.session.completed’, would the fact that 3 events are being sent be part of the problem? Though my function only does something for the event stated above so I’m not sure…

If three events are getting sent to the same function, and it is only processing one of them, then that could certainly explain what is happening given the function. You could add a check for those events, as well, and console.log them (and the checkout.session.completed) so you know what is happening.

@coelmay


12:03:48 AM 350cd648 INFO   event recieved: payment_intent.created
12:03:48 AM 350cd648 Duration: 4.14 ms	Memory Usage: 84 MB	Init Duration: 460.76 ms
12:03:48 AM 9985f83f INFO   event recieved: payment_intent.created
12:03:48 AM 9985f83f Duration: 4.16 ms	Memory Usage: 84 MB	Init Duration: 464.89 ms
12:04:05 AM 0e15499d INFO   event recieved: payment_intent.succeeded
12:04:05 AM 0e15499d Duration: 14.54 ms	Memory Usage: 85 MB
12:04:05 AM 627b8275 INFO   event recieved: checkout.session.completed
12:04:05 AM 627b8275 INFO   fetching stripe purchase data...
12:04:06 AM 627b8275 Duration: 444.56 ms	Memory Usage: 87 MB
12:14:04 AM [ERROR] Function logs are currently unavailable. We are working on resolving the issue.

It’s receiving all webhooks. In the final event, where it says fetching data. It should be updating Firestone. Is my function timing out? If ut is shouldn’t there be an indication saying times out?

If the function was exceeding the execution limit, it would say so in the log.

I assume you’ve added extra logging to the function as there is nothing like this in the code previously shared. Have you tested the Firestore connection to make sure it is receiving data and returning a response? Have you tried logging the response from firebase/firestore?

Note: I haven’t used Firebase so cannot give you specific advise on it making your function work with it.

Yes to your question. When I run in local dev using netlify cli, everything runs flawlessly. Only when deployed do issues show. I added logging for every step of the function so I can see which pieces of codes are actually executed. I will follow up when I have new logs to show with the updated function.

One thing you might consider is creating another site with this function and using Stripe’s Test Mode to create a test webhook to send events to it. This way you have control over which events are sent and can log out data as needed (I did this very thing the other day.)

@coelmay

Hey I wanted to follow up. The last payment successfully update and I think I found the issue to the problem. I didn’t await the firebase function call so I think my function was ending before the firebase sent it’s response. Obviously I won’t know until I get more payments but I really hope that was the issue.

Excellent @OtterlySori, thanks for sharing.

1 Like

Glad this is working now, @OtterlySori :netliconfetti: Please let us know if this comes up again!