One Number. Red, Amber, or Green. Everything Else Is Noise.
You're drowning in dashboards. Login counts. DAUs. Feature adoption percentages. Support ticket volume. NPS scores. Churn rates. Renewal dates. Expansion revenue. Each number tells you something, but none of them tell you the truth: is this customer about to cancel?
Velocity X bundles five signals into a single Customer Health Score. Login recency (30%). Feature depth (25%). NPS (20%). Support load (−15%). Payment health (10%). The score lives 0–100. Red zone (under 40) = urgent risk. Amber (40–70) = stable but shallow. Green (over 70) = expansion candidate. One tile on the dashboard. Five lines of SQL. No guessing. No gut feel. No spreadsheet. Just the number that matters.
Why One Number Beats Twelve KPIs
The human brain is a pattern-matching machine designed to survive, not to execute SaaS retention strategy. Show someone twelve metrics, and they freeze. Show them one score with three colour zones, and they act.
You've seen this before. Sales teams with 15 columns in Salesforce. Nobody uses them. Product teams tracking 40 events. They instrument for ego, not clarity. The moment you ask "which three metrics actually predict churn?", everyone goes quiet. Then they mention "engagement", "stickiness", "velocity", or other word-clouds that live nowhere in your database.
A health score forces the question early: *What actually predicts whether this customer renews?* You interview the data. You backtest two years of churned customers. You find the five inputs that separate "renewed at 12 months" from "cancelled at month 7". You weight them by predictive power. You lock the formula. You ship one score. The score becomes the language the entire company speaks.
The Formula: Five Signals, One Number
Velocity's health score weights five signals proven to predict churn in SaaS:
Login Recency (30% weight) — a customer who hasn't logged in 14+ days is drifting. At 21 days, they're likely gone. Last login within 3 days is 30 points. 4–7 days = 25 points. 8–14 days = 15 points. 15–20 days = 5 points. 21+ days = 0 points. Users who drop below 15 points almost always churn within 30 days.
Feature Depth (25% weight) — using more features = higher engagement = less likely to churn. Count the distinct features touched in the last 30 days. If a product has 12 core features, touching 10+ = 25 points. 7–9 = 18 points. 4–6 = 10 points. 1–3 = 3 points. Zero features touched = 0 points. Customers using less than 4 features by month 3 rarely make it to renewal.
NPS (20% weight) — ask quarterly (automated email in Velocity, handled by Supabase trigger). NPS ≥ 50 = 20 points. NPS 20–49 = 12 points. NPS 0–19 = 5 points. Detractors (NPS < 0) = 0 points. NPS isn't a live signal (it's quarterly), but it captures sentiment. Weight it lower than recency.
Support Load (−15% weight, penalty) — more support tickets = more pain = higher churn risk. Count open + resolved tickets in the last 30 days. Zero tickets = 0 points subtracted (baseline). 1–2 tickets = −2 points. 3–5 = −5 points. 6–10 = −10 points. 10+ = −15 points. (The negative weighting means tickets reduce the score, not increase it.)
Payment Health (10% weight) — failed payment attempts or declined cards signal cash-flow trouble or card expiry. Zero failures in the last 30 days = 10 points. One failure = 8 points. Two failures = 5 points. Three+ failures = 0 points. Customers with repeat payment failures almost never renew without intervention.
Total calculation: add the five weighted inputs, cap at 100, floor at 0. A customer with perfect login recency (30), using 8 features (18), NPS of 55 (12), zero support tickets (0), and clean payments (10) scores 30 + 18 + 12 + 0 + 10 = 70 (green, stable). Same customer with no login in 25 days (0), three features (3), NPS 10 (5), four support tickets (−5), and two failed payments (5) scores 0 + 3 + 5 − 5 + 5 = 8 (red, urgent).
The SQL: Real Data, Real View
Velocity stores the score in a Supabase materialized view. The view recalculates nightly (or on-demand). Here's the shape:
CREATE MATERIALIZED VIEW customer_health_scores AS
WITH login_score AS (
SELECT customer_id,
CASE WHEN days_since_login ≤ 3 THEN 30
WHEN days_since_login ≤ 7 THEN 25
WHEN days_since_login ≤ 14 THEN 15
WHEN days_since_login ≤ 20 THEN 5
ELSE 0 END AS login_points
FROM (
SELECT customer_id, EXTRACT(DAY FROM now() - MAX(last_login)) as days_since_login
FROM user_sessions
GROUP BY customer_id
) ls
),
feature_score AS (
SELECT customer_id,
CASE WHEN feature_count ≥ 10 THEN 25
WHEN feature_count ≥ 7 THEN 18
WHEN feature_count ≥ 4 THEN 10
WHEN feature_count ≥ 1 THEN 3
ELSE 0 END AS feature_points
FROM (
SELECT customer_id, COUNT(DISTINCT feature_name) as feature_count
FROM feature_usage
WHERE created_at > now() - interval '30 days'
GROUP BY customer_id
) fu
),
nps_score AS (
SELECT customer_id,
CASE WHEN nps_score ≥ 50 THEN 20
WHEN nps_score ≥ 20 THEN 12
WHEN nps_score ≥ 0 THEN 5
ELSE 0 END AS nps_points
FROM (
SELECT customer_id, nps_score
FROM nps_surveys
WHERE created_at > now() - interval '90 days'
ORDER BY created_at DESC
LIMIT 1
) ns
),
support_score AS (
SELECT customer_id,
CASE WHEN ticket_count = 0 THEN 0
WHEN ticket_count ≤ 2 THEN -2
WHEN ticket_count ≤ 5 THEN -5
WHEN ticket_count ≤ 10 THEN -10
ELSE -15 END AS support_penalty
FROM (
SELECT customer_id, COUNT(*) as ticket_count
FROM support_tickets
WHERE created_at > now() - interval '30 days'
GROUP BY customer_id
) st
),
payment_score AS (
SELECT customer_id,
CASE WHEN failed_count = 0 THEN 10
WHEN failed_count = 1 THEN 8
WHEN failed_count = 2 THEN 5
ELSE 0 END AS payment_points
FROM (
SELECT customer_id, COUNT(*) as failed_count
FROM payment_events
WHERE status = 'failed'
AND created_at > now() - interval '30 days'
GROUP BY customer_id
) pe
)
SELECT c.id as customer_id,
c.name,
COALESCE(ls.login_points, 0) +
COALESCE(fs.feature_points, 0) +
COALESCE(ns.nps_points, 0) +
COALESCE(st.support_penalty, 0) +
COALESCE(ps.payment_points, 0) AS health_score,
CASE WHEN COALESCE(ls.login_points, 0) +
COALESCE(fs.feature_points, 0) +
COALESCE(ns.nps_points, 0) +
COALESCE(st.support_penalty, 0) +
COALESCE(ps.payment_points, 0) < 40 THEN 'red'
WHEN COALESCE(ls.login_points, 0) +
COALESCE(fs.feature_points, 0) +
COALESCE(ns.nps_points, 0) +
COALESCE(st.support_penalty, 0) +
COALESCE(ps.payment_points, 0) < 70 THEN 'amber'
ELSE 'green' END AS status
FROM customers c
LEFT JOIN login_score ls ON c.id = ls.customer_id
LEFT JOIN feature_score fs ON c.id = fs.customer_id
LEFT JOIN nps_score ns ON c.id = ns.customer_id
LEFT JOIN support_score st ON c.id = st.customer_id
LEFT JOIN payment_score ps ON c.id = ps.customer_id
ORDER BY health_score ASC;
The view joins five CTEs (login, feature, NPS, support, payment), weights them, sums to a final score, and colour-codes the result. Query time: sub-100ms. Refresh nightly. Or on-demand if a support ticket lands — the view re-reads fresh data each query.
The Dashboard Tile
A single React component queries the view and renders four rows: red (urgent), amber (at-risk), green (healthy), and summary stats. The tile shows customer name, health score (0–100 number, bold), status badge (red/amber/green circle), last login date, NPS if available, and an "Action" dropdown with templated emails for each zone. Red customers see a "Schedule call" button. Amber customers see "Check in on adoption". Green customers see "Explore expansion". The tile sorts by health score descending so the worst customers surface first.
Red Flag Playbook: What to Do With Each Zone
RED (score < 40): Urgent. Act within 48 hours. Send a personal outreach email (templated but signed by CSM): "Hey [name], noticed you haven't logged in since [date]. Any blockers we can help with? Offering a 15-min call this week." Schedule a support call. Check the support ticket history — if there are open tickets, resolve them immediately. If there are no tickets but the customer is silent, they're in stealth-churn territory. They've given up on asking for help. A phone call (not email) closes more red customers than anything else. Target: move to amber within 14 days or flag for executive outreach.
AMBER (score 40–70): Stable but shallow. Optimize within 2 weeks. These customers have logged in recently and are paying, but they're using fewer than half your features. They're not churning yet — they're under-leveraging. Send a feature-adoption email: "You're using [feature A], but [feature B] would save you 5 hours a month. Here's a 4-minute video." Invite them to an opt-in onboarding call focused on depth. Track feature adoption for the next 30 days. If they move to green, you've expanded the account. If they stay amber, they're fine for now but not a renewal upsell candidate. Target: move 30% to green, maintain 70% in amber without slipping to red.
GREEN (score > 70): Healthy. Expansion-ready. These customers log in weekly+, use most features, have healthy NPS, and haven't filed support complaints. They're the renewal-lock, upsell, and reference-able segment. Send expansion emails: "Your team is crushing it with [feature]. Ever thought about [adjacent product]?" Invite them to advisory boards or case-study interviews. They're your best customers and they know it. Treat them accordingly.
Six FAQs
How often should I recalculate the health score?
Nightly is safe. The query touches four weeks of data, so daily refresh captures logins, features, support tickets, and payment events as they happen. If you need real-time urgency (e.g., a customer just failed a payment), trigger the view refresh on-demand via an API endpoint. Don't make it a live query on every dashboard page-load — batch it.
What if a customer has zero NPS responses?
Default them to 0 points for NPS (not negative). If they've never filled an NPS survey and they're new (under 30 days), the score is weighted more toward login and feature depth. If they're old (over 90 days) and refuse to respond, treat zero as a soft signal of detachment — they're not a detractor, but they're not an enthusiast either. Consider a re-prompt if NPS is stale.
Can I customize the weights for my business?
Yes. The formula above is Velocity's default, tested across SaaS. But if your product is support-heavy (e.g., a managed service), bump support load weight to 30% and dial down login recency to 20%. If your product is viral (network effects), bump feature depth to 40%. Backtest your changes on historical churn data before deploying. If 80% of your churn had support tickets, the weights matter. If support tickets are noise, they don't. Let your data decide.
Should I surface the health score to customers?
No. Telling a customer "your health score is 23" sounds like a threat. Use the score internally only. Surface the *action* instead: "We noticed [thing]. Here's how we help." The score is your early warning. The communication is customer-friendly.
What if a customer has no login data?
Default to 0 points. They're either new (sign-up today, haven't logged in yet — score them as amber due to other signals) or they've completely disappeared (never logged in, no activity, score red). The SQL COALESCE handles NULLs, so the math doesn't break. New customers need a grace period; add a filter to exclude accounts under 7 days old from red-zone alerts.
How do I know if the score is actually predicting churn?
Run a 6-month backtest. Pull historical data for 100 customers who churned. Calculate their health score from the month before they cancelled. Count how many were in red (< 40). If 70%+ of churned customers hit red in month-before, your score is predictive. If only 40%, the weights are off. Adjust and test again. You're looking for 65%+ signal strength before trusting the formula to drive actions.
The Bottom Line
Twelve metrics paralyse. One score that fits on a dashboard tile and drives action actually saves accounts. Velocity X's health score is login recency (30%), feature depth (25%), NPS (20%), support load (−15%), and payment health (10%). Red means urgent. Amber means shallow. Green means expand. A single materialized view in Supabase. Nightly refresh. 30–day windows on recency signals so the score is always live. Build yours today, backtest against six months of churn data, and watch red customers get a phone call instead of a goodbye email.
Ready to ship customer health into your SaaS? Check Velocity pricing to see how health scores fit into the full Velocity feature set, or dive deeper into cohort retention curves to understand the data that builds health scoring in the first place.