Customer Webhook

You can configure a webhook on Stripe to receive events when customers are created, updated or deleted. You can add a webhook from the Stripe Dashboard here

This webhook is optional and is only used to support shared subscriptions and transferring subscriptions. If you don't use thoses features you don't have to implement this webhook.

This webhook will have to be listening for the follwing events:

customer.created

customer.deleted

customer.updated

updatedCustomerWebhook.ts

This code example updates the user's subscription in firestore

It will check if the user is part of a shared subscription and add it to the user document in the database or delete it if the user was deleted or transfer it if the user updated their email address.

import { firestore, admin } from "../utils/firebase"
import stripe, { Stripe } from "../utils/stripe"
import * as express from "express"

const router = express.Router()

router.post("/", (req: any, res) => {
    let event
    try {
        // check stripe event signature
        event = stripe.webhooks.constructEvent(
            req.rawBody,
            req.headers["stripe-signature"],
            process.env.YOUR_WEBHOOK_SIGNING_SECRET // visible on the Stripe Dashboard (https://dashboard.stripe.com/webhooks)
        )
    } catch (error) {
        reportError(error, { request: req, details: "stripe-signature check error" }, req)
        return res.status(400).send({ error })
    }

    if (!event || !event.type || !event.data?.object) return res.status(400).send({ event, error: "Missing data" })

    const { previous_attributes } = event.data

    const { metadata = {}, email, id: stripeCustomerId } = event.data.object as Stripe.Customer

    const previousEmail = previous_attributes?.email
    const emailWasUpdated = previousEmail && previousEmail !== email

    const { billing_shared_subscription_id: sharedSubscriptionId } = metadata
    const userDeleted = event.type === "customer.deleted"

    return Promise.resolve()
        .then((): any => {
            // if user has a shared subscription, update the subscription
            // this webhook is triggered for each user of a shared subscription
            // when the status of the shared subscription has changed
            if (sharedSubscriptionId) {
                return stripe.subscriptions
                    .retrieve(sharedSubscriptionId)
                    .then((stripeSubscription: Stripe.Subscription) => {
                        return firestore
                            .collection("users")
                            .where("email", "==", email.toLowerCase())
                            .get()
                            .then((querySnapshot) => {
                                if (querySnapshot.empty) {
                                    // user is not in the database yet
                                    // aka the user was assigned a shared subscription license before he created an account
                                    // on the app
                                    return
                                }

                                // get first matching user
                                const matchingUser = querySnapshot.docs[0]

                                const userId = matchingUser.id

                                const subscription = userDeleted
                                    ? admin.firestore.FieldValue.delete() // delete subscription if customer is deleted
                                    : // add shared subscription to new user
                                      {
                                          stripeCustomerId,
                                          id: sharedSubscriptionId,
                                          status: stripeSubscription.status,
                                          plan: stripeSubscription.items?.data[0]?.price?.id ?? null,
                                          trialEnd: stripeSubscription.trial_end,
                                          periodEnd: stripeSubscription.current_period_end,
                                          cancelAtPeriodEnd: stripeSubscription.cancel_at_period_end
                                      }

                                return firestore.collection("users").doc(userId).set(
                                    {
                                        subscription
                                    },
                                    { merge: true }
                                )
                            })
                            .then(() => {
                                // if the shared subscription customer updated his email
                                // delete old subscription
                                if (emailWasUpdated) {
                                    firestore
                                        .collection("users")
                                        .where("email", "==", previousEmail.toLowerCase())
                                        .get()
                                        .then((querySnapshot): any => {
                                            if (querySnapshot.empty) {
                                                return Promise.resolve() // user not found, nothing to delete
                                            }
                                            const matchingUser = querySnapshot.docs[0]
                                            const userId = matchingUser.id
                                            // remove subscription from previous user
                                            return firestore.collection("users").doc(userId).set(
                                                {
                                                    subscription: admin.firestore.FieldValue.delete()
                                                },
                                                { merge: true }
                                            )
                                        })
                                }
                            })
                            .then(
                                () =>
                                    `user shared subscription was updated. ${
                                        emailWasUpdated
                                            ? `Subscription was transferred from ${previousEmail} to ${email}`
                                            : ""
                                    } `
                            )
                    })
            }

            if (emailWasUpdated) {
                // user is transferring ownership of his subscriptions
                // the user is not a member of a shared subscription
                return stripe.subscriptions
                    .list({
                        customer: stripeCustomerId,
                        status: "active"
                    })
                    .then((subscriptions) => {
                        if (subscriptions.data.length === 0) {
                            // no active subscriptions found for customer
                            return
                        }
                        const subscription = subscriptions.data[0]

                        return firestore
                            .collection("users")
                            .where("email", "==", email.toLowerCase())
                            .get()
                            .then((querySnapshot) => {
                                if (querySnapshot.empty) {
                                    throw Error(`user not found: ${email}`)
                                }

                                // get first matching user for new email address
                                const matchingUser = querySnapshot.docs[0]

                                const userId = matchingUser.id

                                // add subscription to new user
                                return firestore
                                    .collection("users")
                                    .doc(userId)
                                    .set(
                                        {
                                            subscription: {
                                                stripeCustomerId,
                                                id: subscription.id,
                                                status: subscription.status,
                                                plan: subscription.items?.data[0]?.price?.id ?? null,
                                                trialEnd: subscription.trial_end,
                                                periodEnd: subscription.current_period_end,
                                                cancelAtPeriodEnd: subscription.cancel_at_period_end
                                            }
                                        },
                                        { merge: true }
                                    )
                                    .then(() => {
                                        // delete old subscription from the user who transferred the subscription
                                        firestore
                                            .collection("users")
                                            .where("email", "==", previousEmail.toLowerCase())
                                            .get()
                                            .then((querySnapshot): any => {
                                                if (querySnapshot.empty) {
                                                    return Promise.resolve() // user not found, nothing to delete
                                                }
                                                const matchingUser = querySnapshot.docs[0]
                                                const userId = matchingUser.id
                                                // remove subscription from previous user
                                                return firestore.collection("users").doc(userId).set(
                                                    {
                                                        subscription: admin.firestore.FieldValue.delete()
                                                    },
                                                    { merge: true }
                                                )
                                            })
                                    })
                            })
                            .then(() => `user subscription was transferred from ${previousEmail} to ${email}`)
                    })
            }

            return Promise.resolve("user has no shared subscription and email was not updated. nothing to do.")
        })
        .then((status) => res.status(200).send({ status }))
        .catch((error) => {
            console.error(`>> WEBHOOK ERROR: ${error}`)
            res.status(400).send({ event, error })
        })
})

export default router

Last updated