CAPI · marketers · engineers · PMs
A conversion with nobody on it
Meta shows Event Match Quality. Other vendors bury the same idea in diagnostics. The number is not a vanity metric. Optimization only trains on events it can attach to a person.
Strong identifiers
Click ids score highest: fbc built from fbclid, gclid / gbraid / wbraid, ttclid, li_fat_id, msclkid. First-party cookies the pixel minted (_fbp) are next. Hashed email and phone after the vendor's normalization. external_id you both already know, hashed on Meta.
These have to be stored at landing and replayed at conversion. A pretty redirect that strips the query deletes the best match you will ever have. CAPI cannot recover a click id you never wrote down.
Hash the customer fields, not the click
On Meta, em, ph, fn, ln, ge, db, ct, st, zp, country, and external_id are SHA-256 hex. vendor.meta-conversions-api.body.user_data.em.invalid is a raw address or a non-hex string in that slot. A digest of the wrong input (mixed case email, local phone format) is a 64-character miss. Uppercase hex of the right input still matches; Meta lowercases the input, not the digest.
Do not hash fbc, fbp, fbclid, or ttclid. They are not customer information parameters. A helper that maps every user_data string through SHA-256 deletes the strongest identifiers you had.
Weak identifiers, sent in the clear
client_ip_address and client_user_agent help when the strong ids are missing, which is most PageView events. They MUST NOT be hashed. vendor.meta-conversions-api.body.hashed_plaintext_field, and the same rule on Pinterest, Snap, and TikTok (context.ip / context.user_agent), flag a 64-character hex in those slots.
Send the client values, not your NAT. A load-balancer IP is worse than omitting the field: you are teaching the graph that every conversion comes from one office. LinkedIn still requires user.userIds as a key, even as an empty list, when matching rides on other blocks.
fbp and fbc have a shape
Meta documents fb.N.timestamp.value. _fbp looks like fb.1.1770000000.random. _fbc looks like fb.1.1770000000.fbclid. vendor.meta-conversions-api.body.user_data.fbp.invalid and .fbc.invalid fire when the prefix, the parts, or the punctuation are wrong. Sending the raw fbclid in fbc is a miss. Hashing them is a miss.
If the pixel never ran, you can still build fbc from the landing fbclid in that format. You cannot invent an honest _fbp; that random is the browser id the pixel minted.
Do not chase 10.0 on PageView
You will not have email on every PageView. Aim for high quality on Purchase and Lead, and accept that PageView is mostly cookies and IP. Blocking the pixel until you have an email is how you starve the model of the volume it uses to find converters.
Pinterest CAPI requires user_data with at least em, hashed_maids, or client_ip_address (vendor.pinterest-conversions-api.body.user_data.user_needs_an_identifier). An empty user_data object is not a legal Pinterest event. Meta requires user_data on every server event (vendor.meta-conversions-api.body.user_data.missing) but does not require email inside it.
Over-hashed IP
A SHA-256 hex in client_ip_address looks like privacy and matches nobody. Split the list: identifiers hash, IP and UA do not.
{
"user_data": {
"em": "f660ab912ec121d1b1e928a0bb4bc61b15f5ad44d5efdc4e1c92a25e99b8e44a",
"ph": "a3c256e7e39a9bdd83b9d57ae84ea2e8a9a8e4c2c0d1f6b8a9c1d2e3f4a5b6c7",
"fbp": "fb.1.1770000000.1234567890",
"fbc": "fb.1.1770000000.IwAR0examplefbclid",
"client_ip_address": "203.0.113.10",
"client_user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)"
}
}
// Wrong: hashed IP / UA. vendor.meta-conversions-api.body.hashed_plaintext_field
{ "client_ip_address": "64-char-sha256-hex", "client_user_agent": "64-char-sha256-hex" }
Check the artifact
Paste the pixel URL or JSON body into the
playground. Same engine as
pixellint validate. Nothing leaves the tab.