Unlock seamless workflows and faster delivery with our latest releases - Join the deep dive

Handling Stripe webhook errors with Netlify Serverless Functions and SANITY

9 replies
Last updated: Feb 11, 2022
Heyo!
Working with a serverless function and Stripe. So after a successful purchase Stripe hits a Netlify Serverless Function which then in turn goes out and hits SANITY and updates the purchased product in the backend.

I am sometimes seeing timeout errors and

Client network socket disconnected before secure TLS connection was established
Do I need to wrap the
client
in an async/await statement? Or any other measures I can take to better handle this?
Here is what the full code looks like

import Stripe from "stripe"
import { Handler } from "@netlify/functions"
import sanityClient from "@sanity/client"

// Initialize Stripe client
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
  apiVersion: "2020-08-27",
})
// Initialize SANITY client
const client = sanityClient({
  projectId: process.env.GATSBY_SANITY_ID,
  dataset: process.env.GATSBY_SANITY_DATASET,
  token: process.env.SANITY_TOKEN,
  useCdn: false, // `false` if you want to ensure fresh data
  apiVersion: "2022-02-01",
})

const handler: 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.STRIPE_WEBHOOK_SECRET
    )

    // Only do stuff if this is a successful Stripe Checkout purchase
    if (stripeEvent.type === "checkout.session.completed") {
      // Get the session from the webhook and then
      // Got Stripe to get all of the session data
      // We need the full line items from the completed purchase
      const eventObject: any = stripeEvent.data.object
      const sessionId: string = eventObject.id
      const sessionData: any = await stripe.checkout.sessions.retrieve(
        sessionId,
        {
          expand: ["line_items", "line_items.data.price.product"],
        }
      )
      const lineItems: any = sessionData.line_items.data

      // Loop over each line item and update the stock in SANITY if necessary
      lineItems.forEach((item) => {
        const sanityId = item.price.product.metadata.sanityId
        client
          .patch(sanityId) // Document ID to patch
          .dec({ stock: item.quantity }) // Increment field by count
          .commit() // Perform the patch and return a promise
          .catch((err) => {
            console.error("Oh no, the update failed: ", err.message)
          })
      })
    }

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

export { handler }

Feb 11, 2022, 4:20 AM
Just a hunch, but you’re probably getting rate limited. The
lineItems.forEach
will loop super fast and make multiple patch requests to the API and likely hit the limit of 25 requests per second.
I think I’d build a transaction with all the patches instead and send it as one request.
Here’s a pattern you can adopt .
Feb 11, 2022, 4:25 AM
Ah okay that makes sense because it is succeding for one item, but failing with multiplen items!
Feb 11, 2022, 4:27 AM
Thank you!
Feb 11, 2022, 4:28 AM
Yarg! That wasn't it. I followed that code and it is working locally fine - but still getting weird errors in live mode.
Feb 11, 2022, 5:16 AM
Also seeing socket hang up errors
Feb 11, 2022, 5:16 AM
Ignore the Stripe Webhook signature errors, that is from testing locally and a mismatch in the webhook secret.
Feb 11, 2022, 5:18 AM
Any chance it would relate to using the CDN or not?
Feb 11, 2022, 5:22 AM
Got it! In my rewrite I missed an await!
Feb 11, 2022, 5:28 AM
Thank you
user Y
!
Feb 11, 2022, 5:28 AM

Sanity– build remarkable experiences at scale

Sanity is a modern headless CMS that treats content as data to power your digital business. Free to get started, and pay-as-you-go on all plans.

Was this answer helpful?