DEV Community

mattling_dev for Stripe

Posted on • Updated on

Building an eCommerce Store 3/3: Webhook endpoints and fulfillment

Introduction

Creating an online store requires the management of a lot of moving parts, such as product and price management, checkout flows for your customers, and order fulfillment to name a few.

In this article you’ll learn about using a webhook endpoint to listen for important events that happen on your Stripe account, like successful payments, so that you can automatically react to them. This is a common model for fulfilling orders when customers make purchases with your business. You can also watch this video with Sia Karamalegos (@theGreenGreek) and me (@mattling_dev) to learn how to create a webhook endpoint and fulfill orders with Netlify serverless functions and the Stripe-node client library.

Webhook endpoints

A webhook endpoint is a route in your system that can receive POST requests from Stripe. Just as your system makes HTTP requests to the Stripe API, Stripe can make HTTP requests to your webhook endpoint to inform your system of important events. There’s many events that you can listen out for. One of the most important is when payments have been made. Depending on your integration your system might listen for payment intent succeeded events or checkout session completed events.

In this article we’ll listen for successful checkout events which will be generated when a customer finishes a checkout or uses a Payment Link to complete a purchase. Here is an example in Javascript using a Netlify serverless function to receive an event from Stripe.

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

exports.handler = async function (event, context) {
  const { body, headers } = event;

  try {
    // Check that the request is really from Stripe by verifying the signature ...
    const stripeEvent = stripe.webhooks.constructEvent(
      body,
      headers["stripe-signature"],
      process.env.WEBHOOK_SECRET
    );

    // Handle successful payments
    if (stripeEvent.type === "checkout.session.completed") {
      // Extract the checkout object itself from the event
      const checkoutSession = stripeEvent.data.object;

      const items = await stripe.checkout.sessions.listLineItems(
        checkoutSession.id,
        { expand: ["data.price.product"] }
      );

      // Custom Business logic to fulfill an order goes here ...
    }

    // Respond to Stripe that the event was received successfully ...
    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}`,
    };
  }
};
Enter fullscreen mode Exit fullscreen mode

Let’s unpack the main features of this function. The raw body post, a request header containing a calculated signature, and a secret are passed to the construct event method (we’ll discuss the secret and signature later). This returns an event object. The event object contains information about the event such as its type, but importantly it also contains the object that refers to the event. In this case the event refers to a Checkout Session, therefore that is the object in the event. We extract that object in order to use its ID to make a call back to the Stripe API to retrieve the line items for the Checkout Session.

In this example, we are also leveraging a feature of the Stripe API called expansion which composes related objects into one response. This can greatly simplify your code by reducing the number of requests that your system needs to make to the Stripe API. In this example, we’re listing the line items, while also requesting that the price and product objects are expanded in the response, all in one API call. You can think of this as analogous to a join over tables in a database. You can learn more about expansion on our Stripe Developers YouTube channel.

Once we have the line items, we can build code that would fulfill the order for the Product(s) purchased. This will depend on your business and fulfillment needs.

To learn about building and configuring Netlify serverless functions follow the comprehensive documentation here.

Secrets and security

In the code sample above, you’ll see that an environment variable called WEBHOOK_SECRET has been passed as a parameter to the construct event method along with a signature that is provided by Stripe as a request header. This allows you to verify that the body payload was really sent by Stripe and not a bad actor. Only Stripe and you know this secret — therefore only Stripe and your system can reconstruct the signature in order to verify the authenticity of the event. Luckily, the Stripe client libraries take care of this for you in the construct event method. You can find the webhook secret in the dashboard when you create and configure the webhook endpoint.

The Stripe CLI

The Stripe command line interface is an essential tool for building and testing your integration. It allows you to interact with API resources like payment intents and checkout sessions, tail your account logs, and much more.

The first of the many features that it offers for developing a webhook endpoint, is the ability to listen for events that happen on your account — like successful payments — and to forward those events to your local development machine. The second is the ability to trigger many types of events from the command line. This speeds up development time immensely as you don’t need to click through time-consuming checkout flows in order to generate events. The Stripe CLI is available as a standalone installation and as the underpinning of the Stripe VSCode extension.

If you’d like to learn more about the Stripe CLI you can watch this deep dive video by CJ Avilla.

Best practices

Stripe expects your webhook endpoint to respond as quickly as possible with an HTTP code in the range 2XX. This lets Stripe know that you have successfully received the event and that we don’t need to continue attempting to let your system know about it by repeatedly POSTing it to your endpoint.

Duplicate events may also occasionally be delivered to your webhook endpoint. Our recommendation is to guard against duplicate events by making your event processing “idempotent”. One way to achieve this is to log the events that the system has processed and then not process already-logged events.

You can read more about best practices with webhooks here.

Summary

Listening and reacting to important events in your Stripe account is an essential part of your system as you scale your business. If you would like support building and configuring a webhook endpoint please don’t hesitate to reach out to our developers at @StripeDev on twitter or join in the conversion on our Discord server.

About the author

Matthew Ling

Matthew Ling (@mattling_dev) is a Developer Advocate at Stripe. Matt loves to tinker with new technology, adores Ruby and coffee and also moonlighted as a pro music photographer. His photo site is at matthewling.com and developer site is at mattling.dev.

Stay connected

In addition, you can stay up to date with Stripe in a few ways:

📣 Follow us on Twitter
💬 Join the official Discord server
📺 Subscribe to our Youtube channel
📧 Sign up for the Dev Digest

Top comments (0)