Why this matters
Downgrade is the soft-churn signal nobody tracks. Cancellation gets a workflow (exit survey, save-offer email, CS call). Downgrade just happens silently in the Stripe dashboard — they stop paying you $300/mo and start paying you $0/mo, and you find out next quarter when MRR breaks down.
But downgrade is the highest-quality leading indicator there is. Saving a downgrade is 10x cheaper than winning back a full cancellation (they still have the account, the data, the muscle memory). The reason for the downgrade is usually small and addressable — they stopped using one specific feature, or their team shrunk and didn’t need 10 seats.
Mixpanel and PostHog can do before/after cohort comparisons but you have to build them by hand each time. Agentry collapses it to one prompt: “show me last month’s downgrades and what changed in their behavior.”
What you get
- A list of accounts that downgraded in the last 30 days, with
from_plan → to_plan - For each: top-3 events that decreased the most between the 30-day pre and post window
- A CS-ready summary: “Acme dropped Pro → Free; they stopped using the API integration 2 weeks before downgrade”
Walk through it
List paid accounts that downgraded in the last 30 days. For each, compare their behavior in the 30 days before vs after the downgrade.
Let me first identify the downgraders.
agentry_analytics_query {
project_id: "default",
hogql: "SELECT distinct_id, properties.from_plan AS from_plan, properties.to_plan AS to_plan, timestamp AS event_ts FROM events WHERE event = 'plan_changed' AND properties.from_plan_rank > properties.to_plan_rank AND timestamp >= now() - INTERVAL 30 DAY ORDER BY event_ts DESC"
}
{
"rows": [
{ "distinct_id": "team-acme", "from_plan": "pro", "to_plan": "free", "event_ts": 1747100000 },
{ "distinct_id": "team-globex", "from_plan": "scale", "to_plan": "pro", "event_ts": 1746950000 },
{ "distinct_id": "team-megacorp", "from_plan": "enterprise", "to_plan": "scale", "event_ts": 1746800000 },
{ "distinct_id": "team-tinyco", "from_plan": "starter", "to_plan": "free", "event_ts": 1746700000 }
]
}
Four downgrades. Let me put them in a cohort and pull behavior before/after for the largest one (team-acme, Pro→Free, lost $300/mo).
agentry_create_cohort {
project_id: "default",
name: "downgrades_last_30d",
description: "Accounts that dropped to a lower tier in last 30d",
filters: [{ event: "plan_changed", to_plan_rank_lt: "from_plan_rank", since: "30d" }]
}
{ "cohort_id": "coh_dg_1", "member_count": 4 }
agentry_get_distinct_id_summary {
project_id: "default",
distinct_id: "team-acme",
windows: [
{ name: "pre_downgrade", from: 1744508000, to: 1747100000 },
{ name: "post_downgrade", from: 1747100000, to: 1749692000 }
]
}
{
"windows": {
"pre_downgrade": {
"total_events": 1840,
"by_event": {
"api_call": 1240,
"dashboard_view": 480,
"report_exported": 88,
"team_member_invited": 12,
"integration_slack_used": 20
}
},
"post_downgrade": {
"total_events": 220,
"by_event": {
"api_call": 4,
"dashboard_view": 200,
"report_exported": 12,
"team_member_invited": 0,
"integration_slack_used": 4
}
}
}
}
Acme’s downgrade tells a clear story:
- API calls collapsed from 1,240 → 4 (-99%). They were Pro because they needed the API. They stopped using it 30 days before the downgrade.
- Dashboard views barely changed (480 → 200, which is half because the time window is 30 days vs 30 days but they’re now visiting less). They’re still around.
- Team invites: 12 → 0. Team shrunk.
Likely reason: an engineering project ended, they don’t need the API right now, they want to keep their data so they downgraded instead of canceling. This is a winback candidate, not a churn candidate. A “want to come back to Pro for $99 first month?” email would probably land.
Want me to do the same analysis for the other three?
The output
Downgrades (last 30d) — pre vs post behavior analysis
team-acme Pro → Free ($300/mo lost)
↓ api_call: 1240 → 4 (-99.7%) ★ THE STORY
↓ team_member_invited: 12 → 0 (-100%)
↓ dashboard_view: 480 → 200 (still showing up)
↓ report_exported: 88 → 12
Diagnosis: API project ended. Saving the seat = saving Pro.
Recommended outreach: "API back for $99 first month?" save offer
team-globex Scale → Pro ($1,800/mo retained, $1,200/mo lost)
↓ workflow_executed: 14,200 → 4,100 (-71%)
Diagnosis: workflow volume cut. They downsized correctly.
Action: NO outreach — they're happy at Pro.
team-megacorp Enterprise → Scale ($27k/mo lost) ★ URGENT
↓ admin_action: 220 → 12 (-95%)
↓ team_member_invited: 24 → 0
Diagnosis: champion left? Admin activity collapsed.
Recommended outreach: CSM calls TODAY, find the new champion.
team-tinyco Starter → Free ($60/mo lost)
↓ all events: 220 → 18 (-92%)
Diagnosis: Dormant. Probably hobby project ended.
Action: NO outreach (low ROI), let them stay on free.
Setting it up
The recipe needs one event class — plan_changed — fired from your Stripe webhook handler whenever a subscription changes tier. Include both from_plan and to_plan, and a numeric plan_rank so the agent can detect “down” without hard-coding tier names:
// In your Stripe webhook handler for `customer.subscription.updated`
const PLAN_RANK: Record<string, number> = {
free: 0,
starter: 1,
pro: 2,
scale: 3,
enterprise: 4,
};
if (oldPriceId !== newPriceId) {
const from_plan = planNameFromPrice(oldPriceId);
const to_plan = planNameFromPrice(newPriceId);
await fetch(`https://api.agentry.sh/v1/analytics/${PROJECT_ID}/`, {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.AGENTRY_DSN}`,
"Content-Type": "application/json",
"User-Agent": "myapp-stripe/1.0", // REQUIRED — Cloudflare 403s default UAs
},
body: JSON.stringify({
event: "plan_changed",
distinct_id: subscription.metadata.account_id,
properties: {
from_plan,
to_plan,
from_plan_rank: PLAN_RANK[from_plan],
to_plan_rank: PLAN_RANK[to_plan],
mrr_delta: (newPriceCents - oldPriceCents) / 100,
},
}),
});
}
For the before/after windows to work, you need at least 30 days of event history for each downgraded account. If you only started instrumenting last week, this recipe gets useful in ~3-4 weeks.
Variations
- “For each downgrade, also show the SUPPORT tickets they opened in the 30 days before. Was there a known frustration?”
- “Of the accounts that downgraded last quarter, how many fully canceled within 90 days? That’s our ‘downgrade → churn’ conversion rate.”
- “Build the inverse — accounts that UPGRADED. What behaviors increased?” (see
expansion-prediction) - “Weekly cron: post the previous week’s downgrades to #cs-churn with the diagnosis.”