One Event, Three Machines — three gardens claim it, one hub decides 1 THE HUB PATTERN GOOGLE · META · OPENAI One Event,Three Machines. One hub decides who gets to claim it. DECLARED INFERRED MID-REASONING YOUR HUB gclid · fbclid · oppref ONE COUNT Illustrative month: 450 leads claimed by three platforms, resolved once in one hub. nofluffadvisory.com Evgeny Popov
How-To

One Event, Three Machines: Orchestrating Conversions Across PMax, Advantage+, and OpenAI Ads

· 50 min read
The gist

A complete signal-architecture guide for running Google PMax/Search, Meta Advantage+, and OpenAI Ads together — not as three separate campaigns, but as three complementary intent surfaces (declared, inferred, mid-reasoning) feeding one first-party hub. Covers the within-platform dedup pattern (one event_id, two legs, one counted conversion), the harder truth that cross-platform dedup doesn't exist and never will, and the hub-and-spoke fix: capture every garden's own click identifier (gclid, fbclid, oppref) into a record only you control. Closes with an incrementality tease — an accurate count still isn't proof of causation — pointing toward agile MMM as the rung above orchestration. Grounded in a real PMax failure (103 clicks, 0 leads) and a live dedup bug caught and fixed mid-campaign.

On this page

You built the landers. You installed the pixels. Now three algorithms are each trying to learn from the same lead — and if you wire them naively, they will double-count it, lose it to an ad blocker, or optimize toward the cheapest possible garbage. This is the signal architecture that makes one conversion land once, everywhere, cleanly — why the three platforms are worth running together rather than picking a favorite — and the harder question underneath all of it: even a perfect count doesn’t tell you what actually caused it.


The trap nobody warns you about

Here is the naive version of “set up conversion tracking,” the one every platform’s onboarding wizard walks you into:

You launch a Performance Max campaign because Google’s UI nudges you there. You install the Meta Pixel by pasting a snippet in your <head>. You add the new OpenAI pixel the same way. You point all three at your homepage because that is the URL you have memorized. You pick an objective that sounds right and hit launch.

Four things are now true, and all four are costing you money:

  1. Your ad platform is optimizing toward a goal you didn’t set. PMax, given a conversion action that barely ever fires, still has to spend the budget — and it spends toward whatever inventory satisfies that thin signal, which on Display and YouTube skews toward bot-adjacent junk and curiosity taps. I ran exactly this: four days, 103 clicks, a $0.33 blended CPC, and zero leads. The search terms were influencer names and people hunting free tools. The machine did roughly what I told it to: find the cheapest way to look busy against a conversion event that essentially never happened.

  2. Your conversions are landing where the pixel isn’t. If the ad points at your homepage but the pixel and the form live on a dedicated lander, you are paying for traffic that cannot convert, because there is nothing there to fire the event. I nearly shipped this one too — the ad’s destination URL was the site root, and the conversion apparatus lived one directory deeper. Empirically, the homepage had no pixel, no loader, no form. Every click would have been untracked.

  3. When they do fire, they fire twice. A browser pixel and a server-side API sending the same conversion with no shared identity is two conversions as far as the algorithm can tell. It over-credits, over-bids, and learns from a number that is double the truth. I hit a subtler version of this same bug days into a live campaign, when the shared id landed in the wrong argument of a documented function call — no error, values in the dashboard, everything looked fine, and it was quietly about to double-count every conversion. More on that below.

  4. You have no neutral scorekeeper, and — this is the part most guides stop short of — you never will, not across platforms. Each of the three will proudly claim the same lead, and none of them will ever compare notes with the other two. That isn’t a gap in the tooling. It’s the business model.

None of these are exotic. They are the default. Getting past them is not about knowing where the buttons are — the buttons move every quarter anyway. It is about holding the right mental model, for what’s happening inside one platform and for what’s structurally impossible between three of them.

The mental model: four layers, not three platforms

Stop thinking “Google, Meta, OpenAI.” Start thinking in layers, because the platforms are just three consumers of the same underlying event.

Layer 1 — The event. One real thing happened in the world: a person gave you their name and email to book a session. That is the atom. Everything downstream is a representation of this one atom. If you ever have more representations than atoms, you have a bug.

Layer 2 — Measurement (your truth). This is GA4, or your CRM, or both. Its job is to count what actually happened, accurately, once, with no incentive to flatter anyone. Measurement is not an ad platform and must never be confused with one. When the three activation platforms disagree — and they will — measurement is the referee.

Layer 3 — Activation (teaching the machines). This is the pixels and the server-side conversion APIs. Their job is not to count, even though the platform also happens to report a count from these same events — that overlap is itself a source of the over-claiming problem, more on that later. Their real job is to teach each ad platform’s bidding algorithm what a good outcome looks like so it can go find more people like the converter. A conversion sent to Meta’s CAPI is a training example first and a reported number second.

Layer 4 — Identity (the thread that ties it together). This is the single most important and most-skipped layer, and it splits in two ways you need to hold separately. Within one platform, identity means a shared event_id so the browser and server legs collapse into one counted conversion. Across platforms, identity means something structurally different — capturing each garden’s own click identifier into a record only you control, because no platform will ever accept another’s id as proof of anything. We’ll come back to both halves in detail.

Once you see it as four layers, the whole design falls out of one question: how does a single event, born at the instant of conversion, reach every place that needs it — inside each garden, and across all three?

The moment of truth: the form submit

Everything hinges on one function — the submit handler on your lead form. This is where the atom becomes representations, and it is the only place a within-platform event_id should ever be born.

Here is the lifecycle, concretely, from the system I run:

  1. Mint once. On submit, generate a single UUID: crypto.randomUUID(). This is the event_id for this conversion, and there will never be another for this atom.

  2. Hand it to the server, inline. The form POSTs to a first-party endpoint (/api/lead, a Cloudflare Pages Function) with the event_id, the email, the page URL, and any click IDs (gclid, fbclid, oppref — more on this trio shortly) in the body. The server will fire the server-side events carrying this exact id.

  3. Stash it for the browser’s next hop. The browser pixel conversion doesn’t fire on the form page — it fires on the thank-you page, after redirect. So before redirecting, write { event_id, email } into sessionStorage. The thanks page reads it, fires the browser pixels with the same id, then clears it so a refresh or a direct visit can’t double-fire.

  4. Gate everything on a real submit. The thanks page only fires conversions if that sessionStorage record exists. Bare visits, refreshes, and bots hitting the URL directly get nothing. Your conversion signal stays clean.

That is the whole trick, for the within-platform half of identity. One id, born once, travelling two roads — one straight to the server, one through sessionStorage to the browser’s thank-you page — so that each platform, on its own, sees browser-and-server as a single deduplicated event.

ONE EVENT TWO LEGS ONE CONVERSION FORM SUBMITcrypto.randomUUID() SERVER/api/leadevent_id in the POST body sessionStorage THANKS PAGEevent_id restored, then cleared DEDUPEDsame event_id, both legs

A subtle but critical corollary: protect the signal like it’s the product, because to the algorithm, it is. Every spam lead that reaches your conversion event is a poisoned training example — you are teaching the machine that bots are your customers. The honeypot field, the time-gate (reject submits faster than ~3 seconds), and the server-side campaign allowlist are not just anti-spam hygiene. They are quality control on the data three algorithms are about to optimize against.

”Server-side” means three different things

Here is the insight that separates an operator from someone following a checklist: the three platforms do not mean the same thing by “server-side conversion,” and wiring them as if they were symmetric will cost you.

PlatformBrowser legServer legHow they reconcile
Meta (Advantage+)Pixel Lead event with eventIDConversions API (CAPI) Lead with matching event_idTrue deduplication: identical event_name + event_id from both legs collapse to one
OpenAI Adsoaiq("measure", …) with event_id in the options argumentEvents API POST with matching idSame pattern: shared id dedups the browser+server pair
Google (PMax / Search)gtag conversion + Enhanced ConversionsEnhanced Conversions for Leads and/or Offline Conversion Import — via the Google Ads API, not a symmetric CAPIIdentity matching, not event dedup: hashed first-party data and click IDs recover and enrich conversions rather than deduplicating a mirrored pair

Read that Google row twice. Meta and OpenAI give you a clean, symmetric pattern: fire the same event from the browser and the server, tag both with one id, and the platform merges them. Google is a different animal — there is no “fire the same conversion from your server and we’ll dedup it against the pixel” primitive in the Meta sense. We’ll unpack exactly what Google gives you instead in its own playbook below.

META & OPENAI — SYMMETRIC GOOGLE — ASYMMETRIC BROWSER pixel fires SERVER CAPI fires event_id ONE CONVERSION same id, two fires, one merge BROWSER pixel fires gtag CONVERSION recorded, not mirrored CRM weeks later hashed email / gclid GOOGLE ADS identity match recovers & enriches, no merge

Why three, not one: the complementarity thesis

Before the platform-by-platform detail, it’s worth being explicit about why you’d run all three rather than pick a favorite — because the honest answer isn’t “diversification” in the vague portfolio-theory sense. It’s that each platform intercepts a genuinely different kind of intent, and losing any one of them is losing a moment the other two structurally cannot recover.

Google Search is explicit, self-declared intent. Someone typed the problem into a box in their own words. It’s the smallest audience, the most expensive click, and the closest thing to “I am looking for this right now” that advertising gets. PMax spreads that same declared-intent signal across a wider Google surface (Display, YouTube, Gmail, Discover, Maps), but the Search core is still where conviction is highest.

Meta is inferred, identity-graph intent. Nobody typed anything. The interest graph knows who resembles your best customers before those people have articulated a need — Advantage+ is built to interrupt someone who fits a pattern, not to answer someone who asked. That makes it the volume and discovery engine: worse at “ready right now,” far better at finding people you’d never have found through a keyword.

OpenAI Ads is a third thing that didn’t exist a few years ago: conversational, mid-reasoning intent. The person isn’t searching a keyword or scrolling a feed. They’re already in the middle of describing their problem to a model, working through it out loud, arguably in a higher state of receptivity than either of the other two surfaces catches — because the ad arrives while they’re actively reasoning about the exact thing you sell, not before (Search) or beside (Meta) that reasoning. It’s the whole insight behind copy like “you asked AI how to grow — now ask an operator.” Sometimes that conversation is the moment a person would otherwise have opened a search tab ten minutes later; the ad meets them earlier and closer to the thought itself.

The complementarity claim, stated plainly: running only one of these isn’t focus, it’s a structural blind spot across two-thirds of how people now discover you. And because each platform’s bidding algorithm only ever sees its own slice — Google has no idea a Meta-sourced buyer converted, Meta has no idea your OpenAI campaign ran — none of them can tell you this on their own. Only you can see the whole shape. That’s why the orchestration layer described in this essay is the actual product here, not any single campaign.

Platform playbook 1: Google — PMax, Search, and the conversion goal that changes everything

Start with the conversion action, not the campaign. Before you touch a campaign, define the conversion in Google Ads (or import a GA4 key event — more below). The campaign is downstream of the goal. This ordering is the whole lesson of the 103-clicks story: a campaign without a genuine conversion signal optimizes for the wrong thing with terrifying efficiency.

PMax is a conversion-hungry machine, and it only bids two ways. Performance Max’s bid strategies are Maximize Conversions and Maximize Conversion Value, optionally constrained by a target CPA or ROAS — there is no “maximize clicks” lever to reach for. That precision matters, because it clarifies the actual failure mode: PMax isn’t choosing to buy junk clicks, it’s spending against a conversion action that essentially never fires, and with no real signal to bid toward, its spend distribution ends up looking indistinguishable from buying whatever’s cheap. Google publishes conversion-history minimums for specific bid strategies — Target CPA wants at least 15 conversions in the trailing 30 days (30 recommended), Target ROAS wants roughly 50 — but there’s no single official PMax floor; “30-plus conversions a month” is a practitioner’s rule of thumb, not a documented Google number. Below whatever floor your account is actually clearing, a plain Search campaign with a properly wired conversion goal usually beats PMax cold, because Search intent does some of the targeting work the algorithm can’t yet do from data.

My rebuild after the PMax failure, concretely: a standard Search campaign, Leads objective wired to the GA4 generate_lead key event, manual-ish CPC with a hard cap, search partners and the Display network turned off, tight phrase-match keywords, and an aggressive negative-keyword list (job seekers, courses, free tools, software names). Expectation reset to 1–3 real clicks a day and a handful of leads a month — judged on cost per qualified lead at week four, not clicks. Note that “turn off Display” is a Search-campaign lever, not a PMax one — PMax’s own junk-inventory controls are different and more limited: account- and campaign-level negative keywords, brand exclusions, and channel-level reporting/exclusions, worth configuring separately if you run PMax at all.

Enhanced Conversions is actually two different products wearing one name — and the distinction matters. Enhanced Conversions for Web augments a live browser conversion: you hash first-party identifiers (email, phone — SHA-256) and attach them alongside the existing gtag conversion event, which materially improves matching to signed-in Google accounts and recovers conversions that cookies alone would miss. Enhanced Conversions for Leads is a different animal entirely — Google’s own documentation calls it an upgraded version of offline conversion import: you feed it a hashed email later, when a lead becomes a real outcome in your CRM, with no gclid required. It’s not browser augmentation at all; it’s a down-funnel truth import keyed by email instead of a click id. Both are worth wiring, but know which one you’re reaching for. And either way, hashing and sending PII — even hashed — is a deliberate privacy posture, not a default; if your public stance is “no PII flows into our analytics,” this is a conscious exception you make at the ad-platform boundary. Decide it on purpose.

The pro move is Offline Conversion Import. Form-fills are a proxy. The real target is a booked, qualified, or won deal. Capture the click id at landing — gclid normally, or gbraid/wbraid when Google supplies one of those instead (iOS, in-app, or consent-limited traffic often arrives without a gclid) — persist it with the lead, and when your CRM says the lead is real, upload that outcome back to Google Ads. Now Google is optimizing toward revenue, not toward form-fillers — the single biggest lever most advertisers never pull. One mechanical note worth knowing: conversions imported from GA4 default to a secondary conversion action (since a 2022 change), which is the actual mechanism that keeps them out of Smart Bidding’s primary “Conversions” column and prevents silent double-counting against a native Google Ads conversion action — “pick one source of truth” isn’t just discipline, Google gives you a setting that enforces it.

Consent Mode v2 is table stakes in regions that require it: it modulates how tags fire based on user consent and lets Google model the conversions it’s no longer allowed to observe directly. There’s a real implementation choice buried in it — basic mode blocks tags outright pre-consent, while advanced mode sends cookieless pings that feed the modeling (Google’s modeling typically activates around a few hundred ad clicks per week in a given country/domain). Wire it if you serve those regions, and know which mode you’re actually running.

Platform playbook 2: Meta — Advantage+, the dataset, and clean dedup

Meta gives you the cleanest browser+server story of the three, so the discipline is about not undermining it.

One dataset, one pixel ID, everywhere. The browser pixel (init + PageView sitewide, Lead on the thank-you page) and the server-side CAPI both point at the same dataset (Meta’s current term for what used to be called a “Pixel” in Events Manager). The Lead event fires in both places, and both carry the event_id you minted at submit. Meta collapses them. If your server event_name doesn’t exactly match the browser one, or the ids differ, dedup silently fails and you double-count.

CAPI is not optional in 2026. Between Safari’s Intelligent Tracking Prevention, ad blockers, and general cookie decay, a meaningful share of browser pixel events never arrive. The server leg — fired from your backend with a hashed email (and phone/name, if you have them) plus plaintext IP address, user-agent, and the _fbp/_fbc cookies (or a _fbc reconstructed from a captured fbclid, only when one is actually present) — is what keeps your match quality and your conversion volume up. Only the identity fields get hashed; hashing IP, user-agent, or the cookies breaks Meta’s own session matching. Fire it fire-and-forget so a CAPI hiccup can never fail the actual lead. Log every response so you can see rejections; never log the token or user data. Watch Event Match Quality (EMQ) in Events Manager as an ongoing health metric, not a one-time setup check — it’s the single most actionable signal-quality number Meta gives you.

Creative enhancements now default ON — decide, don’t inherit. What used to be a separate “Dynamic Creative” toggle has folded into a “Flexible media” control under Advantage+ creative, and the individual visual/text enhancements (filters, expanded images, text rewrites) ship on by default rather than opt-in. For a brand where the specificity of the message is the point, those enhancements blur exactly what makes the ad work. Let the system mix your inputs via Flexible media if you want that, but explicitly toggle off the automatic enhancements so the machine tests your variants, not its guesses about them.

Learning phase is real and it is unforgiving. Meta’s guideline is roughly 50 optimization events per ad set per rolling week to exit the learning phase (counted across browser, CAPI, and modeled conversions together). Below that, delivery is erratic and CPLs are noisy. This has two implications: don’t over-segment (five ad sets each starving for conversions is worse than one that clears the threshold), and don’t judge performance until the ad set has had a chance to exit learning.

Aggregated Event Measurement changed underneath everyone in 2025 — most write-ups you’ll find are stale. The old model — an 8-event cap per domain, manually ranked in Events Manager — was removed for web events; Meta now auto-aggregates all eligible web events with no ranking step. That prioritized-8 model still applies, but only to iOS app campaigns now. Practically: for a web lead form, there’s no Lead event to “rank” anymore — just make sure the event and your domain are correctly set up in Events Manager, and don’t waste time hunting for a ranking UI that no longer exists for web.

Platform playbook 3: OpenAI Ads — the new machine, wired honestly

OpenAI’s ads product is new as of this writing, which means two things: the pattern is refreshingly clean, and the specifics will move — so wire what you can verify against the current docs, and hold the rest loosely.

The pixel. A small loader script (oaiq global) installs from OpenAI’s CDN and initializes with your pixel ID. Paste the exact “Setup code” from the dashboard verbatim — do not reconstruct it from memory. A third-party loader URL you guess wrong fails silently and loses every conversion; there is no error, just no data. This is the one byte-sequence you copy, never type.

The conversion — and an argument-order trap worth knowing before you hit it live. The measure call takes up to four arguments: command, event name, an event-data object, and a separate options object — oaiq("measure", "lead_created", { type: "customer_action" }, { event_id }). That fourth argument is where event_id belongs. Put it in the third (data) object instead, and the call still runs with no error, still shows up in your dashboard — it just carries no dedup key, so the browser and server legs never collapse into one. I shipped exactly this bug for a few days before catching it against OpenAI’s own docs; nothing about running it live told me it was wrong. Verify by argument position, not by “it fired.”

Get the event name right against OpenAI’s actual taxonomy, not the first plausible guess. lead_created is defined as “a user submits a lead form or requests contact” — the correct match for a name-and-email consult form. registration_completed is reserved for “a user finishes an account or event registration flow” (account creation, not a lead form), and appointment_scheduled is for an actual booked meeting or demo. It’s an easy mistake to make from an example snippet that happens to be the first one you see, and it’s cheap to fix only in the first days of a campaign, before the wrong event has accrued any learning history.

Capture oppref — it’s the third click identifier, and it completes the hub described below. OpenAI automatically appends a privacy-preserving click identifier, oppref, to your landing URL when someone arrives from an ad — the same species of thing as Google’s gclid or Meta’s fbclid. The pixel persists it into a first-party __oppref cookie automatically; capture it into your own lead record the same way you capture the other two, so a converted lead’s OpenAI-ad provenance is recoverable later, not just in the session it arrived in.

The server leg. POST to OpenAI’s events endpoint with your API key as a Bearer token, sending the same event with the same id you minted at submit, plus an action_source field ("web" for a website form) on every event. There’s a validate_only flag worth using while you’re first wiring this — it lets you test the request shape without actually recording a conversion. Same dedup principle as Meta: browser + server, one id, one conversion. Keep the key in a server secret, never in the repo, never in the client bundle.

Verify the count, not the raw log. OpenAI’s event log is new enough, and its UI moves fast enough, that I’d rather under-claim than assert specific dashboard mechanics that might be stale by the time you read this. What’s stable is the principle: confirm that the reported conversion count tracks your real submits one-to-one, not double — the same discipline as the other two platforms, whatever the dashboard happens to look like this quarter.

Cold-start caution, amplified. A brand-new pixel has zero conversion history, so conversion-optimized bidding has nothing to learn from. Starting on a clicks objective while the pixel accumulates data is defensible — but it is the same trap as PMax if you leave it there. Set a calendar reminder to graduate to conversion optimization once real registrations accrue, and watch cost-per-lead from hour one. On a new platform with a small budget, expect the first clicks to be slow and possibly gated behind ad review; an empty console in the first ten minutes is not a broken pixel.

You can’t dedupe walled gardens — but you can out-hub them

This is the question every advertiser eventually asks and no platform will ever answer for you: if Google, Meta, and OpenAI all show me a conversion for what looks like the same person, how many people actually converted?

The within-platform dedup pattern from earlier — one shared event_id, browser and server both carrying it — works because you control both legs and hand the platform an id it has agreed to honor. There is no equivalent across platforms, and it isn’t a gap Google, Meta, or OpenAI forgot to fill. Google will never accept Meta’s event_id. Meta will never read a gclid. OpenAI’s oppref means nothing to either of the other two. Each garden is built to be the definitive ledger of its own traffic, and refusing to reconcile against a competitor’s ledger isn’t an oversight — it’s the business model. Chasing a single cross-platform event id that all three would recognize is solving a problem that has no API, and wouldn’t stay solved even if one existed today.

The fix isn’t dedup. It’s a hub. On the same form submit that mints your within-platform event_id, capture every click identifier that happens to be present, into one first-party record: gclid (or gbraid/wbraid when Google supplies one of those instead), fbclid/_fbc, and oppref. Most real visits will only carry one — a person rarely arrives from two ad clicks in the same session — so this isn’t about merging three identities into one person. It’s simpler than that: your own database becomes the only place all three possible identifiers are ever stored side by side, because no platform needs to see the others’, and you’re the only party positioned to know which single one (if any) this particular lead actually carried.

That’s what makes the down-funnel loop closeable on all three platforms, not just Google. When the truth eventually arrives — this lead is now a booked call, a paying client — you round-trip the outcome back to whichever one platform gets to claim it, using whichever identifier your hub actually has for that person: Offline Conversion Import to Google on the stored gclid/gbraid/wbraid; a CRM/offline CAPI event to Meta on the matched hashed email; a value-bearing server event to OpenAI’s Conversions API on the oppref-linked id. Notice that Google’s own “Enhanced Conversions for Leads,” described above, is already this exact pattern under a Google-specific brand name — your hub, feeding one spoke, keyed by hashed email instead of a click id. Once you see that, the lesson generalizes: every platform’s “server-side,” “enhanced,” or “offline import” feature is the same idea wearing a different vendor’s label. Stop looking for a unified cross-garden API. Build the one hub that feeds all three spokes correctly, and let each garden keep believing it’s the only one in the room.

OUTBOUND · VALUE ROUND-TRIP TIME → DEAL CLOSES CRM SIGNAL DEAL WON $18,400 · closed $ YOUR HUB gclid · matched email · oppref GOOGLE Offline Conversion Import (gclid) META CAPI Offline Event (matched email) OPENAI Value Event Conversions API (oppref) $ $ $

There’s a second payoff, quieter but just as real: your hub is also where you catch a walled garden over-claiming. A platform’s dashboard will happily report a conversion that arrived carrying a different platform’s click id — or none at all, just fortunate timing — because from inside that one garden, it has no way to know. You’re the only party who can look at a claimed conversion and ask “did this lead actually carry your identifier, or are you just claiming proximity?” That question is unanswerable from inside any single platform. It’s trivial from your own table.

GA4 is the count; your hub is the identity — together they’re the referee

GA4 and the hub above are the two halves of the same idea: build a truth layer none of the three platforms control.

GA4 counts; it does not optimize. Fire a generate_lead event (a GA4 recommended event) on the thank-you page alongside the platform pixels, and mark it a key event. Now you have a count of leads that is independent of any ad platform’s self-interested attribution.

Feed Google from GA4, deliberately. GA4 key events can be imported into Google Ads as conversion actions — and, as noted above, they land as secondary by default, which is the mechanism that actually prevents them from double-stacking against a native conversion action in Smart Bidding. Be intentional about which event is the bidding target versus which is just observed.

Keep PII out of GA4. The enhanced-conversions and hub-matching PII decisions live at the ad platform boundary, not in analytics. GA4 should count leads without ingesting email addresses. This keeps your measurement layer clean, compliant, and defensible, and it means the referee has no conflict of interest.

Reconciliation has a concrete cause, not just a vague one. Every walled garden over-claims — but “differing attribution windows” is the specific, checkable reason, not an abstract hand-wave. Meta’s default is roughly a 7-day click / 1-day view window; Google’s conversion windows are configurable per action; GA4 runs its own attribution model entirely. Before you conclude two dashboards disagree because of double-claiming, align the windows you’re comparing — a real chunk of “over-claiming” is really just three clocks running differently. GA4 (cross-channel) and your CRM (the ground truth of who booked and who was real) are how you adjudicate what’s left after that. Judge channels on blended cost-per-qualified-lead, computed from your neutral layers — never on platform-reported ROAS.

Cold start: you cannot optimize for a conversion you’ve never had

This is the theme that unifies all three platforms, so it deserves its own frame.

Conversion-optimized bidding is a learning system, and a learning system with no examples learns nothing. On day one, every one of these pixels is a blank slate. If you demand conversion optimization immediately, you either get erratic delivery or the platform quietly falls back to something dumber.

The sequence that actually works:

  1. Bootstrap with a low-friction proxy signal, where the platform actually offers one — clicks on Meta or OpenAI, or (since PMax itself has no clicks lever) a plain Search campaign wired to a real conversion goal from day one on Google — while conversion tracking is fully installed and firing. You are optimizing for a proxy but recording real conversions the whole time.
  2. Feed the machine clean examples. Every real, deduplicated, spam-filtered conversion is a training example. Protect them (honeypot, time-gate, allowlist) so you’re not teaching the algorithm that bots are buyers.
  3. Graduate on a threshold, not a hunch. When you’ve cleared the platform’s rough conversion floor (Meta’s ~50/week, Google’s bid-strategy-specific minimums, OpenAI’s unknown-so-watch-the-data), switch to conversion or value bidding.
  4. Never let a proxy objective run for weeks. The PMax disaster was a conversion action left to starve with no graduation plan. Put the graduation date in your calendar the day you launch.
COLD-START SEQUENCE 01BOOTSTRAPproxy signal,tracking live 02FEEDclean, dedupedexamples 03GRADUATEhit the realthreshold 04NEVER STALLgraduation dateon the calendar

The rung above all of this: does any of it cause anything?

Everything up to this point — the shared event_id, the hub, the GA4 reconciliation — solves exactly one problem: making sure your count of conversions is an accurate count of real events. It does not answer a harder and, honestly, more important question: would this person have converted anyway?

Every one of these three platforms is structurally incentivized to claim credit for conversions it merely observed, not caused. A Search ad on your own brand name intercepts someone who was already typing your URL. A Meta ad shown to someone already mid-funnel from an email you sent three days ago still gets counted as the touch that mattered. An OpenAI ad shown to someone who was going to search your company name ten minutes later anyway still fires lead_created. None of this is fraud — it’s simply what an optimizer does when the only feedback signal it has is “did a conversion happen,” not “did my ad cause it to happen.”

That’s the incrementality question, and it’s a genuinely different discipline from everything above — instead of counting harder, you deliberately withhold the ad from a slice of otherwise-identical traffic and compare outcomes. Meta’s own Conversion Lift studies do exactly this, with a randomized test/holdout split baked into the platform; Meta recommends real budget to reach significance (roughly $30K/month is the commonly cited floor, with 100-plus conversions per group needed before the result means anything) — not a day-one move for a $25/day test, but worth knowing exists. Google’s geo-based Conversion Lift does the analogous thing using “Google Marketing Areas” — matched geographic units, one exposed, one held back — and reports the result as an incremental ROAS: for every dollar spent, how much new value showed up that wouldn’t have existed otherwise.

GEO / HOLDOUT LIFT TEST EXPOSED ad served HOLDOUT ad withheld 8.4% 3.1% INCREMENTAL LIFT n = 1,240 accounts n = 1,240 accounts CONVERTED NOT CONVERTED

At the scale most operators run at, a full lift study on any single platform is out of reach for a while. The practical version of this discipline — worth knowing exists even before you’re ready to run it — is what the industry now calls agile MMM: instead of a slow, backward-looking, once-a-year marketing-mix model, a continuously updated model that ingests every channel’s spend and your own conversion truth, folds in whatever lift or geo experiments you can afford to run, and outputs a live, standing estimate of each channel’s true incremental contribution — not what Google, Meta, and OpenAI each separately claim, but what actually moved when you turned spend up or down. It sits a layer above everything in this essay: GA4 tells you how many; the hub tells you who gets to claim each one; agile MMM tells you whether the claim was ever real in the first place.

I wrote about the transformer-based version of this shift in From Meridian to NNN — worth the follow-on read if the idea of a model that re-grades your channels every week, instead of once a year, is new to you. For now: everything in this essay gets your count right. Incrementality is the next rung, and it’s the one that tells you whether counting right was ever the same thing as spending right.

The failure modes I actually hit

Not hypotheticals. Each of these cost real time or money before it was caught.

  • Destination URL ≠ where the pixel lives. The ad pointed at the homepage; the pixel and form lived on the lander. Every click would have been untracked. Fix: the ad’s final URL must be the page that carries the pixel and the form. Verify it empirically — load the destination and check that the pixel global exists and the form is in the DOM.

  • PMax with no real conversion signal. 103 clicks, 0 leads, cheap-junk inventory. Fix: never run a conversion campaign without a real, firing conversion action; below the data floor, prefer Search-with-a-goal.

  • Objective drift. Bootstrapping on a proxy signal for cold-start reasons is fine; leaving it there is how you re-run the PMax failure in slow motion. Fix: schedule the graduation to conversion optimization the day you launch.

  • event_id in the wrong argument. OpenAI’s measure call takes four arguments, and event_id belongs in the fourth (options), not the third (data) object. Get it wrong and there’s no error, no warning, values show up in the dashboard exactly as expected — the browser leg simply carries no dedup key, so it silently double-counts against the server leg. Fix: match vendor example code argument position for position, and verify dedup by whether the counted total equals real submits — never by “it fired without an error.”

  • Conversion event off-label. Shipped registration_completed for a lead-form submit, when the platform’s own taxonomy reserves that name for account creation and has a dedicated lead_created event for exactly this case. It still worked — the dashboard event matched the string — but it was semantically wrong and cheap to fix only because it was caught in the first days, before any learning history accrued. Fix: check the platform’s own event-taxonomy page, not the first plausible-sounding example.

  • Secret set but not bound. Serverless platforms (Cloudflare Pages Functions among them) bind environment secrets on the next deployment. Setting the secret and testing the current deployment shows nothing firing. Fix: set the secret, then trigger a fresh deploy, then test.

  • BOM-corrupted secrets. Piping a secret value through certain shells (PowerShell’s stdin) prepends a byte-order mark that silently corrupts the key; the API then rejects every call with an opaque auth error. Fix: set secrets via the interactive prompt, or a byte-clean printf, never a naive pipe. Redeploy after every secret change.

  • Ad copy maxed out and truncated. Auto-generated headlines and descriptions that hit the exact character limit get cut mid-word in real placements (”…MarTech &”, “…marketing technology, da”). Fix: write to fit under the limit with complete thoughts, and match the ad’s hook to the lander’s promise.

  • Text-in-image creative. A cropped text graphic is unreadable at thumbnail size and gets cropped further in-feed. Fix: a clean logo or a face out-performs text-in-an-image where the placement is small.

The verification runbook

Never trust a conversion setup you haven’t watched fire. For each platform, prove each leg independently:

  1. Pixel present on the destination. Load the actual ad destination URL and confirm the pixel global exists (typeof window.<pixel-global> === "function"), the loader script is in the DOM, and the lead form is present. If the pixel isn’t on the page the ad points to, nothing else matters.

  2. Browser leg fires on conversion, with the id in the right place. Submit a real (labeled “test — ignore”) lead. On the thank-you page, watch the console (keep the pixel’s debug flag on during launch QA) for the conversion call, and check the argument position of the dedup id against the vendor’s documented signature — not just that a call happened.

  3. Server leg fires with the same id. Tail your function logs (wrangler pages deployment tail or equivalent) and confirm the server conversion returns a success status — and that it carries the same id. Never log the key or user data; log the response status so rejections are visible.

  4. Dedup holds. In the platform’s event or diagnostics view, confirm the reported conversion count equals the number of real submits, not double.

  5. Measurement agrees, and your hub caught the click id. Confirm GA4 recorded the generate_lead key event, that your counts reconcile across GA4, the platforms, and your inbox/CRM, and that your lead record captured whichever click identifier the visit actually carried.

  6. Turn off debug. Once you’ve watched a real conversion land end-to-end, flip the pixel debug flags off so you’re not logging verbose output for every visitor.

What to actually judge it on

The platforms will hand you dashboards full of flattering numbers. Ignore most of them.

The only metric that matters day to day is cost per qualified lead, by channel, computed from your neutral layers — GA4 for the count, your CRM or your inbox for the quality. A channel that reports a $12 CPL but delivers tire-kickers is worse than one reporting $40 CPL that books real calls. You cannot see that difference in any ad platform’s dashboard, because none of them know which leads were real. You do — and because your hub captured every platform’s own click identifier at the moment of conversion, you can close the loop on all three, not just the one with the fanciest offline-import documentation: Google via Offline Conversion Import on the stored gclid/gbraid/wbraid, Meta via a CRM-outcome CAPI event on the matched email, OpenAI via a value-bearing server event on the oppref-linked id. That’s the entire reason the hub exists: to teach each machine not just “this person filled a form” but “this person was worth something,” and to let that — not clicks, not form-fills, not platform-reported ROAS — be the thing all three algorithms compete to maximize.

And above even that sits the rung this essay can only tease: cost-per-qualified-lead is still an observational number. It tells you what happened, not what your spend caused. Getting the count right, as this whole essay is about, is the precondition for asking the incrementality question honestly — you can’t tell whether a channel is truly incremental if you don’t even trust its conversion count yet. Get the count right first. Then go find out if any of it was ever really working.

The discipline, in one paragraph

Treat every conversion as a training example fed to three separate learning machines that will never compare notes with each other, and build the two layers none of them control: one neutral count (GA4), and one hub that holds every garden’s own click identifier side by side (your CRM). Mint one identity per real event and thread it through both legs, inside each platform, so nothing double-counts internally — and stop hunting for a cross-platform equivalent, because none exists and none ever will; that’s not a gap, it’s the point of a walled garden. Respect that each of the three earns a genuinely different kind of intent — declared, inferred, mid-conversation — and that losing any one of them is a blind spot the other two structurally cannot cover. Keep the signal clean, because dirty signal teaches the machines to find you dirty customers. Bootstrap through cold-start with a proxy signal where the platform actually offers one, then graduate on a threshold, not a feeling. Judge the whole apparatus on cost-per-qualified-lead from a layer none of the platforms control. And once that count is finally trustworthy — not a day before — ask the harder question no platform will ever ask itself: would any of this have happened anyway. That’s the rung above orchestration. It’s next.