Skip to content
bizurk
← ALL WRITING

2026-07-01 / 16 MIN READ

Meta CAPI match quality scoring: how to audit your setup

A field operator's audit pattern for Meta CAPI match quality scoring. Read Events Manager, decode the per-identifier score, and find what is leaking spend.

Match quality on Meta CAPI is a 1-to-10 confidence score that decides whether the algorithm trusts your conversions enough to optimize against them. A 6.2 means Meta is guessing on roughly half of your events. A 9.1 means it is guessing on almost none. The gap between those two scores, on the same store with the same ad spend, is usually 25 to 30 percent of optimization signal getting thrown away.

In Q2 2024 I rebuilt server-side CAPI for a Shopify DTC brand in 48 hours and watched the score climb from 6.2 to 9.1 over the following week of event traffic. This article is not the rebuild. The rebuild lives in the Q2 2024 Shopify CAPI rebuild case study and a step-by-step fix walkthrough is in the per-field PII tutorial for match quality. What follows is the audit pattern, the version you run on your own store before you decide whether to rebuild anything.

The CAPI match quality audit checklist

Run these eight checks in order. Each one takes 5 to 30 minutes. The first three are the ones most operators skip, and they are the ones that find the actual leak.

  1. Pull match quality per event name in Events Manager, not the aggregate. A 7.5 average can hide a Purchase at 9 and a ViewContent at 5.5. The per-event view is what the algorithm uses for that event's optimization.

  2. Decode the score against the identifier coverage table. Each identifier (em, ph, external_id, fbp, fbc, address fields) contributes a known band to the score. If your score is 7.2, you can almost always tell which identifier is missing or malformed by working backwards.

  3. Inspect a single event's user_data block in Test Events. Pull a real event from the last hour, click View Details, and read what fields are present and which are flagged "matched." Numbers in the Overview tab lie when normalization is broken; the per-event payload tells the truth.

  4. Cross-check the identifier presence rate per event. Events Manager reports what percentage of events carried each identifier. If em is present on 98 percent of Purchase events but only 60 percent of ViewContent, you have a coverage gap, not a quality gap.

  5. Verify normalization on em, ph, and the address fields. Email lowercased and trimmed before SHA-256. Phone in E.164 (+14155552671). State as the two-letter code. Country as the two-letter ISO code. Zip as the five-digit form for US. Anything else hashes to a value Meta does not have on file.

  6. Check that fbp and fbc are forwarded server-side without hashing. They are opaque cookie IDs and Meta expects them in plaintext. Hashing them breaks the match silently and leaves them counted as "present" but unmatchable.

  7. Confirm external_id is sent on every event type, not just Purchase. ViewContent and AddToCart need it too. The per-event match quality drops fast when external_id is missing on upper-funnel events, and most rebuilds I audit only set it on the order webhook.

  8. Re-pull the score 7 days after any change, not 7 hours. Events Manager aggregates across a rolling 7-day window. A fix shipped today will not show up in the report until next week. Iterating hour-by-hour is a guaranteed way to chase noise.

Close-up macro of a translucent stack edge with a thin electric-blue rim catching light along the bevel.
// the edge · blue rim on glass

What 6.2 vs 9.1 actually costs you in ad spend

Match quality is not a vanity metric. The score directly affects how Meta's optimizer treats your events, which means it directly affects your CPM and your ROAS at any given budget.

At a 6.2, the optimizer leans on modeled conversions to fill the gap. Modeled conversions are statistical guesses Meta applies when it cannot match a server event to a known user. They smooth across cohorts and wash out the lumpier real-world clusters your ads are actually reaching. The algorithm ends up optimizing against an averaged version of your customer base.

When the score is up at 9.1, the optimizer is matching on real users in real cohorts. Lookalike seeds get tighter and retargeting pools get cleaner, because the conversions Meta is reporting are the conversions that actually happened in customers it can now identify with confidence.

On the Q2 2024 rebuild, the same store at the same spend reported roughly 30 percent more conversions visible to the algorithm after the score moved from the low 6s into the 9s. Part of that was real iOS recovery the browser pixel had been losing, and part was Meta's optimizer crediting conversions it could now match. The spend efficiency lift was material enough that the brand stopped second-guessing the budget for the quarter.

Match quality is a 1-to-10 confidence score that decides whether the algorithm trusts your conversions enough to optimize against them.

The Meta CAPI match quality scoring breakdown by identifier

Here is the band each identifier contributes, in the order that produces the steepest climb. These are the bands I have observed across roughly a dozen DTC audits since 2024. Your numbers will vary by a tenth or two; the ordering is consistent.

IdentifierAdds (approx)Failure mode if missing or malformed
em (hashed email)~6.0 to ~7.0Mixed-case or whitespace before SHA-256 nulls the match. Sending raw email gets the event rejected.
ph (hashed phone)~7.0 to ~7.5Phone without E.164 country code hashes to a value Meta cannot match. US-formatted strings score zero.
external_id (hashed customer ID)~7.5 to ~8.3Most-skipped identifier in audits. Missing it caps the score in the high 7s no matter what else is sent.
fbp + fbc (cookies)~8.3 to ~8.7Sent in plaintext, never hashed. Wiped by Safari ITP unless the loader domain is first-party.
address (ct, st, zp, country)~8.7 to ~9.1State and country must be ISO codes. ZIP must be five digits in the US. Full names hash wrong.

The audit move is to look at your current per-event score and walk this table backwards. If the Purchase event scores 7.4 and the only fields you see in the payload are em and ph, you have an external_id gap. If the score is 8.6 and the payload has everything except address, you have an address coverage gap. The number tells you where to look.

Reflective surface doubling a row of vertical accents under a deep blue sky, mirrored geometry pulled into hard symmetry.
// the mirror · doubled verticals

How to read the Events Manager match quality report without misreading it

Most operators look at one number on the Overview tab and stop. That number is the aggregate score across all event types for the rolling 7-day window. It is the least useful view for finding leaks.

The view that matters lives at Events Manager → Data Sources → your pixel → Overview → Event Match Quality. From there, click into a specific event name (Purchase, ViewContent, AddToCart, InitiateCheckout) and pin that view. Each event scores independently, and the algorithm uses the per-event score when optimizing for that event. A 9.1 on Purchase means nothing for ViewContent optimization if ViewContent is at 5.8.

The Connection Method filter is the second view most audits skip. It splits the score into browser pixel events and server CAPI events. A high aggregate with a high browser score and a low server score usually means your server-side payload is missing identifiers the browser is sending. A reverse pattern (high server, low browser) usually means the pixel is firing without consent gating, picking up partial sessions.

The third hidden detail is the Identifier Coverage view, sometimes labeled "Customer Information Parameters" depending on the rollout you are on. It reports the percentage of events that carried each parameter. A score of 7 with em at 100 percent and external_id at 12 percent reads completely differently than the same score with em at 60 percent and external_id at 100 percent. The first is a normalization problem on the email handler. The second is a coverage problem on guest checkouts.

A single broken glass fragment lit asymmetrically with warm pink and cold blue, isolated against deep negative space.
// the shard · twin lights on a broken edge

Why audits fail (and what they miss)

Five things go wrong on most CAPI audits. I have seen each of them ship to a store that thought it was on top of attribution.

The first is auditing only the Purchase event. Purchase is the easiest one to get to 9 because the order webhook has every identifier on a single payload. ViewContent and AddToCart are harder because they fire on browse-tier traffic with no logged-in user. Operators audit Purchase, see a 9, and conclude the implementation is healthy. The algorithm cares about every event type, and the upper-funnel scores are usually where the leak lives.

The second is trusting the aggregate score. A weighted average across event types smooths over a Purchase at 9.2 and a ViewContent at 5.4 into a single 7.6. Looks fine. Optimizes badly.

The third is not testing the actual hash with a known input. The Test Events tool will accept whatever you send, including a malformed hash. Pull an event, copy the em value, and confirm it equals sha256(lowercase(trim(known-email))). If your handler is producing a different hash for the same input, you have a normalization bug that the dashboard will not catch.

The fourth is missing the staging/prod payload divergence. A handler that hashes correctly on the staging server because the env var CAPI_TEST_MODE is set differently in production is a class of bug I have seen four times. The pattern of locking event contracts so this cannot drift is in the postmortem on a CAPI payload that quietly diverged.

The fifth is not re-auditing after a third-party app update. Klaviyo, Triple Whale, Northbeam, and any subscription app can install their own Meta tags. A platform update can ship a new tag that fires alongside yours, double-firing events with different identifier sets, dragging the per-event score down without anyone shipping new code on your side. Re-audit after every third-party install or major app update on the store.

Ultra-wide distant vantage looking across a vast plain to two faint vertical accents on the horizon, deep electric-blue atmosphere holding the scene.
// the long view · two distant points

Common mistakes

The two most expensive mistakes I see in CAPI match quality audits both come from confusing a related metric with match quality itself.

Treating match quality and match rate as the same number. Match rate is the percentage of events Meta could tie to any user at all. Match quality is the confidence of those matches on a 1 to 10 scale. You can have a 90 percent match rate (Meta found a user for 90 percent of your events) and a 6 match quality (Meta is only somewhat sure those matches are the right user). Fix the wrong metric and you spend a week chasing a problem you do not have.

Hashing fbp or fbc. They are already opaque IDs. Meta expects them in plaintext and matches them by exact value. Hashing them produces a string that matches nothing, but the field still shows as "present" in the payload, so the dashboard does not flag it. The score quietly stays in the 7s and the engineer who shipped the hashing function thinks the implementation is correct.

A third one shows up in stores that operate across multiple countries. Hardcoding a default country code on phone normalization (always prepending +1, for example) produces a mangled E.164 string for any UK or AU order. The phone field then counts as "present" but unmatched, which the algorithm penalizes harder than a missing field. Pull the country from the order's billing address and pass that into the E.164 conversion before hashing.

The last gotcha is skipping external_id on guest checkouts because there is no logged-in customer ID. Synthesize one from the email hash. The field needs to be populated on every event type, even the upper-funnel ones, and a stable proxy is better than a null. The mechanics for hashing customer IDs and the consent layer that gates them are covered in the purchase event identifier hashing post and the Consent Mode v2 pattern for CAPI events.

What to try next

Once your Purchase event is above 9 and you have a per-event reading on the rest, do three things.

Audit every other event type to the same checklist. The score that drives ViewContent optimization is the ViewContent score, not the Purchase score. Walk the eight checks above for each event independently.

Set a calendar reminder to re-pull the per-event match quality every 30 days. Scores drift. Apps change. New engineers ship new handlers. The implementation that hits 9.1 on a Tuesday in May is not guaranteed to hit 9 on the same store in October without periodic re-audits.

Run a diagnostic scan on the live store. The CAPI Leak Report covers 14 checks including per-field normalization and per-event match quality drift. It is the scan I built to catch this class of regression on stores that already shipped a rebuild once. If you want the deeper attribution tooling that pairs with it, the BigQuery attribution modeling pattern for DTC stores covers what to do with the events once you trust the data, and the Meta CAPI hub field guide for operators maps the rest of the implementation surface around match quality.

FAQ

What does match quality mean compared to match rate?

Match rate is the percentage of events Meta could tie to any user at all. Match quality is the confidence of those matches on a 1 to 10 scale. They move independently. You can have a 90 percent match rate and a 6 match quality if your identifiers are present but normalized poorly, which is the common failure mode on CAPI implementations that look fine in the Overview tab.

How often does Meta update the match quality score?

Roughly every 7 days, aggregated across the events received in that rolling window. A fix you ship today will not show up in Events Manager for about a week. Watch the dashboard hourly after a deploy and you will see noise and assume the fix failed. Wait the full 7 days before judging.

Why is my Purchase event at 9 but ViewContent at 6?

Purchase events fire from the order webhook, which has every identifier in one payload. ViewContent fires on browse traffic where the visitor is often not logged in, so em, ph, and external_id are missing. Synthesize an external_id from a stable browser fingerprint (fbp itself works as a proxy when nothing else is available) and forward whatever the cookie layer has captured. The ViewContent score will not match Purchase, but it should reach 7.5 to 8 with effort.

What identifier moves the score the most for the least effort?

external_id. It is the second-highest-impact field after email and the least-used in audits. On a store already sending em and ph, adding external_id (hashed customer ID for logged-in users, hashed email as a synthesized fallback for guests) typically moves the per-event score from the high 7s to the mid-to-high 8s in a single deploy. It is also the cheapest fix because the data already exists in Shopify; the handler just needs to read and hash it.

Is a 9.1 actually better than a 9.0 in any meaningful way?

Marginally, yes, but the marginal returns flatten above 9. The jump from 6 to 8 is where the optimizer sees a meaningful lift in signal. Above 8.5, the algorithm has enough confidence to match on real cohorts; the difference between 9.0 and 9.5 is mostly the cross-device edge cases where the same customer is logged into Facebook on a different device than they used at checkout. A week spent chasing a 0.1 lift on one event is rarely worth it, while the same week bringing every other event type up to 8.5 usually is.

Can a third-party app silently lower my match quality?

Yes, and this is the most-missed regression mode. Klaviyo, Triple Whale, Northbeam, and most subscription apps can install their own Meta pixel or CAPI tags. A platform update can ship a tag that fires alongside yours, with a different identifier set, and the per-event score drops without anyone deploying code on your end. Re-audit after every third-party install and after major version updates on existing apps. The CAPI Leak Report flags duplicate tag sources as a default check.

Sources and specifics

  • The 6.2 to 9.1 progression is from the Q2 2024 Shopify DTC rebuild documented in the tracking gap rebuild story.
  • The per-identifier score bands (em ~7.0, ph ~7.5, external_id ~8.3, fbp/fbc ~8.7, address ~9.1) are observed across roughly a dozen DTC CAPI audits I have run since 2024. They are directionally consistent, not a Meta-published table.
  • Events Manager aggregates match quality on a rolling 7-day window per Meta's published documentation, current as of April 2026.
  • Per-event match quality scoring has been live in Events Manager since 2023; the Connection Method filter that splits browser pixel from server CAPI was added later that same year.
  • The single-entry-point hashing utility pattern referenced in the staging/prod divergence section is the same discipline covered in the post on debugging silently divergent CAPI payloads.

// related

DTC Stack Audit

If this resonated, the audit covers your tracking layer end-to-end. Server-side CAPI, dedup logic, and attribution gaps - all mapped to your stack.

>See what is covered

Tell me what you’re trying to ship.

Send a quick message and I read it within a day, or talk to AI Michael first if you want to feel out your project before you write to me.

By sending this, you agree to the Terms and acknowledge the Privacy Policy.