URL: /plays/retargeting · Id: `play:retargeting`

# Retargeting

> Shows Ads only to your own past visitors and signups, stops when they convert, and holds a share back to price every dollar.

Shows [Ads](https://startupbuilder.dev/business/nouns/ad) only to your own past visitors and signups, stops when they convert, and holds a share back to price every dollar.

```text title="Set it up · Ask"
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.
```

```text title="Run one · Ask"
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.
```

```ts title="Run one · SDK"
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 }
```

```json title="Run one · Response · sample"
{
  "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"
    ],
    "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"
  },
  "actions": [
    {
      "$id": "https://api.sb/leaseline.app/actions/action_1",
      "$type": "Action",
      "status": "done",
      "mode": "try",
      "by": "worker_7",
      "verb": "Flag.evaluate",
      "checks": []
    },
    {
      "$id": "https://api.sb/leaseline.app/actions/action_2",
      "$type": "Action",
      "status": "pending",
      "reason": "awaitingTime",
      "mode": "try",
      "by": "worker_7",
      "verb": "Audience.update",
      "checks": [
        {
          "check": "opt-out",
          "status": "pass"
        },
        {
          "check": "first-party",
          "status": "pass"
        },
        {
          "check": "conversion-send",
          "status": "pass"
        }
      ]
    }
  ]
}
```

`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,840 · path → arr`

| 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 |

| Worth | Your time | Spend | First signal | Use it when | Instead |
| --- | --- | --- | --- | --- | --- |
| ≈ +$950 ARR a month at $280 | 35 min week 1, then 10 a week | $15 per added trial, ~$280 a month | Day 3: the first trial from a retargeted click | 1,000+ own Visitors in 90 days, conversions sent | Under 1,000: `editorial-seo` · no server conversion: send one (<Op verb="Integration.call" />) |

**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/leaseline.app · 41 Customers at 2026-10-19 · on Visit.ended · first Audience member added 3 min after the Visit · 8 keys Not live yet`

## Start it this week
```bash
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"}'
```


```json title="The dry run’s 422 · 2026-08-24 · sample"
{
  "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"
    }
  ]
}
```

| Check | Fix (one call) | Your time | Wait |
| --- | --- | --- | --- |
| <a id="check-use-when"></a>`use-when` · <a id="check-baseline"></a>`baseline` · pass: 1,840 own Visitors and Contacts; 2.2 per 100, n 1,610 (gaps #8) | <Op verb="Metric.query" /> on `retargeting-trial-lift`, which fills Before in Your number | 1 min | none |
| <a id="check-conversion-send"></a>`conversion-send` · fail: no test conversion on either Venue | <Op verb="Integration.call" /> sends one test trial to each Venue | 10 min | up to 24 h for each Venue to report it |
| <a id="check-role-unstaffed"></a>`role-unstaffed` · warn (gaps #8): no Marketing Lead | <Op verb="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,840`

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 (<Op verb="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 (<Op verb="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=all`) | | | | |

```json title="One signed delivery · StartupBuilder-Signature"
{
  "$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
  }
}
```

```bash title="Reproduce it in test"
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:paid-media-manager`, `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 | — | — | <Op verb="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` | <Op verb="Action.redirect" /> |
| A new concept (`retargeting-creative`) · a Limit raise | You approve each one | 9 of 9 · 0 | never: the spend is yours | — | <Op verb="Approval.approve" /> · <Op verb="Approval.refuse" /> |
| Change the windows or the holdout share | You approve each one | 0 merged | never | — | <Op verb="Change.merge" /> · <Op verb="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.

```md title="The brief (folded): compiled Agent-register Markdown · play:retargeting@1 · each line names its record key"
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. <Op verb="Role.staff" /> seats it.

```bash
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) | <Op verb="Audience.update" /> 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) | <Op verb="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) | <Op verb="Audience.update" /> removes them; <Op verb="Ad.update" /> 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 | <Op verb="Ad.update" /> retires it → <Op verb="Ad.create" /> 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 | <Op verb="Audience.update" /> |
| Send me pricing | a comment: “How much is it?” | the Pricing Page’s list Price, as it stands | Paid media · your agent | 1 h | <Op verb="Conversation.answer" /> |
| Not now | a hide on the Ad | that Ad stops for them; they age out at day 90 | nobody | at once | <Op verb="Audience.update" /> |
| 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 | <Op verb="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 | — | <Op verb="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 | <Op verb="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 | <Op verb="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).

```json title="What lands in each queue · sample"
[
  {
    "$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 <Op verb="Approval.approve" /> or <Op verb="Approval.refuse" />, <Op verb="Task.claim" /> (Take this one) or <Op verb="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 <Op verb="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:** <Op verb="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. | <Op verb="Integration.call" /> |
| <a id="check-opt-out"></a>`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. | <Op verb="Audience.update" /> |
| <a id="check-first-party"></a>`first-party` (the record’s First-party only) | a member from a matched, licensed or bought source | Use only your own Visitors and Contacts. | <Op verb="Audience.update" /> |
| <a id="check-scale-gate"></a>`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. | <Op verb="Approval.approve" /> |
| <a id="check-claims-resolve"></a>`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. | <Op verb="Content.draft" /> |
| <a id="check-price"></a>`price` (gaps #1) | a Price in an Ad that differs from the Offer’s live Price | Quote the Pricing Page’s live Price. | <Op verb="Approval.approve" /> |

| Stop (pauses the Play) | Pauses when | Then |
| --- | --- | --- |
| <a id="stop-conversion-feed"></a>`conversion-feed` | no conversion on a Venue for 72 h while spend flows | changes freeze there; at 7 days the Venue pauses |
| <a id="stop-consent"></a>`consent` | any Venue tag request from a test browser with no Consent or with an opt-out signal | every Campaign of the Play pauses |
| <a id="stop-converter-leak"></a>`converter-leak` | a converter still in a window 24 h after converting | that Campaign pauses until the exclusion holds |
| <a id="stop-audience-floor"></a>`audience-floor` | an Audience under its Venue’s minimum size | its Campaign pauses; it never widens to keep spending |
| <a id="stop-floor-read"></a>`floor-read` | after 30 days and 100 click Visits, retargeted conversion under the Site’s own | the Play pauses |
| <a id="stop-holdout-loser"></a>`holdout-loser` | a holdout read marks it a loser | the Play pauses until a Decision names a change |
| <a id="stop-frequency"></a>`frequency` | a window’s 7-day frequency above 10 | that window pauses |
| <a id="stop-spend-limit"></a>`spend-limit` | month-to-date spend reaches the Limit | the Play pauses until the month turns (<Op verb="Limit.raise" />) |
| <a id="stop-kill-switch"></a>`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.

```json title="The captured refusal · sample"
{
  "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"
    }
  ]
}
```


```json title="The Stops that fired, as Campaign.paused reasons · sample"
[
  {
    "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 <Op verb="Metric.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,840 · path → arr`

| 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.

```json title="The Campaign card · sample"
{
  "$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=all`; 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.²

```diff title="The 0–7 day concept, 2026-08-26 · the draft claims-resolve refused, then the concept that ran"
- 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
```mdx title="plays/retargeting.mdx"
---
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.
```

```text
$ 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
| Key | Id | What it is |
| --- | --- | --- |
| Tactic | <a id="tactic-ad-tag-consent-gate"></a>`ad-tag-consent-gate` | Ad tag consent gate decides on every Visit which Venue tags load, by the Visitor’s Market, Consent and opt-out signal, before any tag fires. Starts at Sends nothing. |
| Tactic | <a id="tactic-retargeting-plan"></a>`retargeting-plan` | Retargeting plan fixes the conversion Event, tests each Venue’s conversion send, and queues Venues, member rule, exclusions and budgets for one approval. Starts at You approve each one. |
| Tactic | <a id="tactic-retargeting-audiences"></a>`retargeting-audiences` | Retargeting audiences builds three exclusive window Audiences per Venue and first-party Contact lists, and removes converters, opt-outs and aged-out members. Starts at Sends nothing. |
| Tactic | <a id="tactic-retargeting-creative"></a>`retargeting-creative` | Retargeting creative drafts three concepts per window from the claims source, screens them, and retires a concept that falls under its window’s median. Starts at You approve each one. |
| Tactic | <a id="tactic-retargeting-pacing"></a>`retargeting-pacing` | Retargeting pacing holds spend and frequency inside each window and pauses any Campaign that keeps serving a converter. Starts at Sends nothing. |
| Tactic | <a id="tactic-audience-split-holdout"></a>`audience-split-holdout` | Audience split holdout keeps a standing share of the Startup’s own Visitors and Contacts out of every paid Campaign, and prices the lift per $100. Starts at You approve each one. |
| Tactic | <a id="tactic-retargeting-floor"></a>`retargeting-floor` | Retargeting floor pauses the Play when Visits from retargeting clicks convert below the Site’s own rate after 30 days. Starts at Sends nothing. |
| Tactic | <a id="tactic-ad-write-proxy"></a>`ad-write-proxy` | Ad write proxy is the only write path to an ads Venue, holding the token outside the model and forcing every create to paused. Starts at You approve each one. |
| Tactic | <a id="tactic-ad-drift-monitor"></a>`ad-drift-monitor` | Ad drift monitor diffs every live Ad’s rendered creative, text and enhancement settings against the approved source daily, and pauses or resets what drifted. Starts at Sends nothing. |
| Tactic | <a id="tactic-incrementality-holdout"></a>`incrementality-holdout` | Incrementality holdout measures the first Transactions a paid arm adds, by a geo holdout or a day switchback, before any scale step. Starts at You approve each one. |
| Trigger | <a id="trigger-retargetable-visit"></a>`retargetable-visit` | Retargetable visit: `event` on `Visit.ended`, window 90d. |
| Trigger | <a id="trigger-checkout-exit"></a>`checkout-exit` | Checkout exit: `state` on `Checkout.abandoned`, window 7d. |
| Trigger | <a id="trigger-stalled-signup"></a>`stalled-signup` | Stalled signup: `state` on `Contact.stalled`, window 30d. |
| Trigger | <a id="trigger-event-no-show"></a>`event-no-show` | Event no-show: `state` on `Registration.missed`, window 30d. |
| Trigger | <a id="trigger-replay-watched"></a>`replay-watched` | Replay watched: `threshold` on `Watch.replayed`, window 14d. |
| Trigger | <a id="trigger-conversion-recorded"></a>`conversion-recorded` | Conversion recorded: `event` on `Customer.converted`, window 1d. |
| Trigger | <a id="trigger-opt-out-signal"></a>`opt-out-signal` | Opt-out signal: `event` on `Consent.withdrawn`, window 1d. |
| Trigger | <a id="trigger-audience-floor-crossed"></a>`audience-floor-crossed` | Audience floor crossed: `threshold` on `Audience.resized`, window 7d. |
| Trigger | <a id="trigger-holdout-closed"></a>`holdout-closed` | Holdout closed: `event` on `Experiment.matured`, window 7d. |