Your Facebook Ads Report Is Wrong. Here Is How I Fixed CAPI Attribution.
Draft Disclaimer: Please note that this article is currently in draft form and may undergo revisions before final publication. The content, including information, opinions, and recommendations, is subject to change and may not represent the final version. We appreciate your understanding and patience as we work to refine and improve the quality of this article. Your feedback is valuable in shaping the final release.
Language Mismatch Disclaimer: Please be aware that the language of this article may not match the language settings of your browser or device.
Do you want to read articles in English instead ?
TL;DR
What: A secondary capture point for Facebook CAPI browser metadata on post-purchase pages. Why: The original checkout session often loses cookies (in-app browsers, reassigned orders), so Meta cannot attribute purchases to ads. How: Capture fbp, fbc, IP, and user agent on the invoice and COD pages using set-not-reset semantics.
The Problem Nobody Warns You About
Facebook Conversions API is supposed to be the reliable path. You fire a Purchase event from your server, Meta matches it to the ad click, your reporting stays accurate. That is the pitch.
But CAPI does not just need the event. It needs browser metadata to make the match. Four fields in particular: the fbp cookie (Facebook's browser ID), the fbc click ID (which ties back to the specific ad click), the client IP address, and the user agent string.
Without these, Meta receives your purchase event and has no way to connect it to a person, a click, or a campaign. The event shows up in Events Manager as "unmatched." Your ad set thinks it got fewer conversions than it actually did. And the optimization algorithm, the part that decides who sees your ads next, trains on incomplete data.
I ran into this on my ecommerce store. Purchases were happening. CAPI events were firing. But a chunk of them had no browser metadata attached.
Why Browser Metadata Goes Missing
Two scenarios cause this. Both are common and easy to miss.
Scenario 1: Facebook's in-app browser on iOS. A user taps your ad inside the Facebook app. Facebook opens the link in its own in-app browser, not Safari. This browser has a different cookie jar. The fbp cookie your site set earlier? Not there. The fbc parameter from the ad click URL? It might get captured on the landing page, but if the user navigates to checkout, the value may not persist across page loads in the in-app browser. iOS privacy restrictions make this worse with each update.
This is not a rare edge case. A significant portion of mobile ad traffic comes through the in-app browser. If your audience is on iPhones (and in most markets, a big chunk is), this affects a real percentage of your conversions.
Scenario 2: Reassigned orders. In my business, an admin sometimes reassigns an order to a different customer. The original customer checked out and we captured their browser metadata. But the order now belongs to someone else. The CAPI fields on the order point to the wrong person, or worse, the new customer never went through checkout at all, so the fields are blank.
In both cases, the result is the same. The Purchase event fires with empty or incorrect metadata. Meta cannot match it.
The Fix: Capture on Post-Purchase Pages
I needed a page that two things are true about. First, the actual paying customer visits it. Second, it loads in a normal browser context where cookies are accessible.
Two pages fit perfectly: the invoice page and the cash-on-delivery (COD) verification page.
The invoice page is where the customer goes to see their purchase confirmation and download their receipt. The COD verification page is where the customer confirms their identity before delivery. In both cases, it is the real buyer, in their own browser, with full cookie access.
On these pages, I added a small piece of logic. When the page loads, capture the current visitor's fbp cookie, fbc click ID, client IP, and user agent. Then write those values to the order record.
The implementation is straightforward. On the server side, when the controller renders the invoice or COD page, it reads the browser metadata from the request and updates the order. No JavaScript required. The cookies come from the request headers. The IP and user agent are right there on the server.
Set-Not-Reset: The Key Semantics
Here is the critical detail. You do not want to blindly overwrite the CAPI fields. If the original checkout captured valid metadata (the happy path where everything worked), you want to keep it. That data is closer to the ad click and more accurate for attribution.
The rule is simple: only fill in fields that are null or empty. Never overwrite a field that already has a value.
I call this "set-not-reset" semantics. For each of the four fields, check if the existing value is empty. If it is, write the new value. If it already has data, leave it alone.
This means the backfill only activates when something went wrong with the original capture. Orders that came through a normal checkout flow keep their original metadata untouched. Orders where the in-app browser lost cookies, or where an admin reassigned the order, get patched with the best available data.
Is the backfilled data as good as the original? Not always. The fbc click ID from the invoice page visit is not the same as the one from the original ad click. But having approximate metadata is far better than having none. Meta's matching algorithm uses probabilistic matching across multiple signals. Even partial data improves match rates significantly.
Why This Matters for Your Ad Spend
When CAPI events do not match, the consequences compound.
First, your conversion count in Ads Manager is lower than reality. You see 8 purchases when 12 actually happened. That makes your cost per acquisition look higher than it is. You might cut budget on a campaign that is actually profitable.
Second, the optimization algorithm gets worse signal. Meta's algorithm decides who to show your ads to based on who converts. If it cannot see 30% of your conversions, it is building an incomplete picture of your ideal customer. It will target less effectively over time.
Third, lookalike audiences built from your conversion data will be less accurate. Fewer matched events means a smaller, less representative seed audience.
The fix is small. A few lines of code on two pages. But it plugs a hole that silently degrades your entire ad funnel.
The General Pattern
This is not just about Facebook CAPI. The pattern applies to any server-side tracking where you need browser context.
If your conversion event happens after a point where cookies might be lost, add a secondary capture on a page you know the real buyer will visit. Invoice pages, confirmation pages, onboarding screens, account setup flows. Any authenticated page where the user shows up in their own browser.
Use set-not-reset semantics so you never degrade good data. And capture as early as possible after the purchase, because the closer you are to the conversion event, the more relevant the metadata is.
I have started thinking about this as a general rule: never rely on a single capture point for tracking metadata. Browsers are unpredictable. In-app webviews are hostile to cookies. Users switch devices. If the data matters, capture it in more than one place and let the best value win.