URL: /revenue/win-back · Id: `page:/revenue/win-back`

# Win-back: once, after the fix

> Win-back: one note to a former Customer once the reason they left is fixed, and only once.

A [Customer](https://startupbuilder.dev/business/nouns/customer) who left gets one note after the reason they gave is fixed, and never before.

```text title="Ask"
Owner statements shipped. Write once to the Customers who left over it. Don’t send.
```

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

const { events } = await $.Event.list({ where: 'name = "Subscription.unsubscribed"', limit: 10 })   // 2 of the 6 who left in 90 days gave missing-feature: owner statements
await $.Message.schedule({ message: 'message_88', at: '2026-10-22T14:00:00Z' }, { mode: 'try' })   // the one note to Aspen Row, after the Change merged on Oct 20
await $.Message.schedule({ message: 'message_89', at: '2026-10-29T14:00:00Z' }, { mode: 'try' })   // a second note a week later
// 422 · The second note to Aspen Row did not send: the win-back note of Oct 22 is still open.
// customer-ask-cap: Wait for the open ask to close; Decision.list names the Play that holds them. · then Decision.list
await $.Subscription.reactivate({ subscription: 'subscription_4' }, { mode: 'try' })   // Not live yet, gaps #2
return await $.Metric.query({ metric: 'reactivated-arr', shape: 'trend', window: '28d' })   // Not live yet, gaps #4
```

```json title="Response · sample"
{
  "metric": {
    "$id": "https://api.sb/leaseline.app/metrics/reactivated-arr",
    "$type": "Metric",
    "name": "Reactivated ARR"
  },
  "rows": [
    {
      "window": "28d",
      "value": 347,
      "customers": 1
    }
  ]
}
```

`+$347 ARR won back in 28 days · 1 of 6 who left · before-after, not proof · ■□□□ Too early to tell · n 6`

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

## What it does
| What it does | Price per unit | Moves | Who does it | Today |
| --- | --- | --- | --- | --- |
| The win-back step of the `save-and-win-back` Workflow schedules one <Op verb="Message.schedule" /> per former Customer once a Change fixes the reason they gave. | $0.04 per note (Worker cost) | [Won back](/revenue/reactivated-arr) | [Head of customers](/capabilities/how-your-agents-act#head-of-customers) | 3 of 5 calls live |

## Set it up
| Check (dry run on a 0-Customer Startup, gaps #8) | Fix | Then | Your time | Wait |
| --- | --- | --- | --- | --- |
| [`baseline`](/code/refusals#check-baseline) (Not live yet, gaps #8) · warn: 1 of 6 who left came back in 90 days | n 6: it reads before-after, not proof. | <Op verb="Metric.query" /> | 0 min | none |
| [`role-unstaffed`](/code/refusals#check-role-unstaffed) (Not live yet, gaps #8) · warn: Head of customers has no person: each note comes to you | Staff Head of customers, or leave it: each note comes to you. | <Op verb="Role.staff" /> | 0 min | none |

## The records
```json title="Aspen Row, a former Customer"
{
  "$id": "https://api.sb/leaseline.app/subscriptions/subscription_4",
  "$type": "Subscription",
  "status": "canceled",
  "renewsAt": null,
  "termEndsAt": "2026-09-02",
  "quantity": 1
}
```

## Calls
| Call | Here | Emits | Status |
| --- | --- | --- | --- |
| <Op verb="Event.list" /> | 2 of the 6 who left in 90 days gave missing-feature: owner statements | — | live |
| <Op verb="Message.schedule" /> | the one note to Aspen Row, after the Change merged on Oct 20 | `Message.scheduled` | live |
| <Op verb="Message.schedule" /> | a second note a week later | `Message.scheduled` | live |
| `Subscription.reactivate` (Not live yet, gaps #2) |  | — | Not live yet, gaps #2 |
| <Op verb="Metric.query" /> |  | `Metric.queried` | Not live yet, gaps #4 |

## Events
| Event | When it fires | What hears it |
| --- | --- | --- |
| `Change.merged` | the fix ships: owner statements, Oct 20 | the win-back step, for each former Customer whose reason it answers |
| `Subscription.unsubscribed` | they left, with a reason code | the list the win-back step reads |

## Refuses when

```json title="The 422 · sample"
{
  "type": "https://startupbuilder.dev/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "The second note to Aspen Row did not send: the win-back note of Oct 22 is still open.",
  "checks": [
    {
      "check": "customer-ask-cap",
      "status": "fail",
      "doc": "/code/refusals#check-customer-ask-cap",
      "fix": "Wait for the open ask to close; Decision.list names the Play that holds them.",
      "then": "Decision.list"
    }
  ]
}
```

| 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 |
| `fix-triggered-win-back` (Not live yet, gaps #4) | The Play that writes once to former Customers when the reason they left is fixed. | today: The hub Workflow save-and-win-back: its win-back step, one <Op verb="Message.schedule" />. |

## Hub guides it deepens
| On startupbuilder.dev | Id |
| --- | --- |
| [Save and win back](https://startupbuilder.dev/workflows/processes/retention/save-and-win-back) | `workflow:save-and-win-back` |
| [Retention](https://startupbuilder.dev/workflows/processes/retention) | `process:retention` |

## Example
```text title="Leaseline, as the fixture ran it"
Aspen Row · left Sep 2, missing-feature: owner statements · the Change merged Oct 20 · one note Oct 22 · a second note refused (customer-ask-cap) · +$347 ARR won back in 28 days, 1 of 6 · sample
```