Skip to content

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 who left gets one note after the reason they gave is fixed, and never before.

Write once, after the fixMode try
You, to your agent

Owner statements shipped. Write once to the Customers who left over it. Don’t send.

curl "https://api.sb/leaseline.app/events?where=name%20%3D%20%22Subscription.unsubscribed%22&limit=10" \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"

curl -X POST "https://api.sb/leaseline.app/messages/message_88/schedule?mode=try" \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"at":"2026-10-22T14:00:00Z"}'

curl -X POST "https://api.sb/leaseline.app/messages/message_89/schedule?mode=try" \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"at":"2026-10-29T14:00:00Z"}'

# Not live yet (gaps #2)
curl -X POST "https://api.sb/leaseline.app/subscriptions/subscription_4/reactivate?mode=try" \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"

# Not live yet (gaps #4)
curl -X POST https://api.sb/leaseline.app/metrics/query \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"metric":"reactivated-arr","shape":"trend","window":"28d"}'
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
npx startup-builder do Event.list --json '{"where":"name = \"Subscription.unsubscribed\"","limit":10}'
npx startup-builder do Message.schedule --json '{"message":"message_88","at":"2026-10-22T14:00:00Z"}' --try
npx startup-builder do Message.schedule --json '{"message":"message_89","at":"2026-10-29T14:00:00Z"}' --try
npx startup-builder do Subscription.reactivate --json '{"subscription":"subscription_4"}' --try   # Not live yet (gaps #2)
npx startup-builder do Metric.query --json '{"metric":"reactivated-arr","shape":"trend","window":"28d"}'   # Not live yet (gaps #4)
{
  "name": "do",
  "arguments": {
    "code": "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\nawait $.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\nawait $.Message.schedule({ message: 'message_89', at: '2026-10-29T14:00:00Z' }, { mode: 'try' })   // a second note a week later\n// 422 · The second note to Aspen Row did not send: the win-back note of Oct 22 is still open.\n// customer-ask-cap: Wait for the open ask to close; Decision.list names the Play that holds them. · then Decision.list\nawait $.Subscription.reactivate({ subscription: 'subscription_4' }, { mode: 'try' })   // Not live yet, gaps #2\nreturn await $.Metric.query({ metric: 'reactivated-arr', shape: 'trend', window: '28d' })   // Not live yet, gaps #4",
    "mode": "try"
  }
}
Response200application/json
{
  "metric": {
    "$id": "https://api.sb/leaseline.app/metrics/reactivated-arr",
    "$type": "Metric",
    "name": "Reactivated ARR"
  },
  "rows": [
    {
      "window": "28d",
      "value": 347,
      "customers": 1
    }
  ]
}
sample · 2 Not live yetPreview

+$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 doesPrice per unitMovesWho does itToday
The win-back step of the save-and-win-back Workflow schedules one POST /{namespace}/messages/{message}/schedule per former Customer once a Change fixes the reason they gave.$0.04 per note (Worker cost)Won backHead of customers3 of 5 calls live

Set it up

Check (dry run on a 0-Customer Startup, gaps #8)FixThenYour timeWait
baseline (Not live yet, gaps #8) · warn: 1 of 6 who left came back in 90 daysn 6: it reads before-after, not proof.POST /{namespace}/metrics/query0 minnone
role-unstaffed (Not live yet, gaps #8) · warn: Head of customers has no person: each note comes to youStaff Head of customers, or leave it: each note comes to you.POST /{namespace}/roles/{role}/staff0 minnone

The records

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

CallHereEmitsStatus
GET /{namespace}/events2 of the 6 who left in 90 days gave missing-feature: owner statements—live
POST /{namespace}/messages/{message}/schedulethe one note to Aspen Row, after the Change merged on Oct 20Message.scheduledlive
POST /{namespace}/messages/{message}/schedulea second note a week laterMessage.scheduledlive
Subscription.reactivate (Not live yet, gaps #2)—Not live yet, gaps #2
POST /{namespace}/metrics/queryMetric.queriedNot live yet, gaps #4

Events

EventWhen it firesWhat hears it
Change.mergedthe fix ships: owner statements, Oct 20the win-back step, for each former Customer whose reason it answers
Subscription.unsubscribedthey left, with a reason codethe list the win-back step reads

Refuses when

422RefusedThe second note to Aspen Row did not send: the win-back note of Oct 22 is still open.
CheckFixThen
customer-ask-cap fail · Not live yet, gaps #3Wait for the open ask to close; Decision.list names the Play that holds them.GET /{namespace}/decisions
JSON
{
  "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"
    }
  ]
}

It says no, says why, and names the fix.

CheckRefusesFixThen
customer-ask-cap (Not live yet, gaps #3)a second open ask to one Customer in a window, from any PlayWait for the open ask to close; GET /{namespace}/decisions names the Play that holds them.GET /{namespace}/decisions

Plays that use it

PlayWhat it doesUse it when
Objection watchRecords 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 POST /{namespace}/messages/{message}/schedule.

Hub guides it deepens

On startupbuilder.devId
Save and win backworkflow:save-and-win-back
Retentionprocess:retention

Example

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

Compiled, not written: edit its page, then regenerate. Every step runs from a sentence or a line of code. In your words: the CRM words you know, and where each lives here.

Contents