Uplift, Not Propensity: Causal Targeting for the Post-Cookie Web

You are probably paying your best customers to do exactly what they were going to do anyway. Propensity scoring — targeting whoever is most likely to buy — is the martech default, and it is quietly wrong. This is a field guide to uplift modeling: finding the people who convert only because you nudged them, demonstrated with a runnable simulation that keeps working even after third-party cookies are gone.

Murad Madi
Written by Murad Madi
Published
Time 6 MIN READ

Here is an uncomfortable question for anyone who runs paid acquisition, lifecycle campaigns, or a discount budget: how much of that budget goes to people who were always going to convert?

If you target by propensity — a lead score, a “likely to purchase” audience, a lookalike of past buyers — the honest answer is most of it. Propensity ranks people by how likely they are to buy. The people most likely to buy are, almost by definition, the people who would have bought without the coupon, the retargeting, or the reminder. You are handing margin to sure things and calling it performance.

The better question isn’t “who will buy?” It’s “who buys because we acted?” That’s uplift — also called incrementality or true-lift — and it’s the difference between spending a marketing budget and setting fire to a portion of it.

At the same time, the ground under all of this is shifting. Third-party cookies are going away, and the replacement — Google’s Privacy Sandbox — only lets data leave the browser as noisy aggregates, never per-user rows. Almost every advanced targeting method assumes exactly the granular, per-person data that privacy now forbids.

So I built a small, runnable simulation — FCUE, a Federated Causal Uplift Engine — to answer one question end-to-end: can you still learn a causal targeting policy from only the aggregate, differentially-private signals the post-cookie web allows? The charts below all come out of that simulation. Short answer: yes — and the privacy cost is a graceful, measurable degradation, not a collapse.

The four customers every campaign is really talking to

Every incentive you send lands on one of four kinds of people. This 2×2 is the whole of uplift modeling, and once you see it you can’t unsee it in your own reporting.

The uplift quadrants

Who your coupon actually reaches — and what it does to each

Sure thing
≈ 0 uplift

Buys with or without the nudge. The discount is margin you gave away for nothing.

Lost cause
≈ 0 uplift

Never buys. The nudge is ignored. Wasted impression, wasted spend.

Persuadable
> 0 uplift

Buys only because you nudged them. This is the entire reason the campaign exists.

Sleeping dog
< 0 uplift

The nudge annoys them into leaving. You pay money to lose the sale.

Here’s the trap: persuadables usually have a low baseline probability of buying — that’s what makes them movable. So a propensity model, which ranks by likelihood-to-buy, sorts sure things to the top and buries persuadables at the bottom. It optimizes a proxy that is negatively correlated with the thing you actually want.

Propensity vs uplift, stated plainly

Propensity / lead scoring (the default)

Predict who is most likely to buy, then target them. It's what a huge share of martech ships. But it optimizes a proxy — buy-likelihood — that rewards spending on sure things and is blind to persuadability. It only works when 'likely to buy' happens to line up with 'movable', which is a property of your audience you don't control.

Uplift / incrementality (the fix)

Predict who buys BECAUSE of the treatment, and target only them. It compares treated vs. untreated behavior for each cohort, so it naturally avoids sure things (no lift) and sleeping dogs (negative lift). It optimizes the thing you actually care about: incremental profit.

88% vs 30% of the perfect policy's profit captured — uplift vs. propensity

That impact number isn’t rhetorical — it falls straight out of the simulation. The metric throughout is “fraction of the oracle prize captured”: the incremental profit a policy earns divided by what a perfect, all-knowing policy would earn. 1.0 is flawless targeting, 0.0 is no better than treating nobody, and negative means you are actively destroying value.

Line chart. The uplift bandit rises from about 13 percent to roughly 88 percent of the perfect-policy prize over 200 rounds, while propensity's best cutoff sits flat at 30 percent and blanket couponing at 26 percent, well below the 100 percent oracle ceiling.
Learning purely from aggregate counts, the uplift policy climbs to ~88% of the perfect-policy prize. The best possible propensity cutoff stalls near 30%; couponing everyone captures 26%.

The uplift approach here is a contextual bandit — it learns online with Thompson Sampling, balancing exploration (trying treatments it’s unsure about) against exploitation (cashing in on what it has learned), with no threshold to hand-tune. That’s already better than a static A/B test: it reallocates budget toward what’s working while the campaign runs.

Now make it survive the death of cookies

Everything above assumed you could see per-user outcomes. Post-cookie, you can’t. The Privacy Sandbox’s measurement path gives you only summed, noise-injected buckets: how many people in a broad cohort were shown each treatment, and how many converted — with random noise added so no individual can be reverse-engineered.

This should be fatal for causal methods. Uplift is a difference of two noisy quantities, so it already has high variance; differential privacy then adds more noise to both sides, and the variances compound. If a cohort’s true causal signal is small, the noise can flip the decision.

Except for one structural piece of luck.

The linchpin: sufficient statistics are just sums

The bandit’s belief about each cohort’s buy-rate is a Beta-Bernoulli posterior. Its sufficient statistics — the only things it needs to update — are two sums per cohort: exposures and purchases. And sums are exactly what an aggregation API produces. Learning from privacy-preserving aggregates isn’t a hack bolted onto the model; it’s the natural shape of the math.

fcue/bandit.py
python
1 # Each cohort's belief is a Beta(alpha, beta) posterior; its
2 # sufficient statistics are two SUMS — exactly what an
3 # aggregation API delivers. No per-user row is ever needed.
4
5 def update_from_counts(self, exposures, purchases):
6 successes = np.clip(purchases, 0.0, None)
7 failures = np.clip(exposures - purchases, 0.0, None)
8 self.alpha += successes # conjugacy: add the two sums
9 self.beta += failures # successes -> alpha, fails -> beta
Lines highlighted: 8-9

The privacy tax, measured

So what does the noise actually cost? Below, the privacy budget tightens from left (no privacy) to right (very private). Smaller epsilon = more noise = more private.

Line chart with error bars showing the uplift bandit's captured fraction declining as the privacy budget tightens: about 92 percent with no privacy, 83 percent at epsilon 10, 71 percent at epsilon 3, then a knee down to about 29 percent at epsilon 1 and 14 percent under very strong privacy.
Performance degrades smoothly, then hits a knee. Broad cohorts pool enough users that the causal signal survives large per-bucket noise — until the budget gets very tight.

What each privacy level costs you

Fraction of the perfect-policy prize retained under differential privacy

No privacy
92 %

Clean aggregates, upper bound.

ε = 10
83 %

Generous budget. Barely a scratch.

ε = 3
71 %

Strict, still very usable.

Very strong
14 %

Small batches + heavy noise = signal drowns.

Two design choices keep the signal alive, and both are levers you’d actually pull in production:

  • Broad cohorts. Pool users into a handful of intentionally coarse buckets so each one holds enough conversions to overpower the noise. Fewer, bigger cohorts are more privacy-robust and less personalized — the opposite of the pre-cookie instinct to slice audiences ever finer.
  • Budget-aware reporting. Spend the scarce per-user contribution budget on rare, valuable conversions rather than common impressions, then correct the bias on the server. The real scarcity isn’t the noise mechanism itself — it’s composition: every release spends budget, so reporting less often in bigger batches literally buys back precision.

Uplift wins even when you can’t see the future

Here’s the part that should change how you think about method selection. The x-axis below is how correlated “likely to buy” is with “movable by an incentive” — the one assumption propensity’s entire value rests on, and a thing about your audience you usually can’t observe.

Line chart. The uplift bandit stays near 90 percent captured across the full range of coupling from minus 0.8 to plus 0.8. Propensity is near 14 to 20 percent when coupling is zero or negative and only climbs toward 78 percent when coupling is strongly positive.
The uplift policy (coral) holds near 90% everywhere. Propensity (red) is only competitive when eager buyers happen to also be the persuadable ones — a bet on your population you don't get to place.

Uplift is robust across every assumption. Propensity is a wager that your most eager buyers are also your most movable ones — and when that wager is wrong (the left half of the chart), it collapses to barely above treating people at random.

Ranking by buy-likelihood can lose money outright

One last chart, and it’s the one to sit with. This is a Qini-style uplift curve: rank users by a score, treat the top fraction first, and plot cumulative incremental profit.

Uplift curve. The oracle rises to a high peak and the uplift bandit tracks just beneath it. The propensity curve hugs zero and dips negative through the middle before returning near zero; random stays flat at zero.
The uplift policy (coral) tracks the oracle (green). Propensity (red dashed) hugs zero and goes NEGATIVE in the middle — it spends budget on sure things and sleeping dogs, destroying value along the way.

That dip below zero is propensity actively losing money: past the eager buyers, its next-best picks are exactly the sleeping dogs and lost causes. A good targeting score rises fast and stays concave. A buy-likelihood score, judged on incrementality, can be worse than doing nothing.

What’s real here, and what’s simulated

Being explicit about this is the point — it’s what separates a research prototype from a toy.

AspectIn this modelIn a real deployment
Users & outcomesSynthetic, so true uplift is known and we can score the systemReal users; true uplift is fundamentally unobservable
The edge decisionRuns in one processRuns in a browser Shared Storage worklet (sandboxed, no network)
Aggregation + noiseNumPy: bucket sums + Laplace noise + a contribution budgetPrivate Aggregation API → encrypted reports → a Trusted Execution Environment
Privacy accountingBasic sequential compositionFormal budgets enforced by a coordinator

Nothing here claims to be the Privacy Sandbox. It faithfully models the constraints the Sandbox imposes, so the causal-learning question can actually be answered rather than hand-waved. (If the privacy-plumbing side interests you, it’s the natural sibling of my work on ConsentGuard, an edge-native consent proxy.)

The takeaways for a growth team

  • Stop optimizing proxies. “Likely to buy” is not “worth spending on.” Measure incrementality, or at minimum always run a real holdout so you can see uplift.
  • Coarse cohorts are a feature now, not a compromise. The post-cookie world rewards broad, well-chosen segments — they’re both more private and more robust to noise.
  • Degradation is a dial you can read. Privacy versus performance isn’t a cliff; it’s a smooth trade-off you can measure and choose deliberately.
  • Causal + private is not a contradiction. The math already lines up: if your learner’s sufficient statistics are sums, aggregate-only measurement is enough.

The entire engine is deliberately dependency-light — the causal logic, the bandit, and the Privacy Sandbox simulation are all plain NumPy, so every line is readable and testable.

Built with
Python
NumPy
Pytest

The whole thing is runnable

FCUE is open source — four figures regenerate from one command, and the code is written to be read. Clone it, poke at the privacy knobs, and see the trade-offs move.