When adding payment capabilities to a web application, users expect a fast, reliable, and simple bank confirmation process. However, there is more to payment processing than bank confirmation.

More complex payment processing is normally pushed to the background, such as:

  • Payment failures (No Balance, Card expired, Bank restrictions)
  • Notifications (Upcoming charges, Custom Invoice emails)
  • Customer disputes (Putting holds on charges)

Stripe’s Webhooks

Stripe uses webhooks to provide server-to-server communications between its processes and your back-end systems. This enables you to keep your data up to date asynchronously.

However, its webhooks (subscription.* and other events) come with multiple challenges:

Defer’s implementation of Stripe’s webhooks

Defer offers a solution to the above challenges. With Stripe as its model, Defer provides easier webhook management with the Defer Console and Function Metadata.

The code

First, you need to set up a Stripe client in your API. Then, build a Stripe event from the received webhooks (with stripeClient.webhooks.constructEvent()) and forward it for background processing by calling handleStripeWebhookFn().

src/app.js
import Stripe from "stripe";
import express from "express";
import { assignOptions, delay } from "@defer/client";

import handleStripeWebhookFn from "./defer/handleStripeWebhook.js";

const app = express();
const stripe = new Stripe(process.env.STRIPE_PRIVATE_API_KEY);

app.post(
  "/webhook",
  express.raw({ type: "application/json" }),
  (request, response) => {
    let event = request.body;

    const signature = request.headers["stripe-signature"];
    try {
      event = stripeClient.webhooks.constructEvent(
        request.body,
        signature,
        process.env.STRIPE_WEBHOOK_ENDPOINT_SECRET,
      );
    } catch (err) {
      console.error("cannot verify stripe webhook signature:", err.message);
      return response.sendStatus(400);
    }

    const handleStripeWebhook = assignOptions(handleStripeWebhookFn, {
      // process webhooks in the right order
      delay: event.created + 60 * 10,
      // add metadata for the the Defer Console
      metadata: {
        livemode: event.livemode,
        type: event.type,
        apiVersion: event.api_version,
      },
    });

    handleStripeWebhook(event.id)
      .then((executionID) => {
        response.sendStatus(
          200,
          "application/json",
          JSON.stringify({ executionID }),
        );
      })
      .catch((err) => {
        response.sendStatus(400);
      });
  },
);

export default app;

The handleStripeEvent() Defer function retrieves the Stripe event using the Stripe client and performs the associated action, for example, updating a customer record.

src/defer/handleStripeWebhook.js
import { defer } from "@defer/client";
import Stripe from "stripe";

async function handleStripeEvent(eventID) {
  const stripe = new Stripe(process.env.STRIPE_PRIVATE_API_KEY);
  const event = await stripe.events.retrieve(eventID);
  switch (event.type) {
    case "customer.created":
      break;
    case "customer.deleted":
      break;
    default:
      console.log(`skip ${eventID} event`);
  }
}

export default defer(handleStripeEvent, {
  // retry the Stripe event processing
  //   with an exponential back-off retry strategy
  retry: 7,
});