What goes in a customer health dashboard?
A customer health dashboard is the shared answer to "which accounts are in trouble, and how do we know?" — the score distribution, who moved this week, and whether the score agrees with what customers actually do in the product. Build it on health-score components you re-derive in the warehouse, not on a vendor score you cannot explain.
Which cards belong on this dashboard?
- Health score distribution across the book of business
- Accounts and ARR by risk tier (green, yellow, red)
- Biggest score movers over the last 30 days
- Health score vs. weekly active users, plotted per account
- Accounts red on usage but green on score (and the reverse)
- Open risks with owner, playbook, and age
- Coverage: accounts with no touchpoint in 60+ days
- Score component breakdown for a selected account
What data does this dashboard need?
- An account dimension with segment, CSM owner, and contract dates
- Health-score components stored as columns, not just the composite
- Score history with an as-of date so movement is measurable
- Product usage at daily account grain (active users, key events)
- Touchpoints: meetings, tickets, QBRs, emails logged per account
- Open risks or playbook tasks with status and owner
How do you build it?
- Land CS platform, CRM, billing, and product-usage data in a database and keep account IDs, contract dates, ARR, health-score components, and event timestamps.
- Build account-grain models at the grain this dashboard needs, with health components and revenue definitions as explicit columns.
- Create one saved question per card and certify the shared models and definitions.
- Add dashboard filters for Segment, CSM owner, risk tier, renewal quarter, region, plan.
- Reconcile ARR against billing rather than the CS tool, and show data freshness so every viewer knows how current the syncs are.
Example card SQL
WITH latest_score AS (
SELECT DISTINCT ON (account_id)
account_id,
scored_at,
health_score
FROM account_health_scores
ORDER BY account_id, scored_at DESC
), usage_28d AS (
SELECT
account_id,
SUM(active_users) AS active_user_days
FROM product_usage_daily
WHERE usage_date >= CURRENT_DATE - INTERVAL '28 days'
GROUP BY account_id
)
SELECT
CASE
WHEN s.health_score >= 70 THEN 'green'
WHEN s.health_score >= 40 THEN 'yellow'
ELSE 'red'
END AS risk_tier,
COUNT(*) AS accounts,
ROUND(SUM(a.arr_usd), 2) AS arr,
COUNT(*) FILTER (
WHERE COALESCE(u.active_user_days, 0) = 0
) AS accounts_with_no_usage_28d
FROM cs_accounts a
JOIN latest_score s USING (account_id)
LEFT JOIN usage_28d u USING (account_id)
WHERE a.status = 'active'
GROUP BY 1
ORDER BY MIN(s.health_score);