URL: /revenue/what-they-tell-you · Id: `page:/revenue/what-they-tell-you`

# What they tell you: reasons, coded each week

> What they tell you: every cancel reason coded each week, one interview, and a note when it is fixed.

Every cancel reason is coded each week, and the [Customers](https://startupbuilder.dev/business/nouns/customer) who gave it hear back when it is fixed.

```text title="Ask"
Why did customers leave this quarter, and who do we talk to first?
```

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

const { events } = await $.Event.list({ where: 'name = "Subscription.unsubscribed"', limit: 20 })   // 6 cancels since Jul 21, each with its reason code
const { rows } = await $.Collection.query({ query: 'select data.reason, count(*) as customers from events where name = \'Subscription.unsubscribed\' and at >= \'2026-07-21\' group by data.reason' })
await $.Meeting.book({ contact: 'contact_44', at: '2026-10-22T16:00:00Z' }, { mode: 'try' })   // signal-interview: 20 minutes with Aspen Row’s office manager
return await $.Message.schedule({ message: 'message_99', at: '2026-10-26T14:00:00Z' }, { mode: 'try' })   // close-the-loop: owner statements shipped, to the 2 who asked
```

```json title="Response · sample"
{
  "message": {
    "$id": "https://api.sb/leaseline.app/messages/message_99",
    "$type": "Message",
    "status": "scheduled",
    "mode": "try",
    "to": "contact_44",
    "subject": "Owner statements are live"
  }
}
```

## What it does
| What it does | Price per unit | Moves | Who does it | Today |
| --- | --- | --- | --- | --- |
| The `customer-research` Process codes each <Op verb="Subscription.unsubscribe" /> reason every Monday; `signal-interview` books the call and `close-the-loop` writes back. | $0 per coded reason; $0.04 per note (Worker cost) | [Lost Customers](/revenue/churned-arr), [Logo churn](https://startupbuilder.dev/agents/customer#logo-churn) | [Head of customers](/capabilities/how-your-agents-act#head-of-customers) | every call live |

## Set it up
| Check (dry run on a 0-Customer Startup, gaps #8) | Fix | Then | Your time | Wait |
| --- | --- | --- | --- | --- |
| [`use-when`](/code/refusals#check-use-when) (Not live yet, gaps #8) · pass: 6 cancels with a reason in 90 days | Run the Play Not yet when names, or wait until the volume is there. | <Op verb="Metric.query" /> | 1 min | none |
| [`role-unstaffed`](/code/refusals#check-role-unstaffed) (Not live yet, gaps #8) · warn: Head of customers has no person: interviews come to you | Staff Head of customers, or leave it: interviews come to you. | <Op verb="Role.staff" /> | 0 min | none |

## The records
| Reason | Customers | What answers it |
| --- | --- | --- |
| missing-feature: owner statements | 2 | the Change merged Oct 20; a note to each (Win-back) |
| too-expensive | 2 | the $19 Starter plan, offered once (When someone cancels) |
| switched | 1 | objection-watch holds them until the reason stops being true |
| no-longer-needed | 1 | nothing sends |

```json title="The reasons, coded on Monday"
{
  "$id": "https://api.sb/leaseline.app/events/event_1180",
  "$type": "Event",
  "name": "Subscription.unsubscribed",
  "at": "2026-09-02T16:12:00Z",
  "data": {
    "subscription": "subscription_4",
    "reason": "missing-feature",
    "comment": "We need owner statements."
  }
}
```

## Calls
| Call | Here | Emits | Status |
| --- | --- | --- | --- |
| <Op verb="Event.list" /> | 6 cancels since Jul 21, each with its reason code | — | live |
| <Op verb="Collection.query" /> |  | `Collection.queried` | live |
| <Op verb="Meeting.book" /> | signal-interview: 20 minutes with Aspen Row’s office manager | `Meeting.booked` | live |
| <Op verb="Message.schedule" /> | close-the-loop: owner statements shipped, to the 2 who asked | `Message.scheduled` | live |

## Events
| Event | When it fires | What hears it |
| --- | --- | --- |
| `Subscription.unsubscribed` | a cancel, with its reason code | coded every Monday by `customer-research` |
| `Meeting.booked` | a former Customer says yes to 20 minutes | the interview notes, on the reason |

## Refuses when
| Check | Refuses | Fix | Then |
| --- | --- | --- | --- |
| [`customer-ask-cap`](/code/refusals#check-customer-ask-cap) (Not live yet, gaps #3) | a second open ask to one Customer in a window, from any Play | Wait for the open ask to close; <Op verb="Decision.list" /> names the Play that holds them. | <Op verb="Decision.list" /> |

## Plays that use it
| Play | What it does | Use it when |
| --- | --- | --- |
| [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 |
| --- | --- |
| [Customer research](https://startupbuilder.dev/workflows/processes/customer-research) | `process:customer-research` |
| [Signal interview](https://startupbuilder.dev/workflows/processes/customer-research/signal-interview) | `workflow:signal-interview` |
| [Close the loop](https://startupbuilder.dev/workflows/processes/customer-research/close-the-loop) | `workflow:close-the-loop` |

## Example
```text title="Leaseline, as the fixture ran it"
Since Jul 21 · 6 cancels · missing-feature 2, too-expensive 2, switched 1, no-longer-needed 1 · a 20-minute call with Aspen Row Oct 22 · owner statements shipped Oct 20 · close-the-loop note Oct 26 · sample
```