Our leads come in over WhatsApp, from Click-to-WhatsApp ads — no landing page, no pixel, no UTM parameters. What connects "this person paid us" back to "this ad click" is a field Meta attaches to the first inbound message: the click ID, ctwa_clid. If it's there, you can eventually tell Meta which clicks became customers. If it's missing, that loop never closes.

Ours, we believed, was not there. A real test payload we'd inspected weeks earlier had the ad headline, the creative, the source ID — everything except the click ID. So we did what everyone does: opened the checklist. Is the Facebook page owned by the same Business Manager as the WhatsApp number? Is the number linked to the page? Should we move an asset between Business Managers? Our messaging provider has a whole guide page for this failure mode, which tells you how common it is.

Then, mid-checklist, a simpler question occurred to us: we store every lead's referral data. Why are we asking Meta's dashboards what arrives, when our own database has been keeping score the whole time?

One query instead of twenty settings screens

Every inbound from an ad carries a referral object, and we'd been persisting its fields on the contact since day one — including ctwa_clid, populated or not. So the question "is Meta sending it?" collapses into a query over our own table. Per day: how many ad leads arrived, and how many carried a click ID?

contacts total:        91
ad-attributed:         57
with ctwa_clid:        43

per-day: ad leads (missing click id)
  06-06:  2  (2 missing)
  06-07:  2  (0 missing)
  06-08:  8  (2 missing)
  06-09: 30  (8 missing)
  06-10: 15  (2 missing)

That query answered the question more directly than the settings screens had all week. Two findings, both invisible from any dashboard:

It switched on, on a specific day. Before June 7: zero click IDs. From June 7 onward: flowing steadily. That's not noise — that's a step change, the kind a configuration fix produces. It lined up with the page↔number link being completed on the Business Manager side. The "missing" click ID we'd been investigating was real, but it was old news — the problem had already been fixed, and we were debugging a snapshot.

It isn't 100%, even when it works. After the fix, roughly one lead in five still arrives without a click ID — and the misses are scattered across ads that also produce hits. That pattern matters: if one ad always failed, it would point at a setup problem. Misses spread evenly across working ads point at the platform — some clicks just don't carry the ID, and more checklist work was unlikely to recover them. Knowing that stopped us from spending another week chasing the last 20%.

The id you do get may not be the one you expect

One adjacent gotcha from the same work, worth writing down: the referral's source_id is not reliably the ad ID. It's often the ID of the creative's underlying post. If you later join leads to spend from the Marketing API — which reports per ad — a naive join on ad ID silently drops leads. The fix is to also pull each ad's effective_object_story_id and accept a match through either identifier. Our join matched cleanly only after we taught it both.

Treat attribution claims like code: verify from your own data

The general lesson isn't about Meta. It's that attribution debugging has two very different modes. One is archaeology in someone else's UI: settings screens, ownership diagrams, help-center checklists — all describing what should happen. The other is looking at what did happen, in data you already hold.

The second mode is faster and it's the only one that can answer the questions that matter: when did it break or start working (look for the step change), and how much of it works (count the misses). Both answers were sitting in a table we'd been writing to for weeks, waiting for someone to ask.

If your leads arrive over a messaging channel, persist the full referral from day one — even the fields you don't use yet. The day you need to know whether the platform is holding up its end, the answer will be one query away.