Retargeting
Shows Ads only to your own past visitors and signups, stops when they convert, and holds a share back to price every dollar.
- Channel
- Ads and sponsorships
- Summary
- startupbuilder.dev
Be our paid media lead: show Ads only to people who visited us or signed up, by how long ago, and stop the moment they start a trial. Hold back a fifth of them from every Ad so we know what it adds. $10 a day. Show me what’s missing; don’t spend.
# Not live yet (gaps #2)
curl -X POST "https://api.sb/leaseline.app/campaigns?mode=try" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"play":"retargeting"}'import { $ } from 'startup-builder'
return await $.Campaign.create({ play: 'retargeting' }, { mode: 'try' }) // dry run on the day-0 Startup, 2026-08-24 · Not live yet, gaps #2
npx startup-builder do Campaign.create --json '{"play":"retargeting"}' --try # Not live yet (gaps #2){
"name": "do",
"arguments": {
"code": "return await $.Campaign.create({ play: 'retargeting' }, { mode: 'try' }) // dry run on the day-0 Startup, 2026-08-24 · Not live yet, gaps #2",
"mode": "try"
}
}application/json{
"type": "https: //startupbuilder.dev/ errors/ refused",
"title": "Refused",
"status": 422,
"detail": "retargeting can’t start yet: 1 check fails and 1 warns.",
"checks": [
{
"check": "use-when",
"status": "pass",
"observed": "1, 840 past Visitors and Contacts in 90 days",
"doc": "/plays/ retargeting#check-use-when",
"fix": "Keep 1, 000 or more own Visitors in 90 days, or run editorial-seo first.",
"then": "Metric.query"
},
…
Hand our latest Visit that ended without a signup to the paid media agent and show me what it chose and why. Don’t spend.
curl "https://api.sb/leaseline.app/events?where=name%20in%20(%22Visit.ended%22%2C%20%22Checkout.abandoned%22%2C%20%22Contact.stalled%22%2C%20%22Registration.missed%22%2C%20%22Watch.replayed%22%2C%20%22Customer.converted%22%2C%20%22Consent.withdrawn%22%2C%20%22Audience.resized%22%2C%20%22Experiment.matured%22)&limit=1" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"
# Not live yet (gaps #7)
curl -X POST "https://api.sb/leaseline.app/triggers/fire?mode=try" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"like":"event_8211"}'
# Not live yet (gaps #7)
curl "https://api.sb/leaseline.app/decisions?where=on%20%3D%20%22visitor_903%22" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"
# Not live yet (gaps #7)
curl "https://api.sb/leaseline.app/actions?where=on%20%3D%20%22visitor_903%22" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"import { $ } from 'startup-builder'
const { events: [last] } = await $.Event.list({ where: 'name in ("Visit.ended", "Checkout.abandoned", "Contact.stalled", "Registration.missed", "Watch.replayed", "Customer.converted", "Consent.withdrawn", "Audience.resized", "Experiment.matured")', limit: 1 }) // { play } compiled to this Play’s Trigger Events
const { subject } = await $.Trigger.fire({ like: last.$id }, { mode: 'try' }) // the same Event again, in try mode · Not live yet, gaps #7
const { decisions: [decision] } = await $.Decision.list({ where: `on = "${subject}"` }) // what it chose, and why · Not live yet, gaps #7
const { actions } = await $.Action.list({ where: `on = "${subject}"` }) // what it did, with its checks · Not live yet, gaps #7
return { decision, actions }
npx startup-builder do Event.list --json '{"where":"name in (\"Visit.ended\", \"Checkout.abandoned\", \"Contact.stalled\", \"Registration.missed\", \"Watch.replayed\", \"Customer.converted\", \"Consent.withdrawn\", \"Audience.resized\", \"Experiment.matured\")","limit":1}'
npx startup-builder do Trigger.fire --json '{"like":"event_8211"}' --try # Not live yet (gaps #7)
npx startup-builder do Decision.list --json '{"where":"on = \"visitor_903\""}' # Not live yet (gaps #7)
npx startup-builder do Action.list --json '{"where":"on = \"visitor_903\""}' # Not live yet (gaps #7){
"name": "do",
"arguments": {
"code": "const { events: [last] } = await $.Event.list({ where: 'name in (\"Visit.ended\", \"Checkout.abandoned\", \"Contact.stalled\", \"Registration.missed\", \"Watch.replayed\", \"Customer.converted\", \"Consent.withdrawn\", \"Audience.resized\", \"Experiment.matured\")', limit: 1 }) // { play } compiled to this Play’s Trigger Events\nconst { subject } = await $.Trigger.fire({ like: last.$id }, { mode: 'try' }) // the same Event again, in try mode · Not live yet, gaps #7\nconst { decisions: [decision] } = await $.Decision.list({ where: `on = \"${subject}\"` }) // what it chose, and why · Not live yet, gaps #7\nconst { actions } = await $.Action.list({ where: `on = \"${subject}\"` }) // what it did, with its checks · Not live yet, gaps #7\nreturn { decision, actions }",
"mode": "try"
}
}application/json{
"decision": {
"$id": "https: //api.sb/ leaseline.app/ decisions/ decision_701",
"$type": "Decision",
"choice": "retargeting-audiences",
"reason": "a Visit that ended at the pricing Page with no signup, from a Visitor with advertising Consent, outside the standing holdout: the 0–7 day window",
"stakes": "low",
"options": [
"retargeting-audiences",
"audience-split-holdout",
"ad-tag-consent-gate",
"retargeting-pacing",
"retargeting-creative"
],
…
sample · 4 Not live yetPreviewOn PATCH /{namespace}/campaigns/{id}Off PATCH /{namespace}/campaigns/{id}Take this one POST /{namespace}/tasks/{task}/claim
action_1 keeps this Visitor out of the 20 % standing holdout. action_2 adds them to the 0–7 day window at the next hourly upload, where the Ads say nothing about the Visit itself.
+6.5 added trials per $100 · ■■□□ Early sign · n 1,
| Try another: the Visit | Choice | Reason |
|---|---|---|
| A Denver Visit to pricing, 2 min, no signup, Consent given | retargeting-audiences | the 0–7 day window |
| A returning Visitor drawn into the standing holdout on their first Visit | audience-split-holdout | held out of every paid Campaign |
| A Visit with a browser opt-out signal | ad-tag-consent-gate | no Venue tag loads |
| Started a trial 3 h after an Ad click | retargeting-pacing | a converter leaves every window at once |
- 35 min week 1, then 10 a week
- Your time
- Day 3: the first trial from a retargeted click
- First signal
Where it sits: Stranger → Lead → Trialing → Activated → At risk → Churned → Won back · Retargeting trial lift → Deals and trials opened → New Customers ↑ → ARR added each week → ARR
Worth’s formula: about +6.5 trials per $100 × $280 a month × 0.15 trials to paid (your last 90 days) × $347 Pro ARR ≈ +$947. Under First signal: About 7 weeks to tell +5 trials per $100 at 1,000 Visitors a month with 20 % held out; at 1,000 in 90 days, about 14 weeks.
sample · api.sb/
Start it this week
npx startup-builder add play retargeting # 1 · writes plays/retargeting.mdx (Not live yet, gaps #5)
# 2 · dry run; below, its 422 on 2026-08-24
curl -X POST "https://api.sb/leaseline.app/campaigns?mode=try" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"play":"retargeting"}'
# 3 · keep it, at You approve each one
curl -X POST https://api.sb/leaseline.app/campaigns \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"play":"retargeting"}'
# on
curl -X PATCH https://api.sb/leaseline.app/campaigns/retargeting \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"endsAt":null}'
# off: every Campaign pauses through the proxy
curl -X PATCH https://api.sb/leaseline.app/campaigns/retargeting \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"endsAt":"now"}' | Check | Fix | Then |
|---|---|---|
use-when pass · Not live yet, gaps #8 | Keep 1,000 or more own Visitors in 90 days, or run editorial-seo first. | POST /{namespace}/metrics/query |
baseline pass · Not live yet, gaps #8 | Read it once; it fills Before in Your number. | POST /{namespace}/metrics/query |
conversion-send fail | Send one test trial from your server; each Venue stays off until it lands. | POST /{namespace}/integrations/{integration}/call |
role-unstaffed warn · Not live yet, gaps #8 | Staff the Marketing Lead, or leave it: approvals and the kill switch come to you. | POST /{namespace}/roles/{role}/staff |
JSON
{
"type": "https://startupbuilder.dev/errors/refused",
"title": "Refused",
"status": 422,
"detail": "retargeting can’t start yet: 1 check fails and 1 warns.",
"checks": [
{
"check": "use-when",
"status": "pass",
"observed": "1,840 past Visitors and Contacts in 90 days",
"doc": "/plays/retargeting#check-use-when",
"fix": "Keep 1,000 or more own Visitors in 90 days, or run editorial-seo first.",
"then": "Metric.query"
},
{
"check": "baseline",
"status": "pass",
"observed": "2.2 trials per 100 own Visitors with no Ads, n 1,610 in 90 days",
"doc": "/plays/retargeting#check-baseline",
"fix": "Read it once; it fills Before in Your number.",
"then": "Metric.query"
},
{
"check": "conversion-send",
"status": "fail",
"observed": "no server-side trial has reached Facebook or Google Display",
"doc": "/plays/retargeting#check-conversion-send",
"fix": "Send one test trial from your server; each Venue stays off until it lands.",
"then": "Integration.call"
},
{
"check": "role-unstaffed",
"status": "warn",
"roles": [
"marketing-lead"
],
"owned": 0,
"doc": "/plays/retargeting#check-role-unstaffed",
"fix": "Staff the Marketing Lead, or leave it: approvals and the kill switch come to you.",
"then": "Role.staff"
}
]
}It says no, says why, and names the fix.
| Check | Fix (one call) | Your time | Wait |
|---|---|---|---|
use-when · baseline · pass: 1,840 own Visitors and Contacts; 2.2 per 100, n 1,610 (gaps #8) | POST /{namespace}/metrics/query on retargeting-trial-lift, which fills Before in Your number | 1 min | none |
conversion-send · fail: no test conversion on either Venue | POST /{namespace}/integrations/{integration}/call sends one test trial to each Venue | 10 min | up to 24 h for each Venue to report it |
role-unstaffed · warn (gaps #8): no Marketing Lead | POST /{namespace}/roles/{role}/staff, or leave it: approvals come to you | 0 min | none |
Your number · Before 2.2 · Now 6.5 added trials per $100 · Target 7 by Dec 31 · ■■□□ Early sign · n 1,
The Ads cost what the Venues bill, capped at $280 a month by your Limit: about $15 for each added trial. Hourly Audience uploads and the daily drift read cost about $0.20 a day (Worker cost). At 1,000 Visitors a month the same cap holds.
When it starts
| When | Trigger id | Starts on | Window | How it gets in |
|---|---|---|---|---|
| A Visit ends with no signup | retargetable-visit | event on Visit.ended where a Visit the Site earned, ended with no conversion, from a Visitor whose tags may fire | the 0–7, 8–30 and 31–90 day windows | your Site’s tag, after the consent gate (POST /{namespace}/visitors/{visitor}/identify) |
| A member starts a trial | conversion-recorded | event on Customer.converted where the Play's conversion Event for a member, which ends retargeting for them | out of every window within 1 h | your server sends the conversion (POST /{namespace}/integrations/{integration}/call) |
| A browser sends an opt-out signal, or Consent is withdrawn | opt-out-signal | event on Consent.withdrawn where a browser opt-out signal or a withdrawn advertising Consent | tags stop at once; lists update at the next upload | the consent gate on every Visit |
+ 4 more for checkout exits, stalled signups, missed events and Audience size (?lanes=) |
{
"$id": "https://api.sb/leaseline.app/events/event_8211" ,
"$type": "Event",
"name": "Visit.ended",
"at": "2026-10-19T13:48:20Z" ,
"seq": 8211,
"data": {
"visitor": "visitor_903",
"lastPage": "/pricing",
"seconds": 128,
"converted": false,
"consent": {
"advertising": true
},
"optOutSignal": false
}
}npx startup-builder trigger Visit.ended --like event_8211 # replays the Visit
npx startup-builder clock advance --by 7d # moves the member from the 0–7 to the 8–30 day window
npx startup-builder listen Visit.ended --forward-to http://localhost:3000/hook Who runs it
Paid media · your agent (role:, worker_7) holds the Key Result. The plan, every new concept and every raise come to you (Dana Okafor, person, worker_1) for approval, because the Marketing Lead Role has no person. It writes to no one; it builds Audiences and Ads. Your number links to Did it work.
| Option | Rung today | Evidence (passed of n) | Moves up at | Drops back when | You can |
|---|---|---|---|---|---|
Add to a window (retargeting-audiences) · Hold out (audience-split-holdout) | It does it | 1,472 · 368 | — | — | POST /{namespace}/actions/{action}/redirect |
No tags (ad-tag-consent-gate) · Remove a converter (retargeting-pacing) | It does it | 214 · 78 | — | a converter still served after 24 h → converter-leak | POST /{namespace}/actions/{action}/redirect |
A new concept (retargeting-creative) · a Limit raise | You approve each one | 9 of 9 · 0 | never: the spend is yours | — | POST /{namespace}/approvals/{approval}/approve · POST /{namespace}/approvals/{approval}/refuse |
| Change the windows or the holdout share | You approve each one | 0 merged | never | — | POST /{namespace}/changes/{change}/merge · POST /{namespace}/changes/{change}/revert |
| Never: an Ad that mentions a Visit, a bought list, an Audience widened to keep spending | Refused, every time | 1 concept refused | never | — | nothing; the Checks hold it |
What it reads · What it may call. It reads your own Visits and Contacts after the consent gate, the Venues’ server-side conversions and spend. No Ad mentions or implies a Visit, a Page or anything else the Person did.
You hold the paid media Key Result: 7 added trials per $100 by 2026-12-31. ← worker, metric
On each Trigger, pick the Tactic that acts and say why. ← triggers, tactics
Your own Visitors and Contacts only; 20 % held out of every Campaign. ← your file, line 2; first-party
Three windows: 0–7, 8–30 and 31–90 days; a converter leaves all three. ← your file, line 4
No Ad mentions a Visit, a Page or a cart. ← copyRules 3
A Price in an Ad equals the Offer’s live Price. ← copyRules 2; price The slow loop. The weekly Decisions kept the windows and the 20 % holdout, and no Change was merged in the window, so there is no replay or read-back to print. One concept retired on day 30 under its window’s median, a Tactic’s own rule.
Bring your own agent (folded) starts at You approve each one for anything that spends; Audience moves it may make at It does it when it matches the seated agent’s converter removal. POST /{namespace}/roles/{role}/staff seats it.
npx startup-builder eval --scenario retargeting-1840 --agent "claude -p" --seat paid-media-manager # seated: +6.5 trials per $100, 0 converters served past 24 h · yours: +5.9, 2 converters served for 30 h What it can do next
| Option | Picked when | Decided by | Runs | Hands to | If it’s just you |
|---|---|---|---|---|---|
retargeting-audiences retargeting-audiences | a Visit ended with no conversion, Consent given, outside the holdout | Your rule (line 4) | PATCH /{namespace}/audiences/{id} into its window at the next hourly upload | nobody | the same |
audience-split-holdout audience-split-holdout | the Visitor was drawn into the 20 % on their first Visit | Your rule (line 2) | POST /{namespace}/flags/{flag}/evaluate: out of every paid Campaign, for good | nobody | the same |
ad-tag-consent-gate ad-tag-consent-gate | no advertising Consent, or an opt-out signal | Your rule (the consent gate) | nothing: no Venue tag loads | nobody | the same |
retargeting-pacing retargeting-pacing | a member converted, or a window passed 10 a week in frequency | Your rule (the pacing line) | PATCH /{namespace}/audiences/{id} removes them; PATCH /{namespace}/ads/{id} holds the window | nobody | the same |
retargeting-creative retargeting-creative | a concept falls under its window’s median for 14 days | Agent’s call, with its reason | PATCH /{namespace}/ads/{id} retires it → POST /{namespace}/ads drafts the next, from approved Pages | you approve the new concept · Take this one | you |
| When they write back | Fixture reply | What happens | Who answers | By when | Call |
|---|---|---|---|---|---|
| Interested | a click to the trial Page from the 0–7 day Ad | the trial Page; a trial ends retargeting for them within 1 h | nobody | 1 h | PATCH /{namespace}/audiences/{id} |
| Send me pricing | a comment: “How much is it?” | the Pricing Page’s list Price, as it stands | Paid media · your agent | 1 h | POST /{namespace}/conversations/{conversation}/answer |
| Not now | a hide on the Ad | that Ad stops for them; they age out at day 90 | nobody | at once | PATCH /{namespace}/audiences/{id} |
| Wrong person | a comment: “I’m a tenant, why am I seeing this?” | a plain answer, and they leave every window | Paid media · your agent | 1 h | POST /{namespace}/conversations/{conversation}/answer |
| Out of office | an auto-reply to a stalled-signup Ad’s lead form | nothing sends by email; the window runs on | nobody | — | POST /{namespace}/messages/{message}/schedule |
| Question it can’t answer | a comment: “Does it work for HOAs?” | To a person; the comment stays up | you | 1 business day | POST /{namespace}/conversations/{conversation}/escalate |
| Stop | an opt-out signal, or “stop following me around” | tags stop at once and they leave every list at the next upload (opt-out) | nobody | at once | POST /{namespace}/consents/{consent}/withdraw |
When it hands to you
This Play hands no subject to a person: the record names no hand-off. What lands in your queue is approvals: the plan, each new concept and any raise, plus a hard comment now and then. As captured (draft and checks are Not live yet, gaps #7).
[
{
"$id": "https://api.sb/leaseline.app/approvals/approval_121" ,
"$type": "Approval",
"verdict": "approved",
"deadline": "2026-08-26T17:00:00Z" ,
"draft": "Plan: Facebook and Google Display, 3 windows, 20 % held out, $10 a day, trial started as the conversion" ,
"route": "retargeting-plan",
"checks": [
{
"check": "conversion-send",
"status": "pass"
},
{
"check": "first-party",
"status": "pass"
}
]
},
{
"$id": "https://api.sb/leaseline.app/tasks/task_744" ,
"$type": "Task",
"assignee": "worker_1",
"due": "2026-09-23T17:00:00Z" ,
"on": "ad_31",
"brief": {
"who": "a commenter on the 8–30 day Ad",
"said": "Does it work for HOAs?" ,
"chose": "retargeting-creative",
"why": "a question on a live concept only you can answer; the comment stays up" ,
"never": [
"hide it"
]
}
}
]You answer with POST /{namespace}/approvals/{approval}/approve or POST /{namespace}/approvals/{approval}/refuse, POST /{namespace}/tasks/{task}/claim (Take this one) or POST /{namespace}/tasks/{task}/return with a reason, each a one-call window. An Approval unanswered by its deadline expires, and nothing new spends. A Task nobody claims by its due time goes up a Role through POST /{namespace}/tasks/{task}/escalate, and person-promise fires.
On time: you, 11 of 11 within 1 business day, median 2 h, n 11, ■□□□ Too early to tell beside Other Startups’ 93 % on time. Your queue: in week 1, 4 approvals (the plan and 3 concepts); then about 1 concept and 1 comment a week.
The fixed line: POST /{namespace}/deals/{deal}/assign sets owner to the receiving person, the agent is never a Deal’s owner, and Meetings land on the owner’s calendar under the owner’s name.
When it won’t send
| Check (refuses one send) | Refuses | Fix | Then |
|---|---|---|---|
conversion-send (the record’s Conversion send first) | a Campaign on a Venue that has not received a test conversion from your server in 24 h | Send one test trial from your server; each Venue stays off until it lands. | POST /{namespace}/integrations/{integration}/call |
opt-out (the record’s Opt-out binds) | a tag or a list upload for a browser with an opt-out signal or no Consent | Nothing to do: tags stop and the member leaves every list. | PATCH /{namespace}/audiences/{id} |
first-party (the record’s First-party only) | a member from a matched, licensed or bought source | Use only your own Visitors and Contacts. | PATCH /{namespace}/audiences/{id} |
scale-gate (the record’s Scale gate) | a Limit raise or a second Venue with no winning holdout Readout | Wait for the holdout read, then approve the raise. | POST /{namespace}/approvals/{approval}/approve |
claims-resolve (hub) | an Ad sentence no approved Page backs, or one that mentions what the Person did | Say what Leaseline does, from an approved Page; never the Visit. | POST /{namespace}/content/draft |
price (gaps #1) | a Price in an Ad that differs from the Offer’s live Price | Quote the Pricing Page’s live Price. | POST /{namespace}/approvals/{approval}/approve |
| Stop (pauses the Play) | Pauses when | Then |
|---|---|---|
conversion-feed | no conversion on a Venue for 72 h while spend flows | changes freeze there; at 7 days the Venue pauses |
consent | any Venue tag request from a test browser with no Consent or with an opt-out signal | every Campaign of the Play pauses |
converter-leak | a converter still in a window 24 h after converting | that Campaign pauses until the exclusion holds |
audience-floor | an Audience under its Venue’s minimum size | its Campaign pauses; it never widens to keep spending |
floor-read | after 30 days and 100 click Visits, retargeted conversion under the Site’s own | the Play pauses |
holdout-loser | a holdout read marks it a loser | the Play pauses until a Decision names a change |
frequency | a window’s 7-day frequency above 10 | that window pauses |
spend-limit | month-to-date spend reaches the Limit | the Play pauses until the month turns (POST /{namespace}/limits/{limit}/raise) |
kill-switch | you press it | every Campaign pauses through the proxy; write scope is revoked |
The captured refusal, then the Stop that fired as a Campaign.paused reason (Not live yet, gaps #2); the concept that ran instead is in Example.
| Check | Fix | Then |
|---|---|---|
claims-resolve fail | Say what Leaseline does, from an approved Page; never the Visit. | POST /{namespace}/content/draft |
price pass · Not live yet, gaps #1 | — | POST /{namespace}/approvals/{approval}/approve |
JSON
{
"type": "https://startupbuilder.dev/errors/refused",
"title": "Refused",
"status": 422,
"detail": "The 0–7 day concept did not run: it says the reader looked at the pricing Page.",
"checks": [
{
"check": "claims-resolve",
"status": "fail",
"doc": "/plays/retargeting#check-claims-resolve",
"fix": "Say what Leaseline does, from an approved Page; never the Visit.",
"then": "Content.draft"
},
{
"check": "price",
"status": "pass"
}
]
}It says no, says why, and names the fix.
[
{
"reason": {
"limit": "converter-leak",
"observed": "6 converters still served at 26 h",
"bound": "24h",
"window": "Google Display, 0–7 days"
},
"doc": "/plays/retargeting#stop-converter-leak" ,
"task": "task_731"
}
]Did it work
A standing 20 % of your own Visitors and Contacts was held out of every Campaign at random on their first Visit (rules m–o); each figure’s call is POST /{namespace}/metrics/query by node and by arm, and every node above the leaf is Not live yet (gaps #4).
+6.5 added trials per $100 · ■■□□ Early sign · n 1,
- Chose
- retargeting-audiences
- Why
- a Visit that ended at the pricing Page with no signup, from a Visitor with advertising Consent, outside the standing holdout: the 0–7 day window
- From
- retargeting-audiences · audience-split-holdout · ad-tag-consent-gate · retargeting-pacing · retargeting-creative
- Expects
- a trial started within 30d, or the member ages out at day 90
- Reopens when
- Customer.converted for visitor_903, or 90d since the last Visit
- By
- worker_7
JSON
{
"$id": "decisions/decision_701",
"$type": "Decision",
"choice": "retargeting-audiences",
"reason": "a Visit that ended at the pricing Page with no signup, from a Visitor with advertising Consent, outside the standing holdout: the 0–7 day window",
"stakes": "low",
"options": [
"retargeting-audiences",
"audience-split-holdout",
"ad-tag-consent-gate",
"retargeting-pacing",
"retargeting-creative"
],
"by": "worker_7",
"on": "visitor_903",
"expected": "a trial started within 30d, or the member ages out at day 90",
"reopensWhen": "Customer.converted for visitor_903, or 90d since the last Visit"
}| Node | Agent half (served, 80 %) | Your half (held out, 20 %) | Added | Confidence | Basis |
|---|---|---|---|---|---|
retargeting-trial-lift | 66 trials of 1,472 (4.5 per 100) on $520 | 8 of 368 (2.2 per 100) | +6.5 trials per $100 [1.7, 11.4] | ■■□□ Early sign | held back at random · n 1,840 · 212 still in their 30-day window |
opened-deals-and-trials | 5.3 per 100 (78 of 1,472) | 3.3 (12 of 368) | +2.0 per 100 [−0.1, 4.2] · Not carrying up yet | ■□□□ Too early to tell | the same assignment |
new-arr ↑ | $3,470, 10 Customers on Pro · Booked: credited, not all caused by this Play | $347, 1 Customer (a quarter the size) | +$2,082 [−$1,379, +$5,543] · Worth about +$950 a month | ■□□□ Too early to tell (10 vs 1 Customers) | the same · 14 trials still deciding |
net-new-arr · arr | +$3,470 | +$347 | +$2,082, carried: it moves no other bridge term | ■□□□ Too early to tell | carried from new-arr |
The leaf counts trials started within 30 days of a member’s last Visit, served against held out, per $100 of spend, over everyone assigned once the consent gate ran (rule m), to 300 matured in the held-out 20 %; at 368 assigned, 156 have matured, so the Experiment runs on.
How it moves it: served members saw a concept from approved Pages while still inside their 0–30 day window, at a 7-day frequency of 4.1, and none after converting past the first 24 h.
Guardrails: served against held out: paid per trial, 0.13 against 0.08 (new-paying-customers per trial), and refunds on retargeted trials, 0 of 10 against 0 of 1.
{
"$id": "https://api.sb/leaseline.app/campaigns/retargeting" ,
"$type": "Campaign",
"play": "play:retargeting@1" ,
"path": [
"retargeting-trial-lift",
"opened-deals-and-trials",
"new-arr",
"net-new-arr",
"arr"
],
"experiment": {
"metric": "retargeting-trial-lift",
"unit": "ratio",
"heldBack": 368,
"decision": null
},
"readout": {
"value": 6.5,
"n": 1840,
"maturing": 212,
"interval": [
1.7,
11.4
],
"readability": "C",
"basis": "held-back",
"pooled": false
},
"ledger": {
"spend": 520,
"workerCost": 11,
"hours": 1.8,
"pipelineArr": 0,
"wonArr": 3470,
"projectedArr": 947,
"paybackDays": 55
}
}Outside benchmark: this Play’s catalog record carries no sourced range yet, so Other Startups’ results stand in: +4.8 added trials per $100, ■■■□ Likely, n 22,400 Visitors.
Before it · After it. Before it: programmatic-seo, editorial-seo and promote-winners bring the Visitors. After it: product-led and instant-response take the trials and forms it brings back, and every converter leaves its windows.
Example
Leaseline ran it from 2026-08-24 to 2026-10-19 (56 days) on Facebook and Google Display at $10 a day: 2,054 Visitors and Contacts entered, 214 had no tags (no Consent or an opt-out signal), and 1,840 were assigned, 1,472 served and 368 held out. Read every captured Ad and comment at ?threads=; three follow the tables.
| What happened, by Tactic | Decisions | People | Trials (counts) |
|---|---|---|---|
| Add to a window | 1,472 | 0–7: 611 · 8–30: 538 · 31–90: 323 | 41 · 19 · 6 |
| Hold out · No tags · Remove a converter | 368 · 214 · 78 | — | 8 in the held-out 20 % |
| A new concept | 9 drafted, 1 refused, 8 ran | 1 retired on day 30 | — |
| Served · held out | 1,472 · 368 | $520 · $0 | 66 · 8 |
| At 1,000 Visitors a month (the Use it when volume) | about 1,870 in 56 days | held back at random, 20 % | Other Startups’ results: +4.8 per $100, ■■■□ Likely, n 22,400 |
| Day | Event | What happened | Your time |
|---|---|---|---|
| 0 | event_8002 Campaign.created | the three steps; the dry run fails conversion-send | 15 min |
| 1 | event_8009 Integration.called | one test trial reached both Venues from your server | 10 min |
| 2 | event_8014 Approval.approved | you approved the plan and 3 concepts | 9 min |
| 3 | event_8031 Offer.trialed | the first trial from a retargeted click | — |
| 19 | event_8102 Campaign.paused | converter-leak on Google Display: 6 converters at 26 h; the exclusion fixed in 2 h | 6 min |
| 30 | event_8140 Ad.updated | the 31–90 day concept retired under its window’s median; you approved the next | 4 min |
| 1–56 | event_8012 Approval.created, first of 10 | you approved 9 concepts; claims-resolve refused 1, rewritten once and run; weekly reviews | 1 h 16 min · total 1.8 h measured, 1.7 h asked |
The 0–7 day Ad, Facebook, Sep 3 · Every renewal notice, 60 days out, for every owner you manage. Try Leaseline free for 14 days.¹
A Denver manager, served, Sep 4 · clicked, started a trial, and left every window within the hour. Paid for Pro on Sep 18: +$347 ARR, credited to the Ad’s coded link.²
- Still looking at our pricing page? Your renewals won’t wait.
+ Every renewal notice, 60 days out, for every owner you manage. Try Leaseline free for 14 days. A commenter on the 8–30 day Ad, 2026-09-22 16:40 · “Does it work for HOAs?” · Leaseline, 16:42 · *Thanks for asking. Dana, our founder, answers this one herself, here, by tomorrow 17:00.*³ The comment stays up; the Brief went to you (
task_744), and you replied at 09:15.
¹ What Leaseline does, from an approved Page, and nothing about the Visit. ² Credit by the coded link; the conversion came from your server, never the Venue’s own count. ³ The routed line, compiled from the Role’s Settings.
Your settings
---
holdout: 0.2 # a fifth of your own Visitors and Contacts never see an Ad
budget: $10/day
---
Windows: 0–7, 8–30 and 31–90 days since the last Visit. A trial started ends it. $ npx startup-builder show plays/retargeting
holdout 0.2 · budget $10/day · windows 0–7, 8–30, 31–90 · trial ends it your file plays/retargeting.mdx:2,3,5 (Ask 2026-08-24)
worker role:paid-media-manager, owns never · chooses none (D38): the Decision picks a Tactic · approval every-send · checks conversion-send, opt-out, first-party, scale-gate, claims-resolve, price · stops 9 · placements facebook, instagram, google-display, youtube, linkedin catalog play:retargeting@1 (Not live yet, gaps #5) Every key from the catalog
Compiled, not written: edit the Play record retargeting and its Leaseline run, 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.