URL: /revenue/health · Id: `page:/revenue/health`

# Who might cancel: health, silence and who to call first

> Who might cancel: health by Customer, 14 days of silence, and the first call your CSM makes.

Customer health reads every paying [Customer](https://startupbuilder.dev/business/nouns/customer) each morning, and the CSM’s queue opens on the one most likely to cancel.

```text title="Ask"
Which customers might cancel, and who do I call first?
```

```ts title="SDK"
import { $ } from 'startup-builder'

const { rows } = await $.Metric.query({ metric: 'customer-health', shape: 'trend', breakdown: 'customer' })   // 4 Customers at risk; Harbor Row lowest since Oct 2
await $.Process.enable({ process: 'retention' }, { mode: 'try' })   // silence-watch: 14 days with no sign-in opens a Task
const { task } = await $.Task.next()   // your CSM agent: "Call Harbor Row first: 3 support threads on late notices"
await $.Message.schedule({ message: 'message_77', at: '2026-10-22T15:00:00Z' }, { mode: 'try' })   // the upgrade note usage-upsell drafted for Harbor Row
// 422 · The upgrade note to Harbor Row did not send: they are at risk.
// keep-first: Let their CSM reach them first, then ask again once they are healthy. · then Task.next
return await $.Metric.query({ metric: 'churned-arr', shape: 'trend', window: '7d' })   // Not live yet, gaps #4
```

```json title="Response · sample"
{
  "metric": {
    "$id": "https://api.sb/leaseline.app/metrics/churned-arr",
    "$type": "Metric",
    "name": "Churned ARR"
  },
  "rows": [
    {
      "week": "2026-10-19",
      "value": -1041,
      "customers": 3,
      "atRiskWeekBefore": 2
    }
  ]
}
```

`−$1,041 ARR lost this week · 3 Customers · 2 of them at risk a week earlier`

`Metric.query({"metric":"churned-arr","shape":"trend","window":"7d"})` · sample · leaseline.app at 2026-10-19

## What it does
| What it does | Price per unit | Moves | Who does it | Today |
| --- | --- | --- | --- | --- |
| <Op verb="Metric.query" /> reads `customer-health` by Customer each morning; the `silence-watch` Workflow opens a Task after 14 days with no sign-in. | $0 per read; $0.04 per check-in note (Worker cost) | [Customer health](https://startupbuilder.dev/agents/customer#customer-health), [Lost Customers](/revenue/churned-arr) | [CSM](/capabilities/how-your-agents-act#csm) | 4 of 5 calls live |

## Set it up
| Check (dry run on a 0-Customer Startup, gaps #8) | Fix | Then | Your time | Wait |
| --- | --- | --- | --- | --- |
| [`billing-connected`](/code/refusals#check-billing-connected) (Not live yet, gaps #8) · fail | Connect Stripe so every charge and Meter reading is live. | <Op verb="Integration.connect" /> | 5 min | the first sync, about 10 min |
| [`role-unstaffed`](/code/refusals#check-role-unstaffed) (Not live yet, gaps #8) · warn: the CSM has no person: at-risk Tasks come to you | Staff the CSM, or leave it: at-risk Tasks come to you. | <Op verb="Role.staff" /> | 0 min | none |

## The records
| Customer | Health | At risk since | Why | Who calls | By |
| --- | --- | --- | --- | --- | --- |
| Harbor Row | 31 | Oct 2 | 3 support threads on late notices | CSM · your agent drafts, you call | Oct 20 |
| Cedar Lane | 38 | Oct 9 | no sign-in for 19 days; asked us to stop emailing on Oct 12 | you, by phone | Oct 22 |
| Juniper Homes | 44 | Oct 12 | use down 40 %, renews Nov 3 | CSM · <Op verb="Renewal.open" /> | Oct 19 |
| Pinecrest Lofts | 47 | Oct 14 | their admin left | CSM · your agent | Oct 21 |

```json title="The four at risk, who calls first"
{
  "$id": "https://api.sb/leaseline.app/metrics/customer-health",
  "$type": "Metric",
  "name": "Customer health",
  "rows": [
    {
      "customer": "customer_19",
      "value": 31,
      "atRiskSince": "2026-10-02"
    }
  ]
}
```

## Calls
| Call | Here | Emits | Status |
| --- | --- | --- | --- |
| <Op verb="Metric.query" /> | 4 Customers at risk; Harbor Row lowest since Oct 2 | `Metric.queried` | live |
| <Op verb="Process.enable" /> | silence-watch: 14 days with no sign-in opens a Task | `Process.enabled` | live |
| <Op verb="Task.next" /> | your CSM agent: "Call Harbor Row first: 3 support threads on late notices" | `Task.offered` | live |
| <Op verb="Message.schedule" /> | the upgrade note usage-upsell drafted for Harbor Row | `Message.scheduled` | live |
| <Op verb="Metric.query" /> |  | `Metric.queried` | Not live yet, gaps #4 |

## Events
| Event | When it fires | What hears it |
| --- | --- | --- |
| `Task.offered` | a Customer turns at risk, or 14 days pass with no sign-in | the CSM’s <Op verb="Task.next" /> |
| `Renewal.opened` | a term Subscription nears its end | the CSM’s renewal Task |

## Refuses when

```json title="The 422 · sample"
{
  "type": "https://startupbuilder.dev/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "The upgrade note to Harbor Row did not send: they are at risk.",
  "checks": [
    {
      "check": "keep-first",
      "status": "fail",
      "doc": "/code/refusals#check-keep-first",
      "fix": "Let their CSM reach them first, then ask again once they are healthy.",
      "then": "Task.next"
    }
  ]
}
```

| Check | Refuses | Fix | Then |
| --- | --- | --- | --- |
| [`keep-first`](/code/refusals#check-keep-first) (Not live yet, gaps #8) | an upgrade, referral, review or reference ask to a Customer who is at risk, past due or leaving | Let their CSM reach them first, then ask again once they are healthy. | <Op verb="Task.next" /> |
| [`owned-by-a-person`](/code/refusals#check-owned-by-a-person) (Not live yet, gaps #8) | a send to a Contact whose Lead, open Deal or Conversation a person owns | Leave it to the person who owns it: the draft waits in their queue. | <Op verb="Task.assign" /> |

## Plays that use it
| Play | What it does | Use it when |
| --- | --- | --- |
| `quiet-account-rescue` (Not live yet, gaps #4) | The Play that calls a paying Customer who went quiet, before they cancel. | today: The hub Workflow silence-watch, which opens a Task in <Op verb="Task.next" />. |
| [Annual prepay](/plays/annual-prepay) | Offers a steady monthly Customer a year up front, at a discount their own churn pays for, and answers an annual cancel with one save. | 10 or more monthly Customers past their third renewal, losing 2 % or more a month |
| [Objection watch](/plays/objection-watch) | Records why each Customer or Deal said no, holds them from other outreach, and sends one Message, with proof, once the reason stops being true. | 20 or more nos a month recorded with a reason: lost Deals, cancels, downgrades and stalled trials |

## Hub guides it deepens
| On startupbuilder.dev | Id |
| --- | --- |
| [Retention](https://startupbuilder.dev/workflows/processes/retention) | `process:retention` |
| [Silence watch](https://startupbuilder.dev/workflows/processes/retention/silence-watch) | `workflow:silence-watch` |

## Example
```text title="Leaseline, as the fixture ran it"
Oct 19 · 4 at risk · Harbor Row first (31) · the upgrade note usage-upsell drafted for them is refused (keep-first) · the CSM calls Oct 20 · 2 of the 3 Customers lost this week were at risk a week earlier · sample
```