denis bauer· belgrade, serbia·linkedIn·cv·email·Book a call with me
ChurnAlert
Case study

The one Stripe field Intercom never imports

A small Intercom app that copies Stripe’s cancel_at_period_end onto the contact — so support can ask a customer why they are leaving on the day they decide, not a month later when the subscription finally expires.

Stripe · Subscriptions
Dummy TestActiveCancels Mar 6 ⏱
Billing monthly · next invoice Mar 6 for €8.99
ChurnAlert
reads Stripe, writes Intercom
Intercom · User data
NameIsabelle TestEmailisabelle@testik.comCancel at period endFalseStripe customer idcus_RsQnuZYoeS3xFn
Users · cancel_at_period_end is true1 user matches✉ cancellation survey sent
subscription active, nothing to do
0
field that was missing
0
Stripe fields Intercom imports without it
0
days a cancelled subscription stays “Active”
0
moving parts in the stack
fourteen fields, down to the card brand — and not the one that matters
What this was

Churn was happening, and nobody could ask why until it was too late

Intercom’s Stripe integration is the reason support can see a customer’s plan next to their conversation. It imports a generous set of fields. What it leaves out is the flag Stripe sets the moment a customer cancels, cancel_at_period_end — so a cancellation survey keyed on Stripe data could only fire when the subscription actually ended, weeks after the decision.

I built ChurnAlert to close that gap: an in-house Intercom app that reads the flag from Stripe and writes it onto the contact as a custom data attribute, where segments and series can use it like any other field.

One boolean. Everything downstream — the survey, the win-back offer, the follow-up — Intercom could already do; it just had nothing to trigger on.

The problem

Fourteen Stripe fields, and not the one that says a customer is leaving

Intercom’s Stripe app imports the plan, the price, the interval, even the card brand. It does not import cancel_at_period_end — the flag Stripe sets the moment someone cancels.

Intercom · filter by attribute
Sstripe_id
Sstripe_account_balance
Sstripe_delinquent
Sstripe_deleted
Sstripe_plan
Sstripe_plan_price
Sstripe_plan_interval
Sstripe_plan_interval_count
Sstripe_subscription_period_start_at
Sstripe_subscription_status
Sstripe_last_charge_amount
Sstripe_last_charge_at
Sstripe_card_expires_at
Sstripe_card_brand
✕cancel_at_period_endnot imported
Intercom help centre · the Stripe app · February 2025
Intercom’s article listing the attributes its Stripe app imports

Their own list, kept as it was published. The events at the bottom — invoice created, charge failed, subscription deleted — fire when things happen, not when a customer decides.

The cost

Thirty days in which the customer has already left and Intercom still calls them active

A cancelled subscription keeps its Active status until the paid period runs out. Intercom’s suggestion was to wait for that, or to detect the cancellation from a URL change on the front end.

day 0Activecancel_at_period_end · true
day 0 · the customer cancelsday 30 · the period ends🔔 survey fires

The status flips on day 30. A survey keyed on it reaches someone who stopped thinking about why they left a month ago — and with no period left to win them back in.

The status field has a second trap: a paused subscription is still “Active” to Stripe, so nothing keyed on status can tell a paying customer from one who has stepped away. A flag that says exactly what it means is the only thing worth filtering on.

The app

Two buttons in the Intercom inbox

An in-house Intercom app: connect a Stripe account once, then press Sync whenever the segment should be brought up to date. Nothing else to learn.

Redrawn · what the agent sees
ChurnAlert
Welcome to ChurnAlert!
Your Stripe account is connected. Click below to sync subscription statuses.
Sync Subscriptions
Reconnect Stripe ↗
Synced · 5 subscriptions read · 2 marked cancel_at_period_end

The sync is a button, not a webhook: the agent decides when the segment refreshes, and the result says what changed. Reconnect re-runs the Stripe OAuth flow when a key rotates.

As recorded · the inbox in 2025
Intercom · Inbox · ChurnAlert
Screen recording of ChurnAlert syncing a subscription inside the Intercom inbox
How it works

From one field to a retention flow

Read the flag from Stripe, write it to the contact in Intercom as a custom data attribute, and let Intercom’s own segments and series do the rest.

One sync, end to end

Both APIs are behind OAuth, so the app holds two sets of tokens per workspace and refreshes them itself.

Stripe API
subscriptions · cancel_at_period_end
ChurnAlert · Node.js
on Railway · OAuth tokens in Redis
Intercom API
custom data attribute on the contact
waiting for the agent…

What one boolean unlocks

Intercom already knows how to run a series off a segment. It only ever lacked something to filter on.

The customer cancelsin Stripe, day 0The flag turns trueon the Intercom contactThey enter the segmentcancel_at_period_end is trueThe survey asks whywhile they still rememberAn answer, or a returnan offer, a fix, a discount

Impact

Before and after

Built in-house in early 2025, for one support team and one Stripe account.

click a row to turn it back over
Under the hood
Node.js
The whole app: OAuth callbacks, the sync, the inbox panel
Stripe API
Subscriptions, read on each sync for cancel_at_period_end
Intercom API
Writes the value as a custom data attribute on the contact
Redis
Both sets of OAuth tokens, per workspace
OAuth 2.0
Stripe Connect on one side, Intercom on the other
Railway
Hosting, one service, no ops
if something doesn’t exist, just go ahead and build it, I guess