You can send Meta a perfectly formed conversion event and still watch your Event Match Quality sit at "Poor." Usually the culprit isn't the event. It's what's missing from it. Two of the most valuable match signals Meta accepts don't come from a form field at all. They come from cookies: _fbp and _fbc.
They matter most for exactly the visitors that are hardest to track: the anonymous top-of-funnel traffic that clicks an ad, lands on your page, and hasn't given you an email yet. Capture these two cookies and you can tie that person's eventual conversion back to the ad click you paid for. Miss them, and Meta is guessing.
Here's what each cookie is, how long it lasts, how to capture it without dropping it on the floor, and the mistakes that quietly cost you match quality. I checked every rule below against Meta's own documentation in November 2026.
What _fbp and _fbc actually are
Both are first-party cookies: set on your domain, and readable by JavaScript running on your domain. Neither contains personal data. They're opaque identifiers Meta uses for matching, and Meta's docs call them the Browser ID (fbp) and the Click ID (fbc).
_fbp, the Facebook browser ID. When the Meta Pixel loads and no _fbp cookie exists yet, it creates one, then reuses it on later visits. It identifies the browser, and it exists for essentially everyone once the Pixel has run, ad traffic or not. Its value looks like this:
fb.1.1720598400000.1834267592
That's four dot-separated parts: the fb prefix, a subdomain index, the creation time in milliseconds, and a random number the Pixel generates so every _fbp is unique. The subdomain index records which level of your domain the cookie was set on: 0 for com, 1 for example.com, 2 for www.example.com.
_fbc, the Facebook click ID. This one is the prize. When someone clicks your Meta ad, Meta appends an fbclid parameter to your landing URL:
https://yoursite.com/offer?fbclid=IwAR2xY...
The Pixel reads that fbclid and stores it as the _fbc cookie, in the same format:
fb.1.1720598400000.IwAR2xY...
The difference is in the last segment. _fbp ends in a random number, _fbc ends in the actual click ID. That click ID is what lets Meta connect a conversion to a specific ad, ad set and campaign, which is why _fbc is the higher-value of the two.
_fbp vs _fbc at a glance
_fbp | _fbc | |
|---|---|---|
| Meta calls it | Browser ID | Click ID |
| Identifies | The browser | The ad click |
| Exists when | The Pixel has run once | The visitor arrived with fbclid in the URL |
| Format | fb.1.<creation time>.<random number> | fb.1.<creation time>.<fbclid> |
| Lifetime | 90 days, per cookie scanners | 90 days, as Meta recommends |
| Hash before sending? | No | No |
| Conversions API field | user_data.fbp | user_data.fbc |
On lifetime: Meta's docs recommend a 90-day expiry for _fbc but don't give one for _fbp. Cookie scanners such as CookieYes list _fbp at 90 days as well. Safari cuts both much shorter, which is the catch covered below.
How to see them in your browser
On any page where your Pixel runs:
- Open DevTools (
F12, orCmd + Option + Ion a Mac). In Chrome, go to Application, then Cookies, then your domain. In Safari and Firefox it's the Storage tab. - Type
_fbinto the filter box. - Compare each Value with the formats above, and check the Expires column to see when the browser will delete it.
No _fbc in the list? That's normal unless you arrived from a Meta ad. It only forms when there's an fbclid to read.
Why they move the needle
Meta scores every event you send on Event Match Quality: how confidently it can tie the event to a real person. More strong parameters, higher score, better attribution and optimization.
Email and phone are great when you have them. But a huge share of ad traffic converts (or half-converts) before handing over any personal detail, and for those people _fbp and _fbc are often the only signals you've got. _fbc in particular carries the click ID, so it's the difference between "a conversion happened somewhere" and "this conversion came from that campaign." Sending both is one of the cheapest ways to lift match quality across your whole funnel.
The catch: the browser is where these cookies go to die
If capturing two first-party cookies sounds trivial, it usually is, right up until the browser deletes them. The same forces that break browser-side pixels are working against _fbp and _fbc:
- Safari's Intelligent Tracking Prevention caps script-written storage at 7 days, and JavaScript cookies at 24 hours in some cases. A visitor who clicks your ad today and converts ten days later has no
_fbccookie left, even though the conversion is 100% attributable. - Safari's Link Tracking Protection removes
fbclidfrom links opened in Private Browsing, Mail and Messages (Mapp's notes on it), so_fbcnever forms for those clicks. - Ad blockers and tracking prevention stop
connect.facebook.netfrom loading, so the Pixel never runs and neither cookie is ever set. - Cookie consent, done right, holds the Pixel back until the visitor opts in, so early in the session the cookies simply don't exist yet.
- Redirects, link shorteners and cross-domain jumps strip the
fbclidfrom your URL before the Pixel can read it, so_fbcnever forms in the first place.
Meta's docs go a step further than the Pixel does: they recommend also setting _fbc as an HTTP cookie from your server, with a 90-day expiry.
The lesson: don't assume the cookies were captured cleanly at page load and will still be there later. Capture them at the moment of conversion, and have a fallback.
How to capture them reliably
Three moves cover the vast majority of cases.
1. Read the cookies when the visitor converts, not just on landing. On your domain, at submit time, read them straight from document.cookie:
function readCookie(name) {
const match = document.cookie.match(
new RegExp("(?:^|;\\s*)" + name + "=([^;]*)")
);
return match ? decodeURIComponent(match[1]) : undefined;
}
const fbp = readCookie("_fbp");
let fbc = readCookie("_fbc");
2. Rebuild _fbc from fbclid when the cookie is missing. This is the step most people skip, and it's one Meta documents. If there's no _fbc cookie but you have the fbclid, build the value yourself as fb.1.<creation time>.<fbclid>, following three rules from Meta's docs:
- Use
1as the subdomain index when you generate the value yourself. - Use the time you first saw the
fbclidas the creation time, not the time of the conversion. - Keep the
fbclidexactly as you received it. Meta's docs say the click ID is case sensitive, so never lowercase it or change it in any other way.
So save it the moment the visitor lands, along with the time:
// On landing: remember the click ID and when you first saw it.
const fbclid = new URLSearchParams(location.search).get("fbclid");
if (fbclid && !sessionStorage.getItem("fbclid_seen")) {
sessionStorage.setItem(
"fbclid_seen",
JSON.stringify({ fbclid, at: Date.now() })
);
}
// At conversion: prefer the Pixel's cookie, fall back to the saved click ID.
if (!fbc) {
const saved = JSON.parse(sessionStorage.getItem("fbclid_seen") || "null");
if (saved) fbc = `fb.1.${saved.at}.${saved.fbclid}`;
}
sessionStorage keeps the value across pages in the same tab, which covers a landing page that links to a separate form page. For journeys that last days, store it in a cookie your server sets, as Meta recommends. And keep the fbclid alive in the first place: don't bounce visitors through a shortener that drops the query string.
3. Send them server-side, in plain text. Pass fbp and fbc in the user_data of your Conversions API event. Two rules people get wrong:
- Do not hash them. Email, phone and name get SHA-256 hashed, but Meta's customer information parameters mark
fbcandfbpas "Do not hash." Hashing them breaks the match. - Share an event ID between the CAPI event and the browser Pixel event, so Meta deduplicates them and counts the conversion once.
Sending them from your server instead of the browser takes cookie lifetimes out of the equation for that event: you read each cookie while it still exists and forward the value server to server, where no browser can expire it.
Or let Meta's library handle it. Meta's open-source Parameter Builder library reads and saves these values for you, in JavaScript on the client and in PHP, Java, Python, Node.js and Ruby on the server. The values it builds end with an extra "appendix" segment. Send those exactly as they are too.
The mistakes that quietly cost you match quality
- Hashing
_fbp/_fbc. The single most common error. They must be sent raw. - Changing the click ID. Lowercasing, trimming or "cleaning up" the
fbclidbreaks it. Meta treats it as case sensitive. - Using the conversion time as the creation time. When you build
_fbcyourself, the timestamp is when you first saw thefbclid. - Capturing only on the first page. SPA funnels and multi-step forms never reload, so a cookie value grabbed at landing can be stale or gone by the time the conversion fires. Read the cookies at submit.
- No
fbclidfallback. If you only read the_fbccookie and never rebuild it, every visitor whose cookie was blocked or expired loses their click match. - Losing
fbclidto redirects. A "click → shortener → redirect → landing page" chain often arrives with the parameter stripped, so_fbccan never form. - Firing before consent. Reading cookies that shouldn't exist yet, or setting them before the visitor opts in, is both useless and a compliance risk.
How Collectform captures them for you
Wiring all of this by hand (reading the cookies at the right moment, forwarding them unhashed, hashing everything else, deduplicating against the Pixel) is exactly the kind of plumbing that's easy to get subtly wrong. In Collectform it's built into the form:
- Read when each event fires. Every time the form sends an event (a view, a start, a submission or a button click), it reads
_fbpand_fbcfrom the visitor's browser at that moment, while the values still exist. - Saved on the response. The values are stored with each submission, so you can see them on the response in your dashboard instead of guessing from Events Manager.
- Sent the way Meta asks.
fbpandfbcgo to the Conversions API server-side in plain text, next to your SHA-256-hashed advanced matching fields, the visitor's IP address and user agent, and an event ID shared with the browser Pixel so Meta deduplicates. - Consent-aware. With the form's cookie consent banner turned on, Collectform doesn't read either cookie, or send anything to Meta, until the visitor accepts marketing cookies.
Here's the shape of the user_data block it sends, with the values shortened:
{
"user_data": {
"em": ["<sha256 of the email>"],
"ph": ["<sha256 of the phone digits>"],
"fbp": "fb.1.1720598400000.1834267592",
"fbc": "fb.1.1720598400000.IwAR2xY...",
"client_ip_address": "203.0.113.7",
"client_user_agent": "Mozilla/5.0 ..."
}
}
When you connect your Meta Pixel, Collectform loads it on the page, and the Pixel creates _fbc from the fbclid the normal way. What Collectform doesn't do yet is rebuild _fbc itself when the Pixel can't run, for example behind an ad blocker. If that matters for your traffic, the fallback in step 2 above, or Meta's Parameter Builder, covers it.
If your site runs on Next.js, the Next.js server-side tracking guide walks through the full setup.
The takeaway
_fbp and _fbc are two of the highest-leverage match signals Meta accepts, and they're the ones you're most likely to be dropping. The browser expires them, blockers kill them, and redirects strip the fbclid before _fbc can even form.
Capture them at conversion time. Never hash them. Keep the click ID exactly as Meta sent it.
Server-side CAPI with automatic _fbp / _fbc capture, deduplication and consent handling is included on every Collectform plan, from $20/month billed yearly, with a 7-day trial. Explore templates or start building with Collectform.
