Skip to content

Match quality dropped

The costliest match-quality failure is invisible: a wrongly lowercased name hashes to something that matches nobody, and nothing reports an error.

updated

Match quality falls when events arrive carrying fewer usable identifiers than they could — or carrying them in a form the platform cannot read. The second kind is worse, because there is no error anywhere: a wrongly normalised value still hashes to a perfectly valid-looking 64-character string that simply matches nobody.

The one that broke everything quietly

Ad platforms require customer data to be lowercased before hashing. PHP’s plain strtolower() is byte-wise, so it leaves every uppercase non-ASCII character exactly where it found it:

MĂDĂLINA    → mĂdĂlina      (should be mădălina)
BUCUREȘTI   → bucureȘti     (should be bucurești)
IONUȚ@…     → ionuȚ@…       (should be ionuț@…)

Every one of those hashes to a valid-looking value that corresponds to no real person. The event is accepted, the delivery succeeds, and the match rate quietly falls. Anyone whose name or city contains a Romanian, Turkish, Greek or Cyrillic character was affected — and because the same normalisation feeds Meta and TikTok, both platforms were affected simultaneously.

PixelCapi now uses multibyte-aware case folding everywhere, including in the browser-side advanced matching data, which is the copy easiest to overlook. Region and country fields were never affected: they strip non-ASCII characters before folding.

If your match quality dropped and then recovered after an update, this is very likely what you were seeing.

When mbstring is missing

Some hosts do not have PHP’s mbstring extension. The rule is three-way:

  • Extension present — correct multibyte lowercase.
  • Extension absent, value is pure ASCII — the plain function is byte-identical, so nothing is lost.
  • Extension absent, value contains non-ASCII — the field is omitted, logged once per field, and reported in Diagnostics as a missing requirement.

Missing beats wrong. A missing field lowers your match rate honestly; a wrongly folded one lowers it while looking fine.

Phone numbers

Meta and TikTok want the same number in two renderings — E.164 with the plus for TikTok, without it for Meta. PixelCapi normalises per platform.

The country code comes from the order’s billing country first, then a configurable default, then your WooCommerce store base country. If none of those yields a country, the number is omitted rather than guessed: a national-format number with an assumed country code is a hash that matches the wrong person.

That last fallback is worth knowing about: because the store base country is used when nothing better is available, some numbers rest on a stated assumption rather than on order data. Coverage is higher; certainty for those specific numbers is lower.

What actually moves the score

In rough order of impact:

  1. Correct normalisation. Getting this wrong makes every other improvement pointless.
  2. More identifiers per event. Email and phone are the heavy hitters; city, region, postcode and country add to it.
  3. A durable identifier for guests. Logged-in customers are easy. Guest checkouts are where a stable external identifier makes the largest difference — which is what the Identity Store is being built to provide.
  4. Click IDs. These are sent raw, not hashed, and the plugin will reconstruct a click ID from the URL parameter when the cookie is missing.

What is never hashed

IP address, user agent and click identifiers are sent raw, because the platforms require them in that form. Everything that identifies a person — email, phone, name, city, region, postcode, country, external ID — is SHA-256 hashed on your server before it leaves.

Was this page helpful?