URL: /revenue/cancel-and-save · Id: `page:/revenue/cancel-and-save`

# When someone cancels: the cancel, then one save offer

> When someone cancels: the cancel runs at once, one save offer follows, and no agent can hold it.

A cancel runs the moment the [Customer](https://startupbuilder.dev/business/nouns/customer) asks. One save offer follows while access lasts, and no agent can hold the cancel.

```text title="Ask"
Tidewater Homes clicked cancel. Let it stand, then make one save offer. Don’t send.
```

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

await $.Subscription.unsubscribe({ subscription: 'subscription_15', reason: 'too-expensive', comment: 'We only manage 12 units now.' }, { mode: 'try' })   // 200: it ends Nov 14, at the close of the period, and access lasts until then
await $.Subscription.keep({ subscription: 'subscription_15', offer: 'offer_5' }, { mode: 'try' })   // the one save offer: the $19 Starter plan for 12 units, theirs to press
await $.Action.redirect({ action: 'action_702', changes: { effectiveAt: '2026-12-14' } }, { mode: 'try' })   // an agent tries to hold the cancel a month
// 422 · The cancel for Tidewater Homes was not held: a cancel runs on its date.
// exit-never-waits: Leave the cancel standing; the Customer takes it back with one press while access lasts. · then Subscription.keep
return await $.Metric.query({ metric: 'save-rate', shape: 'trend', window: '28d' })
```

```json title="Response · sample"
{
  "metric": {
    "$id": "https://api.sb/leaseline.app/metrics/save-rate",
    "$type": "Metric",
    "name": "Save rate"
  },
  "rows": [
    {
      "week": "2026-10-19",
      "value": 0.33,
      "saved": 1,
      "cancels": 3
    }
  ]
}
```

`1 of 3 cancels saved in 28 days · ■□□□ Too early to tell · n 3`

`Metric.query({"metric":"save-rate","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 |
| --- | --- | --- | --- | --- |
| <Op verb="Subscription.unsubscribe" /> ends the Subscription at the close of its period and returns 200; one <Op verb="Subscription.keep" /> offer follows, the Customer’s own press. | $0 per cancel; $0.04 per save note (Worker cost) | [Save rate](https://startupbuilder.dev/agents/customer#save-rate), [Lost Customers](/revenue/churned-arr) | [CSM](/capabilities/how-your-agents-act#csm) | every call 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: save offers come to you to approve | Staff the CSM, or leave it: each save offer comes to you. | <Op verb="Role.staff" /> | 0 min | none |

## The records
```json title="Tidewater Homes, after the cancel"
{
  "$id": "https://api.sb/leaseline.app/subscriptions/subscription_15",
  "$type": "Subscription",
  "status": "active",
  "renewsAt": null,
  "termEndsAt": "2026-11-14",
  "quantity": 1
}
```

## Calls
| Call | Here | Emits | Status |
| --- | --- | --- | --- |
| <Op verb="Subscription.unsubscribe" /> | 200: it ends Nov 14, at the close of the period, and access lasts until then | `Subscription.unsubscribed` | live |
| <Op verb="Subscription.keep" /> | the one save offer: the $19 Starter plan for 12 units, theirs to press | `Subscription.kept` | live |
| <Op verb="Action.redirect" /> | an agent tries to hold the cancel a month | `Action.redirected` | live |
| <Op verb="Metric.query" /> |  | `Metric.queried` | live |

## Events
| Event | When it fires | What hears it |
| --- | --- | --- |
| `Subscription.unsubscribed` | the Customer cancels, at once | the save step of `save-and-win-back`, and the reason to `customer-research` |
| `Subscription.kept` | the Customer presses keep before Nov 14 | the CSM’s readout; nothing is booked as lost |

## Refuses when

```json title="The 422 · sample"
{
  "type": "https://startupbuilder.dev/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "The cancel for Tidewater Homes was not held: a cancel runs on its date.",
  "checks": [
    {
      "check": "exit-never-waits",
      "status": "fail",
      "doc": "/code/refusals#check-exit-never-waits",
      "fix": "Leave the cancel standing; the Customer takes it back with one press while access lasts.",
      "then": "Subscription.keep"
    }
  ]
}
```

| Check | Refuses | Fix | Then |
| --- | --- | --- | --- |
| [`exit-never-waits`](/code/refusals#check-exit-never-waits) (Not live yet, gaps #3) | any agent Action that holds, delays or conditions a Customer’s cancel or downgrade | Leave the cancel standing; the Customer takes it back with one press while access lasts. | <Op verb="Subscription.keep" /> |
| [`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 |
| --- | --- | --- |
| [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 |
| `save-at-cancel` (Not live yet, gaps #4) | The Play that offers one save after a Customer cancels, with the cancel already done. | today: The hub Workflow cancel-subscription, then one <Op verb="Subscription.keep" /> offer. |

## Hub guides it deepens
| On startupbuilder.dev | Id |
| --- | --- |
| [Cancel a subscription](https://startupbuilder.dev/workflows/processes/plan-changes/cancel-subscription) | `workflow:cancel-subscription` |
| [Save and win back](https://startupbuilder.dev/workflows/processes/retention/save-and-win-back) | `workflow:save-and-win-back` |

## Example
```text title="Leaseline, as the fixture ran it"
Tidewater Homes · cancel on Oct 18, reason too-expensive, “We only manage 12 units now.” · 200, ends Nov 14 · one save offer: the $19 Starter plan · an agent’s hold on the cancel refused (exit-never-waits) · 1 of 3 cancels saved in 28 days · sample
```