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