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().

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);

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

    const signature = request.headers["stripe-signature"];
    try {
      event = stripeClient.webhooks.constructEvent(
    } 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,

      .then((executionID) => {
          JSON.stringify({ executionID }),
      .catch((err) => {

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.

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":
    case "customer.deleted":
      console.log(`skip ${eventID} event`);

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