Marketing Leadership You Can Actually Afford
Back to Blog

Meta Is Reporting Fewer Purchases Than You Actually Made

Thirteen reasons Meta's purchase count comes in under your real sales — ranked by probability, with the check and the fix for each. Most of them are not bugs.

An analytics dashboard interface showing conversion and performance data

Photo by Deng Xiang on Unsplash

If Meta's reported purchase count is consistently lower than the sales sitting in your database — never higher, just persistently short — there are four likely causes. Meta is filtering the event because of how it has classified your domain. The two numbers are counting different populations. Your tracking middleware is dropping events without telling anyone. Or the event fires too late for Meta to accept it.

Those four are not mutually exclusive. In practice the answer is usually two of them at once, and the order you check them in matters more than the depth of any single check.

This is the diagnostic we run. It is ranked by probability, it tells you what to check and roughly how long each check takes, and it separates the hypotheses that need a code change from the ones that need a settings change or no change at all. Some of the most common causes are not bugs. One of them takes five minutes to confirm and determines whether the rest of the work is a configuration fix or an architecture rebuild.

Start there.

Before You Debug Anything, Get Three Numbers

The single most common way teams waste weeks on this problem is by comparing two numbers that were never going to match, then hunting for a bug that does not exist. Before any diagnosis, separate three counts.

Number A is your database. Every sale you made in the window — paid, organic, direct, branded search, affiliate, email and SMS reactivation, returning customers.

Number B is what Meta received. Open Events Manager and look at raw purchase events accepted into the dataset. This is pre-attribution. It includes events Meta could not match to a person and events that had nothing to do with an ad.

Number C is what Ads Manager reports. This counts only events Meta could match to a person, tie to an ad touchpoint, and place inside the attribution window. It is reported on the date of the ad click, not the date of the purchase.

Now ask the question that collapses the search space: where is the gap, A to B, or B to C?

  • If A is much larger than B, events are not arriving. Look at restriction filtering, middleware configuration, batch rejection, duplicate event IDs, and whether you are sending to the dataset you think you are.
  • If A and B are close but B is much larger than C, events are arriving and failing to attribute. Look at click identifiers, match quality, attribution windows, and your reporting basis.
  • If C is low but roughly proportionate to your actual paid share, nothing may be broken at all.

That last case deserves emphasis. C should be meaningfully lower than A. Meta only counts sales it can tie to an ad. Establishing your realistic paid share is step zero, and for a business with a mature email list or a large repeat-purchase base, that share can be a lot smaller than the marketing team assumes.

Tier A: The Checks Worth Doing in the First Hour

Three checks. Minimal effort, high probability. Do not write code before these are done.

1. Meta Has Classified Your Domain Into a Data-Sharing Restriction Tier

Highest probability, highest impact, five minutes to check, and not fixable by code.

In late 2024 Meta began notifying advertisers that it would classify health-related domains into data-sharing restriction tiers, with enforcement rolling out through January and February 2025. It is still in force. Classification is automated: Meta crawls your domain and inspects your event payloads, then assigns a tier.

Two things make this trap so effective. First, it is a separate process from ad creative review, so you can be passing ad review and fully restricted on data at the same time. Second, nobody sends you a clear notice saying which tier you are in.

The tiers, as described consistently across roughly six independent sources. Meta has not publicly confirmed its own tier labels, so treat the names as industry shorthand rather than official terminology.

Tier What happens Typical business
1 — "Core Setup" URLs truncated to the domain, custom parameters stripped, Automatic Advanced Matching unavailable, reduced Events Manager visibility including Test Events. Purchase and Lead optimization still work. Supplements, fitness
2 — Standard events restricted Purchase, Lead and AddToCart blocked from optimization and reporting. PageView, LandingPageView, ViewContent, Search and App Install still allowed. Telehealth, weight-loss programs, hormone therapy
3 — Fully restricted No conversion tracking, no pixel retargeting, custom events unavailable too. Fallback is on-platform lead ads. Patient portals, prescription services

If you sell anything adjacent to health, read those descriptions carefully before deciding they do not apply to you. The scope is wider than obvious healthcare. Supplements, fitness programs, mental health services, hormone therapy, sleep products and eligibility quizzes that ask health questions have all been swept in.

The critical detail, and the most common misconception in this entire area: the Conversions API does not get around it. Sending Purchase server-side from a restricted domain is filtered exactly the same as a browser pixel would be. Ours Privacy states it plainly on their own Meta page — partially restricted companies cannot send lower-funnel standard events such as purchases.

How to check, in order:

  1. Events Manager, then Settings, then Manage data source categories. This is the definitive answer. Screenshot it.
  2. Look for a restriction banner or a care portal label on the data source.
  3. In Ads Manager, open the conversion event dropdown on an ad set. Is Purchase greyed out or missing?
  4. Search the Business Manager admin's email for Meta notifications dated November 2024 onward naming affected events.
  5. Check the Events Manager Diagnostics tab for restriction-related flags.
  6. Compare events received against events available for optimization on the dataset. A restricted account shows received-but-unusable.

If it is confirmed, the documented path is to move off the standard Purchase event and onto a generic custom event with a deliberately clean payload. This is a real, vendor-supported pattern, not something someone improvised. Ours Privacy's own guidance is to transition off pixels and standard purchase events and use the Conversions API with obfuscated custom events and redacted health data — their example is a custom event named literally "Event 4."

Two caveats determine whether that actually works.

Renaming alone will fail. Meta has stated it will detect and filter custom events that appear potentially sensitive regardless of what data restrictions are applied. Meta evaluates the whole payload, not just the event name. The pattern works because the payload is clean: no drug or condition names, no diagnostic terms, no content_category inherited from your CMS, no condition-bearing URL in event_source_url, no custom parameters carrying detail. If you do this, spec the payload field by field.

Custom events have to be acknowledged and enabled in Events Manager before they become a valid optimization target. A renamed event that was never registered arrives and does nothing.

If you land in Tier 3, custom events are blocked as well. The move becomes on-platform lead ads, upper-funnel optimization, and measurement through blended acquisition cost rather than in-platform return on ad spend. That is a strategy conversation, not a configuration fix.

On appeals, the sources genuinely conflict. Some say appeals are available in Events Manager with a three to seven day review and a thirty-day lockout if rejected. Meta representatives have reportedly said Core Setup enrollment cannot be appealed. Freshpaint says appeals rarely succeed. The best reconciliation is that Tier 1 has no appeal and genuine misclassification into Tier 2 or 3 can be appealed. Plan as though the appeal fails.

2. The Comparison Itself Is Apples to Oranges

High probability as a contributing factor. About an hour to check, and it costs nothing to rule out.

A large share of "Meta is undercounting" reports turn out to be methodology errors. Four mechanisms, each of which pushes Meta's number down artificially.

Meta reports on the click date, not the purchase date. A purchase today from a click five days ago is added retroactively to that earlier day's row. The trailing week of any Ads Manager report is structurally incomplete and keeps rising for days afterward. Comparing Meta purchases for August 1 through 7 against orders placed August 1 through 7 compares two different populations.

Reporting lags 24 to 72 hours. Events show up in Events Manager within about twenty minutes. Attribution is what lags. Reports keep settling through roughly T+3 or T+4.

The 2026 attribution changes structurally cut reported conversions. On January 12, 2026, Meta removed 7-day-view and 28-day-view attribution from the Ads Insights API entirely, across all API versions at once, with reported drops of 15 to 40 percent for affected accounts. On March 3, 2026, click-through was redefined to require an actual link click; likes, comments, shares, saves and profile taps moved into a new engage-through column capped at one day. If you report on the click-through column alone, conversions that used to appear there have either moved or vanished. That looks exactly like undercounting.

If you first noticed this problem in 2026, those two changes alone could be most of it. Pin down when you first saw the gap.

Total sales are not ad-attributable sales. Your database total includes organic, direct, branded search, affiliate, returning customers and lifecycle email and SMS reactivations. Meta should never claim those. For a business with a large served-customer base and active winback flows, the repeat and reactivation share is rarely small.

How to check:

  • In Ads Manager, go to Columns, then Compare Attribution Settings. View the same period under 1-day click, 7-day click and 28-day click side by side. A steep gradient means the undercount is window-scoped, not broken.
  • Add the engage-through column. Conversions that disappeared in March 2026 are likely sitting in it.
  • Re-pull an identical historical date range seven days apart. If the numbers rose, you are watching click-date backfill and the original reconciliation ran too early.
  • Check whether any dashboard pipeline still requests 7d_view or 28d_view. After January 2026 those return nothing, producing a step-change drop with no underlying cause.
  • Segment your database. What share of purchases in the window were first-time versus repeat or reactivation? Exclude repeats before comparing.

None of this is an accusation that whoever ran the first reconciliation got it wrong. It is cheap to rule out, and if it explains part of the gap it changes how much of the remaining gap is worth spending money on.

3. Your Tracking Middleware Is Silently Dropping or Neutering Events

High probability if the install is recent. One to two hours.

Any consent or CAPI middleware layer sitting between your systems and Meta has a pipeline roughly like this: event, then allow list, then data governance rules, then destination mapping, then dispatch. Ours Privacy, to take a documented example, has at least eleven ways an event dies before reaching Meta and eight more ways it reaches Meta in a state where it is worthless.

Events that never leave the middleware:

  • Event allow list. An event that is not allowlisted does not appear in recent events and is not forwarded to any destination. Allowlisting is manual, and the documentation warns to do it before implementing tracking.
  • Test mode or a test token. Events are processed and stored, and nothing is sent to external destinations. A test token left on in production is 100 percent silent loss.
  • An unpublished version. Configuration changes are staged. If the latest version was never published, the fix your team believes they made is not live.
  • Data governance rules. Consent categories that stop dispatch, evaluated in priority order with first match winning. These also require publishing.
  • Stop-dispatch mappers. Per-destination conditional blocking, usually on consent.
  • Source-to-destination connection. Allowlisted events still do not transmit if the source connection is inactive.
  • Bot detection. Threshold mode commonly defaults to a bot score around 70 out of 100, and vendors admit their scoring is imperfect without IP reputation data. Server-to-server traffic from a datacenter IP is exactly what naive bot scoring flags.
  • Domain allowlisting. The SDK only loads on explicitly allowlisted domains. A new landing page domain or subdomain silently gets nothing.
  • Ingest-level deduplication. Same visitor and distinct identifier pair, and only the first event is stored. Where no time window is documented, a returning customer purchasing twice can collide.
  • Event age caps. Ingest APIs commonly round events older than seven days to seven days, and default invalid time values to now.

Then the sneakier category — events that reach Meta and are worthless:

  • A test event code left set. The behavior is explicit in the documentation: events sent with a test code appear only in the Test Events tab and are not used for ad optimization or reporting. Check this first. It is a one-field, one-minute check that would explain everything.
  • Custom salting. A per-destination custom salt applied to all hashing. Vendors document the tradeoff themselves — it disrupts the ability to match data across platforms. A salted email hash is a random string to Meta. Events arrive, match nothing, and match quality collapses. It defaults to off, but it is one click away, and its compliance rationale makes it tempting for a compliance-minded person to enable without telling marketing.
  • Synthetic data enrichment. Some platforms can generate a synthetic email, phone number or IP. If any of those got mapped into the Meta destination, Meta receives perfectly formed, perfectly hashed, completely unmatchable data.
  • Redaction or null modifications on email, phone or IP. Meta receives the literal string REDACTED, or a null. Match quality goes to zero.
  • Nulls from the CRM clearing click identifiers. This one is documented and vicious: sending a null fbclid from a webhook clears the previously captured click ID on the visitor, while omitting the field entirely is ignored. A CRM that helpfully sends nulls for empty fields will systematically wipe your attribution. Easy bug to write, hard bug to notice.
  • Excluding request context on a source, which silently disables probabilistic identity recovery.

How to check, in this order:

  1. Open your middleware's recent events view, filter to the purchase event, and open the dispatches section on an individual event. Is Meta listed? Green, or errored with a message? This single view splits "we did not send it" from "Meta did not count it" and is the fastest useful diagnostic in the entire process.
  2. Inspect the Meta destination config. Is the test event code blank? Is custom salting off? Are any synthetic data fields mapped? Any redaction or null rules on email or phone?
  3. Is the latest configuration version published?
  4. Is the purchase event on the allow list for the Meta destination specifically?
  5. Is any governance rule or stop-dispatch mapper catching it? Is the source-to-destination connection active?
  6. Check bot detection mode and threshold on the server-to-server source.
  7. Sample the dispatch failure log. Most platforms retry with exponential backoff for up to 48 hours, and failures surface with error messages.
  8. Confirm the dataset ID in the middleware matches the dataset your ad sets optimize against, digit by digit.

While you are in there, verify two parameters that vendor documentation frequently omits: action_source, which Meta requires, and event_source_url, which is required alongside it for website events. If you cannot confirm them in the mapping interface, they are direct questions for vendor support.

One practical note on vendor support. Several of the newer privacy-focused middleware vendors are small and recently funded, with email-only support on weekday business hours and no documented escalation path. Budget for doing the legwork yourself rather than waiting on the vendor.

Tier B: Pipeline Integrity

Lower probability individually, but several of these are catastrophic when present and cheap to rule out.

4. A Static or Non-Unique Event ID Is Collapsing Many Purchases Into One

Low probability, catastrophic magnitude, five minutes to check.

Meta deduplicates on event_id plus event_name within a 48-hour window. If the implementation emits a constant — the literal string "purchase", a template literal that never interpolated, a session ID, a cart ID — every purchase in a rolling 48-hour window collapses into one. Forty orders a day becomes roughly one reported purchase.

Related traps: an event ID derived from a customer ID collapses repeat purchases; a genuine second purchase by the same customer within 48 hours inherits the same ID; and where no event ID is sent at all, Meta's fallback deduplication on event name plus browser ID can be over-aggressive.

Middleware that sets the event ID automatically from the event's own identifier is usually safe on this specific point, but its own ingest-level deduplication is a second, independent collapse risk.

The five-minute test: pull twenty real order IDs from your database for a single day, then grep the outbound payloads or your middleware's recent events for those orders. Are there twenty distinct event IDs? Then check Events Manager Diagnostics for "Same event ID received for many event instances."

The fix is to use the order or transaction ID: globally unique, deterministic, and identical across browser and server. Prefix with the event name where one ID could serve multiple event types.

5. Whole Batches Are Being Rejected Because One Event in Them Is Invalid

Medium probability, very high magnitude, about an hour, and badly under-appreciated.

Meta's documentation is blunt: if any event you send in a batch is invalid, the entire batch is rejected. Batches hold up to 1,000 events. One malformed record kills all thousand.

The most common trigger is event_time. Events may be backdated up to seven days, and if any event_time exceeds that window, the entire request is rejected and no events are processed. If you have any lag between payment and the event firing — a fulfillment delay, an approval step, a nightly backfill job, a delayed webhook retry — a single eight-day-old order silently erases every purchase batched with it. Repeatedly. Forever. With no visible error unless somebody is logging non-200 responses.

Other batch killers: milliseconds instead of seconds in the timestamp, which produces a far-future date and gets rejected; local time instead of UTC; an expired access token, since personal tokens expire on password change and fail silently; and malformed currency or value fields.

How to check:

  • Does anything log the Conversions API HTTP response? Look for non-200s, warnings in the messages field, and fbtrace_id values. If there is no error logging at all, that is finding number one regardless of everything else. An entire category of failure is invisible to you right now.
  • Compute the distribution of event send time minus payment time across recent orders. Any tail beyond seven days is guaranteed loss, and if events are batched, that tail poisons whole batches.
  • Confirm the token is a System User token set to never expire with ads_management permissions, not a personal token.
  • Check Diagnostics for currency and value errors.

The fix is per-event validation before batching, dropping and logging invalid records rather than letting them poison the batch, alerting on non-200 responses, a System User token, and firing the event closer to real time.

6. The Purchase Fires on Fulfillment Instead of Payment, or a Refund Filter Suppresses Legitimate Sales

Medium-high probability, thirty minutes, and it fits the symptom unusually well.

If your business has a genuine gap between the card being charged and the order being real — an approval step, a manual review, a fulfillment delay — there are three concrete mechanisms here. Note that all three can only push Meta's number down, never up, which matches a consistently-low-never-high symptom better than most alternatives.

  1. Refund status evaluated at send time. If the pipeline only fires Purchase when the payment is captured and not refunded, and refunds happen days later, a naive implementation suppresses purchases that were legitimately made and only subsequently refunded. Meta should count those. Meta has no Refund standard event; refunds belong in reconciliation, not in Meta.
  2. Declined-then-retried cards. If the event is keyed on the first attempt's ID, a successful retry may never fire.
  3. Purchase fires only on fulfillment. Anything stuck in review, denied downstream, or awaiting shipment never fires at all, while still counting as a sale in your database.

There is also an optimization cost independent of counting. Attribution survives a delay, because Meta backdates to the click date within the seven-day cap. Optimization largely does not. A conversion signal arriving days later contributes almost nothing to the algorithm's learning, which matters enormously if you are in a learning phase trying to bring costs down.

The fix, and this is a real recommendation regardless of what else the diagnosis turns up: fire the conversion event on payment capture. It is immediate, and it is a genuine financial commitment. Send approval and fulfillment as separate downstream events for reporting only. Faster signals that predict downstream value outperform delayed signals of actual value. Handle refunds as line items in reconciliation, not by suppressing the event.

7. Events Are Landing in a Different Dataset Than the One Your Ad Sets Optimize Against

Low probability, ten minutes, quick to rule out.

Ads Manager reports conversions from the dataset the ad set optimizes against. Events landing anywhere else are received perfectly, visible in Events Manager, and completely absent from campaign reporting. The classic signature is "Events Manager looks fine, Ads Manager shows almost nothing."

Exposure goes up with every independent party touching the stack. An agency running media buying, an in-house API integration, and a middleware vendor are three things that could each be configured with a different ID. Agency-versus-client Business Manager splits are a very common cause. Note that Meta folded Pixels into Datasets — same numeric ID, new name — while a lot of vendor documentation still uses the legacy "Pixel ID" terminology.

How to check: run Meta Pixel Helper on the confirmation page, since more than one pixel ID firing is an immediate red flag. Then go to Business Settings, Data Sources, Datasets, list all of them, and check events received in the last seven days on each; any unexpected dataset receiving purchases is the leak. Confirm each dataset is assigned to the ad account running the campaigns. Compare the ad set's configured dataset against the ID in your middleware digit by digit. And check Diagnostics for "New domains sending data," which often reveals a stray source.

Tier C: Match Quality

These generally cause partial degradation — a B-to-C gap — rather than a cliff. They are also where most server-side implementations quietly lose a third of their attribution.

8. Click Identifiers Are Not Captured at the Start of the Funnel and Replayed on the Server-Side Purchase

High probability wherever a custom funnel feeds a server-side purchase event.

The fbc parameter is the deterministic link between a specific ad click and this conversion. Without it, Meta falls back on probabilistic matching. It is the single most common reason server-side purchases fail to attribute.

The specific reason to suspect it: middleware documentation typically states that fbc and fbp formatting is automatic with the standard web SDK, with manual formatting required only for custom data mappings or server-to-server integrations. If your purchase fires from your CRM or backend, that is precisely the server-to-server case where the tool does not do it for you.

The formats matter. fbc is fb.{subdomainIndex}.{creationTimeMs}.{fbclid} and fbp is fb.{subdomainIndex}.{creationTimeMs}.{randomNumber}, where subdomainIndex is 0 for a bare TLD, 1 for example.com and 2 for www.example.com. A mismatch between where the cookie was written and how it is reconstructed server-side produces a malformed string that matches nothing.

Every way it gets lost in a custom multi-step funnel:

  • Safari's tracking prevention caps JavaScript-written cookies at seven days, and practitioner reporting puts it as low as 24 hours when the landing URL carries an fbclid. Your seven-day click attribution window outlives the identifier storing it.
  • Link Tracking Protection strips fbclid before page load in Safari Private Browsing and for links opened from Mail or Messages.
  • Redirects drop the query string. HTTP to HTTPS, www to apex canonicalization, geo-redirects, any hop to a checkout step.
  • Multi-step navigation. Every funnel step that is a real page load or form POST loses the URL parameter unless something explicitly carries it forward.
  • A write race, where consent tooling delays the cookie write or a redirect fires before the write completes.
  • The CRM sending a null fbclid, which actively clears the stored click ID as described earlier.

Before you call any of this a bug, compute the ceiling. The fbc parameter only exists when someone clicked an ad. If a hundred purchases occurred but only thirty came from ad clicks, expect at most thirty events carrying fbc. Divide ad clicks landing on the site by total purchases first. Only the gap below that ceiling is fixable, and for a business with heavy email and SMS reactivation, the ceiling may be far lower than it looks.

How to check: open the Purchase event's parameter coverage breakdown in Events Manager and read the fbc and fbp percentages directly. Those per-parameter percentages tell you far more than the headline match quality score. Segment by browser — if Safari and iOS fbc coverage is a fraction of Chrome's, tracking prevention is the culprit, not your code. Run a manual trace. And in DevTools, check Application, then Cookies, on the landing page: do _fbc and _fbp exist, what is the expiry, and what is the domain scope?

The fix is the server-side vault pattern. At landing, on the first request, read fbclid from the query string; if _fbc exists, read it, and if not, construct it using the time you first observed the fbclid, never modifying the fbclid string itself. Write it as a server-set HTTP cookie in the response header rather than through document.cookie, scoped to the apex domain, SameSite Lax, Secure, with a 90-day expiry. Server-set cookies are not subject to the seven-day JavaScript cap, which makes this the single most effective mitigation available. Refresh it on every pageview so the clock restarts.

Then the part almost everyone skips. At the start of the funnel, not at purchase, write fbc, fbp, the browser's IP address, the browser's user agent and the event source URL onto the lead record in your CRM. Store both first-touch and latest-touch. Carry them forward onto the order record at order creation. When the CRM fires the purchase hours or days later with no browser context, it reads those values off the order record rather than off a cookie that no longer exists.

Never send a fabricated, empty or null fbc. Omitting the parameter is strictly better.

9. The IP Address and User Agent Are the Server's, Not the Browser's

Medium-high probability, fifteen minutes, and a classic server-to-server bug.

Meta's documentation notes that client_ip_address and client_user_agent are added automatically for browser events but require manual configuration for server-side events. The near-universal mistake is that the backend passes its own values. Every event then looks like it came from one datacenter IP with a user agent like python-requests or axios.

This is invisible. The fields are populated, so nothing errors. It destroys matching, and it is exactly the kind of traffic naive bot scoring flags.

While you are in there, verify the hashing rules. fbc, fbp, IP and user agent must be sent plain and never hashed — hashing them is a silent killer because the field still looks populated. Conversely, email, phone, first name, last name, zip, city, state, date of birth and gender must be normalized and then SHA-256 hashed. Lowercase and trim the email. Phone numbers go digits-only with the country code, so 18015551212 rather than (801) 555-1212.

To check, log one outbound payload and read the two fields. If the IP is constant across all events, or the user agent is an HTTP library string, that is the bug. Diagnostics will often flag it as "Server sending invalid match key parameters."

10. Probabilistic Identity Stitching Cannot Bridge Your Click-to-Purchase Lag

Medium probability if you are relying on it.

Middleware platforms typically stitch a web visitor to a later backend purchase through layered signals: a first-party server-set cookie, provided identifiers such as an external ID or email, probabilistic identity recovery, and a visitor ID passed in the URL.

The probabilistic layer has strict documented limits. Default match windows are often 60 minutes. It is skipped entirely when more than three distinct visitors share an IP, which kills it on mobile carrier NAT — a large share of paid social traffic. It never works across devices or networks. And it is silently disabled if request context is excluded on the source.

If your backend purchase events lack a stored visitor ID and lack an email, and you are leaning on probabilistic recovery to bridge a multi-day gap, most conversions will land unattributed.

Use the deterministic path instead. Capture the visitor ID from local storage at the point of form submission, store it on the customer and order record, and send it back on the backend conversion. Belt and braces: always attach the hashed email too, so the platform can merge on that if the visitor ID is missing.

11. Low Event Match Quality From Missing or Scrubbed Identifiers

Real, but usually a contributor rather than the cause.

Practitioner benchmark bands, and note that Meta publishes no official threshold, so anyone quoting a Meta-blessed number is wrong: under 4.0 is poor, 4.0 to 5.9 is unstable, 6.0 to 7.9 is the floor for reliable optimization, 8.0 to 8.9 is the target for Purchase, and above 9 you hit diminishing returns.

Do not chase the score. Chase volume. Triple Whale's framing is the right one — an account with a match quality of 9 that captures 92 percent of actual conversions has a bigger problem than an account with a 6 that captures 99 percent. If your symptom is a coverage problem, fix what is missing before polishing what is arriving.

There is a structural tension worth naming honestly if you operate under health privacy rules. The identifiers that raise match quality most — email, phone, name, city, state, zip — overlap heavily with the HIPAA Safe Harbor identifiers you are trying not to disclose. A compliant setup sends roughly one to two match keys where a typical ecommerce setup sends six to nine. Some match-rate loss there is structural rather than a tooling problem. Every vendor in this space markets compliance without losing conversions, and none of them publishes the delta. Treat any such claim as unsupported.

One more trap: Meta rejects certain user data combinations as insufficient even though they look populated. City plus country plus state plus zip plus gender plus user agent alone. Date of birth plus user agent alone. First name plus gender alone. A geo-only enrichment layer can produce a non-trivial score while the event remains unmatchable.

The fix is to get hashed email and phone above 95 percent coverage on purchases, since you have that data at checkout. Send an external ID consistently across browser and server. Verify normalization happens before hashing, and that nothing is double-hashed.

12. Limited Data Use Applied Globally Rather Than to Covered States

Low probability, fifteen minutes, worth ruling out cheaply.

Setting data_processing_options to LDU tells Meta to process the event under its service-provider role. Meta's own documentation warns that retargeting and measurement capabilities will be limited, without defining what that means operationally. Practitioner consensus is that conversions still count and still feed optimization, with the restriction landing on audiences and personalization. But Meta's phrasing is real, official and unexplained, so it is not safe to describe LDU as definitively measurement-neutral.

To check, log a raw payload and look for data_processing_options. On the browser side, open DevTools, filter network requests to facebook.com/tr, and look for dpo=LDU. If it appears on traffic outside covered states, it is applied globally and misconfigured.

13. Aggregated Event Measurement or an Unverified Domain

Largely obsolete for web in 2026. Sixty seconds to rule out.

Meta dismantled web AEM configuration through 2025. The manual eight-event ranking, the standalone AEM tab, Value Sets, domain verification for event prioritization, and conversion-domain selection at ad creation are all gone. Meta aggregates eligible web events automatically now. The eight-event model still applies to iOS app campaigns and to accounts Meta has not migrated.

A lot of 2026-dated blog content still recycles the old advice. Check only to rule out: does an AEM or Web Event Configuration section still exist for your dataset? If it does, the account is unmigrated and Purchase should be ranked first. Verify the domain in Business Settings under Brand Safety anyway. It costs nothing.

The Trace Protocol

Three phases. Phase 1 is the one that changes the conversation, because it converts an open-ended hunt into a targeted one.

Phase 1: Establish the Shape of the Gap

Half a day, before touching any code.

Confirm the restriction tier and screenshot it, since it gates everything downstream. Pull your backend orders for a completed 14-day window ending at least seven days ago, which avoids both reporting lag and click-date backfill. Pull Events Manager purchase events received for the same window. Pull Ads Manager purchases for the same window with all three attribution columns: click-through, engage-through and view-through. Then segment the backend orders by first-time versus repeat, and ad-attributable versus not.

Those four numbers localize the problem immediately using the three-numbers rule above.

Phase 2: Trace One Known Conversion End to End

Click a live ad and record the full landing URL including the fbclid. In DevTools, confirm _fbc and _fbp exist, and note the expiry and domain scope. Walk the entire funnel, re-checking after each step that the cookie survived — this is where a custom multi-step funnel breaks. Complete a real test purchase and record the order ID.

Then follow it. In your middleware, find that event and open its dispatches: did it go to Meta, green or errored? On the payload, verify that the event ID is unique and matches any browser-side ID, that event_time is in seconds UTC and recent, that action_source and event_source_url are present, and that user_data contains hashed email and phone plus fbc, fbp, and the user's real IP and user agent. On Meta's response, confirm HTTP 200 with one event received and no warnings, and record the fbtrace_id. Re-send with a test event code and read the Test Events view, which is Meta's own parsed view of what it actually received — and note that if this view is degraded, that is itself a restriction signal. About twenty minutes later, confirm it appears in Events Manager activity. Then 48 to 72 hours later, confirm it appears in Ads Manager against the campaign, on the click date rather than the purchase date.

Whichever step it dies at is your root cause. Run it three or four times across Chrome, Safari desktop and Safari on iOS to catch tracking-prevention failures.

Phase 3: Systematic Sweep

Resolve every Diagnostics warning, prioritizing "Same event ID received for many event instances," which is catastrophic, and "Event missing some deduplication parameters." Check parameter coverage on Purchase, with email and phone above 95 percent and fbc as close to its computed ceiling as possible. Spot-check event ID uniqueness with twenty order IDs. Audit datasets with Pixel Helper and a full enumeration in Business Settings. Audit the token. And stand up alerting on non-200 Conversions API responses, which given all-or-nothing batch rejection is usually the highest-value engineering task on the entire list.

How to Reconcile Honestly

Getting this wrong is how a healthy setup gets diagnosed as broken, and how a broken one gets declared fine.

  • Use a closed window ending at least seven days ago. Never reconcile recent data.
  • Compare on Meta's date basis. Meta's rows key to ad delivery date and your orders table keys to order date. They will never tie row by row, so compare totals across 14 to 30 days and let the edge effects wash out.
  • Normalize the time zone. Convert backend timestamps into the ad account's time zone before bucketing.
  • Pin the attribution setting explicitly and hold it constant. Include engage-through for any period after March 2026.
  • Compare against ad-attributable sales, not total sales. Exclude repeat purchases, lifecycle reactivations, organic, direct and branded search.
  • Build a deterministic join with the event ID as primary key and click identifiers and UTMs as fallback. Target at least 95 percent of attributed orders carrying a valid join key.
  • Handle refunds as explicit line items, including a separate row for refunds issued this period against prior-period orders.
  • Accept that exact reconciliation is impossible. Meta uses modeled conversions for opted-out iOS users, and you cannot distinguish modeled from real in Ads Manager. For truth, triangulate with incrementality testing — geo holdouts and conversion lift studies — not with reconciliation alone.

What "Fixed" Actually Looks Like

Setting this expectation up front matters, because the wrong target wastes real money.

Measure Healthy range Note
Meta-attributed purchases vs. total sales 55–65% Normal, not broken
Meta vs. properly filtered ad-driven sales Within 10–15% This is the number to hold a vendor to
Orders carrying a usable tracking identifier 95%+ Directly fixable
Pixel and Conversions API duplicate rate Under 2% Directly fixable
Event Match Quality on Purchase 6.0 floor, 7.5+ good Chasing 9 is wasted effort
Hashed email and phone coverage on purchases Above 95% Directly controllable
Meta API errors logged and alerted on 100% Often currently zero

100 percent is not achievable and nobody should promise it to you. Attribution windows exclude some conversions by design. People switch devices. Sales that were not driven by ads legitimately should not count. And the January 2026 window change removed a portion permanently.

If you are currently inside those ranges, the honest answer is that the system is working and the original reconciliation was comparing two different things. If you are well below them, there is a real problem and it is worth paying to fix. The Phase 1 diagnosis tells you which, usually inside a week.

The Architecture to Land On

Independent of what the diagnosis turns up, this is where most accounts with this symptom should end up.

  • Multi-point conversion tracking. If you track purchase only, you have no way to see where the funnel breaks rather than just that it does, no fallback optimization target if Purchase is restricted, and no way to tell which creative drives form fills versus which drives sales. Add funnel-start, lead captured and payment as distinct events.
  • Fire the conversion on payment capture, not approval or fulfillment, and send downstream states as separate reporting-only events.
  • A server-side vault for click identity: capture the click identifiers, IP and user agent at funnel start, persist them to the customer record, and replay them at purchase.
  • A clean payload spec, written field by field. No condition or product-sensitive names, no inherited content categories, sanitized event source URLs, and coded product identifiers rather than descriptive ones. This is worth doing whether or not you are restricted.
  • Error visibility. Non-200 response logging and alerting, plus a weekly dispatch-failure review in whatever middleware you use. Most of these platforms have no proactive alert for dispatch failures or volume drops, so somebody has to go look.
  • Measurement that does not depend on Meta being right. Blended acquisition cost and revenue-to-spend reporting off your own database, plus geo holdout tests for true incrementality. In a regulated vertical, in-platform return on ad spend will always be partial — and if you are planning to scale into channels that are harder to measure, you want that infrastructure before you get there, not after.

A Compliance Note Worth Reading Twice

This is not legal advice, and if you are in a regulated vertical you should have this conversation with counsel rather than with a marketing vendor. But it bears directly on the architecture decisions above, so it would be a disservice to leave it out.

Solving Meta's restriction problem is not the same as solving HIPAA. Even with a generic event name and a scrubbed payload, sending a hashed email plus a conversion signal from a health-services system still tells Meta that a specific identified person is a patient of that service. The identity of the sender supplies the medical context regardless of how neutral the event name is. Meta does not sign business associate agreements, including for the Conversions API.

The enforcement context is not theoretical. In July 2026 the FTC sued Hims & Hers, joined by California and Utah, over sharing health data with Meta and others through exactly this surface. Two vectors were named: tracking pixels and customer list uploads for Custom Audiences. A privacy policy disclosure was not accepted as a defense. Earlier actions against GoodRx, BetterHelp and Advocate Aurora established the pattern, and cumulative healthcare tracking liability now exceeds $100 million.

Practically, this means any tracking architecture in a regulated vertical should be reviewed by whoever owns your compliance posture before it goes live. It is also a good reason to have a competent third party document what is being sent and why.

Frequently Asked Questions

Does the Conversions API get around Meta's health data restrictions?

No. This is the most common misconception in this area. If your domain is classified into a restricted tier, sending Purchase server-side is filtered identically to a browser pixel. Multiple vendors state this explicitly, and the Conversions API solves a different problem — browser-side signal loss — not a policy one.

How much of a gap between Meta and my database is normal?

Against total sales, 55 to 65 percent is a normal capture rate rather than evidence of a bug. Against properly filtered ad-attributable sales, within 10 to 15 percent is the number worth holding an implementation to. These are practitioner benchmarks; Meta publishes no official capture rate.

Can I appeal a data restriction classification?

Sources conflict. Appeals appear to exist in Events Manager for genuine misclassification, with a review period of several days and a lockout if rejected, but success rates are reportedly low and the lightest tier may not be appealable at all. Plan as though the appeal fails and build the architecture that works either way.

What is the single fastest check?

Open your middleware's event log, find one purchase event, and look at whether it was dispatched to Meta and whether Meta accepted it. That one view separates "we never sent it" from "Meta did not count it," and those two have completely different fixes.

Why did my conversions drop in early 2026 with no other change?

Two Meta changes. On January 12, 2026, view-through attribution windows were removed from the Ads Insights API. On March 3, 2026, click-through was redefined to require an actual link click, moving engagement-based conversions into a separate capped column. Either can produce a step-change drop with no underlying cause in your implementation.

Where to Start

Do not start with code. Get the three numbers, confirm your restriction status, and check whether a test event code was left on. Those three steps take under an hour combined, cost nothing, and in our experience account for the majority of cases.

Then trace one real conversion end to end. Whichever step it dies at is your answer, and everything after that is targeted work rather than an open-ended hunt.

And if the honest answer turns out to be that the implementation is correct and the original reconciliation was measuring two different things — that is a legitimate result, and a good vendor will tell you so.

Not Sure Where Your Conversions Are Dying?

We run this exact diagnostic as a bounded engagement — restriction status back the same day, the full findings inside a week, and a plain answer if it turns out nothing is broken.

Book a Call