Detection: Conversions API missing or weak for Purchase
Key: capi_missing_or_weak
Severity: High (missing) / Medium (weak)
Confidence: 75–90%
What this rule detects
We flag an account when the server-side Conversions API (CAPI) is either
absent for the Purchase event, or present but sending so few events relative
to the browser pixel that it is not meaningfully protecting against signal
loss. The rule looks at every pixel event named Purchase, separates the
browser-pixel count from the CAPI count, and compares them. If no Purchase
event carries a CAPI source at all, the account is CAPI-missing. If a
CAPI Purchase event exists but its total volume is below half the
browser-pixel Purchase volume, it is CAPI-weak. The rule only runs for
accounts that have an active sales-objective campaign and a projected
monthly spend of at least $1,500 — below that, the configuration work is
hard to justify against the recoverable upside.
Why it matters in 2026
The browser pixel alone is structurally lossy. Apple's iOS 14.5+ App Tracking Transparency framework lets users decline cross-app tracking, and most do; Safari's Intelligent Tracking Prevention caps client-side cookie lifetimes; and broad browser-level third-party cookie deprecation removes more client signal every year. Every purchase the browser pixel fails to report is a purchase Meta's delivery model never learns from. The Conversions API is the server-side channel that closes this gap: your server posts the Purchase event directly to Meta, matched to a user by hashed email, phone, and other parameters, with no dependence on the browser, cookie state, or ATT consent. Meta's own guidance, and the design of Advantage+ campaigns and the rebuilt "Andromeda" ad-ranking model, all assume a healthy server signal — Andromeda's larger candidate pool needs more reliable conversion data to rank well, and conversion modeling (the statistical fill Meta applies to estimate conversions it cannot directly observe) is strictly less accurate than a real server-reported event. An account spending into sales objectives without CAPI is optimizing against a partial, ATT-degraded picture of its own results. This is why the missing case is the highest-severity finding in the catalog: it is not waste inside one ad set, it is a degraded signal feeding every delivery decision in the account.
The math
The rule's headline statistic is the CAPI-to-browser coverage ratio for the Purchase event:
browser_total = sum of all Purchase events flagged is_browser
capi_total = sum of all Purchase events flagged is_capi
coverage_ratio = capi_total / browser_total
A ratio of 0 means CAPI is missing. A ratio of 1.0 means CAPI reports as many Purchases as the browser; healthy deduplicated setups often exceed 1.0 because CAPI captures conversions the browser lost. We treat a ratio below 0.50 as weak.
The ratio is a point estimate from a finite window of events, so it carries sampling uncertainty. To express that, treat "this Purchase event was seen by CAPI" as a binomial outcome over the combined event volume and put a Wilson score interval around the CAPI share. The Wilson interval behaves correctly for small samples and skewed proportions, unlike the normal-approximation "Wald" interval:
p_hat + z^2/(2n) z / p_hat(1 - p_hat) z^2 \
center = ------------------ +/- -------- * sqrt( ---------------- + -------- )
1 + z^2/n 1 + z^2/n \ n 4n^2 /
p_hat = capi_total / (capi_total + browser_total) (CAPI share of all events)
n = capi_total + browser_total
z = 1.96 for a 95% two-sided interval
The interval is [center - margin, center + margin]. A coverage ratio of
0.50 corresponds to a CAPI share of 1/3 (one CAPI event for every two browser
events). When the upper bound of the Wilson interval still sits below
that 0.333 share, the weakness is real and not a quiet month.
For comparing coverage between two pixels or two account configurations, the
Beta-Binomial conjugate model gives a clean posterior. Treat the true
CAPI share as unknown with a Beta(alpha, beta) prior; observing s CAPI
events out of n total events updates it in closed form:
prior: coverage ~ Beta(alpha, beta)
data: s CAPI events out of n total events
posterior: coverage ~ Beta(alpha + s, beta + n - s)
posterior mean = (alpha + s) / (alpha + beta + n)
With a weak uniform prior Beta(1, 1), an account with 66 CAPI and 240
browser Purchases has posterior Beta(67, 241), posterior mean ≈ 0.218 —
firmly in weak territory, with little uncertainty given 306 events.
When an audit evaluates many events or many accounts at once, raw p-values
from those comparisons need a multiple-comparison correction so we do not
report a weak finding that is really chance. We use Benjamini-Hochberg
false-discovery-rate control: sort the m p-values ascending,
p(1) <= ... <= p(m), and keep the largest rank k whose p-value still clears
its rank-scaled threshold:
find largest k such that: p(k) <= (k / m) * Q
m = number of comparisons
Q = target false-discovery rate (we use Q = 0.10)
reject (keep) comparisons ranked 1..k; drop the rest
For the shipped rule the comparison is a single account-level coverage ratio against a fixed 0.50 threshold, so Benjamini-Hochberg is not applied per finding — but it is the correction we use whenever coverage is compared across multiple events or pixels.
The thresholds we use and why
| Parameter | Value | Why |
|---|---|---|
| Purchase event name | Purchase |
The standard Meta event for completed sales. Custom-named events are out of scope here. |
| Weak-coverage ratio | capi ÷ browser < 0.50 | Below half the browser volume, CAPI is not materially closing the ATT gap. A default — see note below. |
| Minimum projected monthly spend | $1,500 | Below this, the engineering cost of a CAPI setup is hard to justify against the recoverable upside. |
| Requires active sales campaign | objective in the sales / leads / app-promotion / conversions set | CAPI for Purchase only matters when the account is optimizing for conversions. |
| Missing-case confidence | 90% | No CAPI Purchase event at all is directly observable, not inferred. |
| Weak-case confidence | 75% | A low ratio can also reflect a deduplication or event_id mismatch, so confidence is lower. |
| Recoverable share | 10% of spend (missing), 5% (weak) | Conservative estimate of optimization lift from restored signal. |
The 0.50 ratio threshold is a sensible default, not a statistically derived constant. A correctly deduplicated CAPI setup typically reports at or above browser volume; we draw the "weak" line at half to leave headroom for event-timing skew and partial rollouts. The $1,500/month spend floor and the 5% / 10% recoverable shares are also defaults. All three should be reviewed by Nachiket against real audit data.
Known false-positive cases and how we mitigate them
- Deduplication hides healthy CAPI. If browser and CAPI both fire for the
same purchase and share an
event_id, Meta deduplicates them — but the raw per-source counts the rule reads can still look browser-heavy depending on how the snapshot attributes deduplicated events. A genuinely healthy setup can read as weak. The rule surfaces both raw counts in its evidence so a practitioner can sanity-check against Events Manager before acting. - A recent CAPI launch. An account that switched on CAPI mid-window will show a depressed ratio because the browser count includes pre-launch days. Re-running the audit on a window that starts after the CAPI launch date resolves this.
- CAPI exists under a custom event name. If the team named its server
event something other than
Purchase, the rule sees no CAPI Purchase and reports missing. The evidence (browser count, CAPI count = 0) makes this easy to spot; the fix is naming alignment, not a new integration. - Browser pixel does not fire at all. Mobile-app-only checkouts or off-platform marketplaces produce no browser-pixel Purchases. The rule checks that the browser count is non-zero before flagging, so this case does not trigger a false positive.
A worked example
A $30K/month account runs an active Advantage+ Sales campaign. Over the
30-day audit window its Purchase pixel events break down as:
browser-pixel Purchase events: 240
CAPI Purchase events: 66
The account clears both gates: projected monthly spend ($30,000) ≥ $1,500, and there is an active sales-objective campaign.
coverage_ratio = capi_total / browser_total = 66 / 240 = 0.275
0.275 is below the 0.50 weak threshold and above 0, so the account is CAPI-weak — severity Medium, confidence 75%.
Wilson 95% interval on the CAPI share of all 306 events
(p_hat = 66/306 = 0.2157, n = 306):
CI approx [0.173, 0.266]
The entire interval sits below the 0.333 share that a 0.50 ratio implies, so the weakness is not a quiet-month artifact — it is a real coverage gap.
Recoverable monthly value for the weak case is 5% of spend, projected to 30 days:
recoverable_in_range = total_spend * 0.05 = 30000 * 0.05 = $1,500
monthly_cost = recoverable_in_range * (30 / days_in_range)
= 1500 * (30 / 30) = $1,500 / month
Had this account shown zero CAPI Purchase events, it would be CAPI-missing instead: severity High, confidence 90%, recoverable estimate 10% of spend (~$3,000/month).
Limitations
- The rule reports a coverage gap; it cannot diagnose the cause. Missing
integration, a custom event name, a broken
event_idmatch key, and a partial rollout all look similar in the counts. - It cannot measure match quality. A CAPI event that arrives with weak or unhashed customer parameters is still counted, even though Meta may fail to attribute it. Volume parity is necessary but not sufficient for a healthy server signal.
- It does not assess deduplication correctness. Two pixels double-counting the same purchase is a separate problem the count comparison cannot see.
- It only evaluates the
Purchaseevent. Coverage of upstream events (ViewContent, AddToCart, InitiateCheckout) is handled by the separateweak_event_coveragerule. - The recoverable-value figure (5% / 10% of spend) is a deliberately conservative estimate of optimization lift from restored signal, not a measured counterfactual.
Source
This methodology page is generated from apps/api/app/services/detections/capi_missing_or_weak.py. The detection code is open for inspection on GitHub. Disagree with how the rule fires? Open the file. Read the code. Tell us where we're wrong.