Skip to content

Stripe events

customer.subscription.updated: what it means and what to email

Stripe sends customer.subscription.updated whenever a subscription changes. How to read previous_attributes, and which changes deserve an email.

Sent
Whenever a subscription changes: a renewal, a new plan, a coupon, a status change
Stripe emails
Not for the change itself, though renewal reminders and receipts go out on their own
Kicklace
A renewal or plan change, and a card going past due; other changes are ignored

What it means

Something about a subscription changed. It is the busiest of the subscription events, because Stripe sends it for almost everything: a renewal, a switch to another plan, a seat added, a coupon or discount applied, an invoice item added, the status moving from trial to active, and a customer pressing cancel on a plan that will run to the end of its period.

By itself the event does not say which of those it was. The answer is in previous_attributes.

When Stripe sends it

Each time any of those happens, so a single subscription sends it many times over its life, at least once every billing period.

The Event's data.previous_attributes holds only the fields that changed, with their old values, and when an array changed it holds the whole array. Read it first, before the subscription itself.

  • A status change shows status, for example trialing to active or active to past_due.
  • A renewal shows the item's billing period. Since API version 2025-03-31.basil that period lives on each item, in items.data[], and before that on the subscription as current_period_end.
  • A new plan or seat count shows the old items in previous_attributes.items.data[], price and quantity included.
  • A cancellation set for the end of the period shows cancel_at_period_end going from false to true, and the subscription ends later with customer.subscription.deleted.

Stripe does not promise the order events arrive in, and the same one can arrive twice, so keep the ids of the ones you have handled.

Does Stripe email the customer?

Not for the change itself. Stripe has its own emails around a subscription's life: a reminder before a renewal if you turn on Send emails about upcoming renewals, which it sends for subscriptions that charge automatically, a receipt when the renewal is paid, and the failed payment email when it is not. None of them is sent because of this event.

The email to send

Only for the changes the customer would want a line about, and you pick them out of previous_attributes. An upgrade is the one worth writing, because they have just agreed to pay you more and that deserves a thank-you from you. A plan that will end at the period's close is worth a note too, with the date and the way back.

Subject: You're on [new plan] now

Hi {{first_name}},

Your [your app] plan is now [new plan], as you asked. [Say what is different, and when the new price starts.]

Thank you for moving up, and if anything about it isn't what you expected, reply to this email and I will sort it out.

[Your name]

Fields worth reading

  • previous_attributes: what changed, with the old values. Everything below is read against it.
  • status: trialing, active, past_due, canceled, unpaid, paused, incomplete or incomplete_expired.
  • items.data[].price and items.data[].quantity: the plan and the seats now. price.unit_amount and price.recurring.interval say what it costs and how often.
  • items.data[].current_period_end: when the current period ends.
  • cancel_at_period_end and cancel_at: whether it ends at the close of the period, or on a date you set.
  • cancellation_details: why it is ending, when it is.
  • default_payment_method and latest_invoice: the card, and the invoice that last came out of it.
if (event.type === "customer.subscription.updated") {
  const subscription = event.data.object;
  const before = event.data.previous_attributes;
  const nowPastDue = before?.status !== undefined && subscription.status === "past_due";
  const cancelling = before?.cancel_at_period_end === false && subscription.cancel_at_period_end;
}

To see a real payload, run stripe trigger customer.subscription.updated in a sandbox and read the event it sends.

What Kicklace does

With Stripe connected, three things come out of it, and the rest are ignored.

  • A status that just turned past due lands on the person as a failed payment, payment_failed, with the subscription brought up to date. It is the same failure invoice.payment_failed reports, so the two land as one.
  • A renewal, or a change to the items, the seats or the price, lands as a renewal, subscription_renewed, and the subscription on the account is brought up to date. A plan or seat count that went up also moves the account to Expanding, and the account's row says what went up.
  • Every other change is ignored, a cancellation set for the end of the period included. Kicklace hears that one when customer.subscription.deleted arrives, at the end.

A side road can start from subscription_renewed once one has arrived, and the Expanding stage can carry a sequence of its own for an upgrade. Nothing sends until you switch it on, and Kicklace shows who would have got it over the last 30 days first.

Write yours from your URL. Paste your site's address into the welcome email generator and it writes your first emails in your site's voice, ready to copy, with no sign-up. The upgrade note is one you add on the map, where Write for me drafts it in your voice.

Sources

Checked against Stripe’s docs on .

Free up to 1,000 contacts.

Join the journey

We're building Kicklace so you can get back to building your own product. We'll email you when there's something new worth showing.