Holdouts and uplift measurement
A recipient who converts might have converted anyway. Every other campaign metric — delivery, opens, clicks, replies — answers “how did the message perform?”; none of them answer “did the campaign cause anything.” Orbit answers two distinct causal questions with two distinct randomized holdout cohorts, each compared through the same lift engine:- “Did sending lift conversion?” — the campaign-wide holdout. You set a percentage of the eligible audience that receives nothing, then compare the senders’ conversion against that do-nothing control.
- “Did optimizing WHEN we send lift conversion?” — the smart-send holdout, available on campaigns running recipient-optimal send-time optimization (
smart_send). You hold a percentage of contacts out of the time optimization; they receive the campaign immediately in natural batch order while the rest receive their predicted best-time window. Comparing the two isolates the value of the scheduling optimization above the campaign’s baseline effect.
Enable a holdout
Safety defaults are opt-out. Both holdout percentages read unset,
0, or a malformed value as no holdout — nobody is held out and the lift endpoint reports “no holdout configured.” A holdout only carves out a cohort when you explicitly set a positive percentage. Values above 50 are clamped so a bad value can never invert which arm is the control.Campaign-wide holdout — did sending lift conversion?
Setvariables.campaign_holdout_percent (a number, 1–50) on the campaign before launch:
GET /campaigns/:id/holdout-lift.
Smart-send holdout — did optimizing WHEN we send lift conversion?
Prerequisite: the campaign is on recipient-optimal scheduling (smart_send = true — either that flag or send_time_optimization = "recipient-optimal"). On an eligible campaign, opt in with variables.smart_send_holdout_pct (a number, 1–50) before launch:
How assignment stays random
Cohorts are split by deterministic hashing, not by sampling at send time — the same contact lands in the same arm on every pass, across every pause/resume and audience re-resolution. A contact is hashed with SHA-256 into a bucket from 0 to 99; a bucket below the configured percentage is held out. Determinism also makes the audit replayable: given the campaign id and a contact id, you can re-derive the assignment exactly. Each experiment family hashes on its own seed prefix, so a contact’s smart-send-holdout bucket is uncorrelated with their campaign-wide holdout bucket, their A/B variant bucket, and their send-time-experiment bucket — one experiment’s assignment can never bias another’s. The campaign-wide holdout uses thecampaign-… family; the smart-send holdout uses the smart-send-holdout:… family.
Read lift: the two endpoints
Both endpoints return the same lift shape: a treatment arm and a control arm with counts and conversion rates, the absolute lift (treatment rate minus control rate, in percentage points), the relative lift, and a Wald confidence interval with a two-sided z-test —z_score, p_value, and an is_significant flag that is true when the interval excludes zero. A “winning” variant proves correlation; a treatment-vs-control lift whose confidence interval excludes zero proves the send caused incremental conversions. For full request/response schemas, see the campaigns API reference.
GET /campaigns/:id/holdout-lift— did sending lift conversion? The card reportshas_holdout: falseandtracking_method: "no_holdout"on an un-configured campaign;counts_onlywhen the control cohort was carved out but per-arm conversions haven’t been joined yet; and a full lift + confidence interval once both arms’ conversions are known.GET /campaigns/:id/smart-send-holdout-lift— did optimizing when we send lift conversion? On a campaign not using recipient-optimal scheduling (or with no smart-send holdout set) the card reportsconfigured: falseandtracking_method: "no_holdout"— a benign read state, not an error.
Outcome metrics on the smart-send card
Click lift is silent for channels with no tracked link (plain SMS, most WhatsApp sends). The smart-send endpoint therefore also returns ametrics object scored against the same treatment/control partition for three more outcomes, each with its own lift card and confidence interval:
delivered— the contact’s message reached a terminal delivered status.read— a WhatsApp/RCS read receipt, or the email open-pixel stamp (the same predicate the deliverability dashboard’s “Read Rate” card uses).replied— an inbound message arrived on the contact’s conversation attributed to this campaign.
A worked workflow
- Set both flags before launch. Say, a 10% campaign-wide holdout plus a 10% smart-send holdout on a recipient-optimal campaign. Launch.
- Read the campaign-wide lift.
GET /campaigns/:id/holdout-lift— until itsis_significantreads true, treat the campaign’s effect as unproven vs. doing nothing. - Read the optimization lift.
GET /campaigns/:id/smart-send-holdout-lift— checklift(clicks) and each card undermetrics(delivered/read/replied). - Decide whether the optimization earns its keep. When the lift’s confidence interval spans zero, the optimization is not demonstrably moving conversion at your current volume — either widen the audience, accept the un-optimized schedule, or re-run on a larger send. When the interval clears zero, keep the optimizer enabled and read the same card on future sends to confirm the effect holds.