Churn prediction is the practice of scoring each active customer's likelihood of cancelling before they actually cancel, so the team can intervene while there's still something to save. It only pays off if the model is built on signals that precede churn, not signals that merely correlate with it in hindsight — and if the output feeds an action, not just a dashboard nobody opens.
The signals that actually predict churn
Usage decline is the strongest and most available signal in most B2B SaaS products: a drop in login frequency, feature usage, or seats actively used over a trailing window (commonly 30 and 90 days) tends to precede cancellation by weeks or months. Support signals matter too — a rise in ticket volume, especially unresolved or escalated tickets, correlates with dissatisfaction. Billing signals (failed payment retries, downgrades, seat reductions) are lagging but high-precision. The mistake most teams make is training only on account-level aggregates and missing the within-account pattern: a champion who leaves, or a single power user going quiet, is often a stronger signal than a slight dip in average usage across the whole account.
Framing it as a prediction problem
Churn prediction is a binary classification problem with a time horizon: will this account churn in the next N days? Getting the label right matters more than the algorithm. Voluntary churn (cancel button pressed) and involuntary churn (payment failure with no recovery) have different drivers and should usually be modeled separately. Gradient-boosted trees (XGBoost, LightGBM) are the standard choice for tabular account data — they handle mixed feature types well and are easy to explain with SHAP values, which matters because customer success teams need to know why an account scored high risk, not just that it did.
import pandas as pd
def build_features(events: pd.DataFrame, as_of: pd.Timestamp) -> pd.DataFrame:
window_30 = events[events.event_at >= as_of - pd.Timedelta(days=30)]
window_90 = events[events.event_at >= as_of - pd.Timedelta(days=90)]
return pd.DataFrame({
"logins_30d": window_30.groupby("account_id").size(),
"logins_90d": window_90.groupby("account_id").size(),
"login_trend": (
window_30.groupby("account_id").size()
/ window_90.groupby("account_id").size().clip(lower=1)
),
"open_tickets": window_30[window_30.event_type == "support_ticket"]
.groupby("account_id").size(),
}).fillna(0)
The leakage trap
The single most common way churn models look great in backtesting and fail in production is label leakage: features computed using data from after the point you're trying to predict from, or features that are themselves downstream of the decision to churn (a downgrade to a free plan a week before cancellation is not a predictor — it's the same event). Build a strict as-of-date feature pipeline and validate that every feature could genuinely have been computed on that date, not with hindsight.
A churn score only matters if it triggers something — an automated check-in email, a CSM task, a proactive discount offer. Teams that build the model and stop there usually find the score sits unused, because nobody owns the follow-through. Design the intervention before you finish the model.
Evaluation that reflects the business
Accuracy is close to useless here because churn is rare relative to retention — a model that predicts "never churns" can be 95% accurate and worthless. Use precision and recall at the top-K accounts by risk score (the number your CS team can realistically act on this week), and track lift over a naive baseline like "flag anyone whose usage dropped 50%." Revenue-weighted recall — did you catch the accounts that matter most in dollar terms — is usually more useful to the business than accounts caught overall.
Product usage patterns shift as the product changes, pricing changes, and the customer base matures. A model trained once on last year's cohort will quietly degrade. Monthly or quarterly retraining with a held-out recent window catches drift before it erodes trust in the score.
Start simple before you build a model
Before reaching for gradient boosting, a rules-based health score (weighted combination of login recency, usage trend, and support tickets) often captures 70-80% of the value with a fraction of the engineering effort, and it's far easier for a CS team to trust and reason about. Move to a trained model once the rules-based score is in production, being acted on, and the team has enough labeled churn events to validate against.
Churn prediction is worth building when the account base is large enough that CS can't manually track everyone, and worth skipping — or starting with rules — when it isn't. The model is only as useful as the intervention it triggers and the discipline to retrain it as the product and customers change.
Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.