Chapter 7·Part III · Implementation: Correctness and Architecture
Conversion Architecture
A conversion is a claim about business value, encoded as a measurement event. That definition sounds pedantic until you watch what happens when it is ignored. In the {COMPANY} property (GA4 534525683, measurement ID G-WH410Z73V1, stream 14548640408) the audit found six key events, and at least three of them made claims the business could not honor: a purchase event that had never fired, a cta_click event marked as a conversion across roughly 1,200 statically generated pages, and a career_application_submit event that measured job seekers rather than customers. The property was counting things. It was not measuring value. This chapter designs the conversion layer from first principles for a business whose entire revenue arrives offline, through phone calls and web-form leads that a technician later closes as a booked job, and it argues for a specific, opinionated target state: one canonical lead conversion, one differentiated value model, and a disciplined refusal to import events that would teach an ad platform the wrong lesson.
Chapter 5 (Audit Findings) catalogued what was wrong. Chapter 6 (The Server-Side Measurement Protocol) fixed the transport so that a server-side generate_lead can actually reach GA4. This chapter sits between them and above them: it decides what should count, why, and at what worth, so that the plumbing in Chapter 6 carries the right signal and the offline-revenue loop in Chapter 11 (The Offline-Revenue Loop) has a coherent conversion to enrich. Event naming and the custom-dimension registry that describes each conversion belong to Chapter 8 (Event Taxonomy and Custom Dimensions); the client and value model that let a conversion survive attribution belong to Chapter 9 (Identity, Attribution, and Value). Here the concern is narrower and prior to all of that: the architecture of the conversions themselves.
7.1 Conversions from first principles
Start with the revenue mechanism, because conversion design that does not start there produces vanity metrics. {COMPANY} sells pest-control jobs in four Central California counties (Monterey, Santa Cruz, San Benito, Santa Clara). Demand is seasonal: termite swarms drive spring inquiries, rodents drive cooler-month inquiries. There is no cart, no checkout, no online payment. A prospect finds a service, pest, or location page, then either calls the phone number or submits a form. Money changes hands days or weeks later, offline, when the office quotes the job and the customer accepts. The digital property therefore cannot observe revenue directly. It can observe only the moment a stranger becomes a lead.
That single observable moment is the natural macro conversion, and everything downstream in the measurement design follows from naming it precisely. A conversion event in this property should satisfy three conditions. It should mark a discrete, non-repeating intent to buy (a submitted lead, not a scroll or a hover). It should be countable without ambiguity, so that one human lead produces exactly one counted conversion. And it should carry, or be joinable to, a monetary value that the business is willing to stand behind internally. Any event that fails one of these three tests is a micro conversion at best and noise at worst, and it does not belong in the key-events set that GA4 exposes to bidding systems.
The baseline violated all three tests at once. purchase marked an intent that does not exist on this site and had produced zero rows across the property lifetime. cta_click marked a repeating, low-intent gesture (any button, any of 1,200 pages) and could fire many times per session. career_application_submit marked a real discrete action, but one with no purchase intent whatsoever. The conversion layer had been assembled by reflex, toggling events to "key event" because they looked important, rather than by deriving them from the revenue mechanism. Rebuilding it means running that derivation deliberately.
7.2 Macro and micro conversions
The distinction between macro and micro conversions is not a matter of importance ranking. It is a matter of what an automated bidder is allowed to optimize toward. A macro conversion is the event you would pay to cause: for {COMPANY}, a submitted lead. A micro conversion is a leading indicator that correlates with the macro conversion but is not itself worth money: a form_start, a cta_click, a page view of a high-intent service page. Micro conversions are valuable for diagnosis. They tell you where a funnel leaks. They are actively harmful as optimization targets, because a bidding algorithm instructed to maximize a micro conversion will find the cheapest way to produce that micro conversion, which is rarely the same as the cheapest way to produce revenue.
The measurement plan for this property therefore sorts its events into two tiers with a bright line between them. The macro tier holds the lead conversions. The micro tier holds form_start, form_error, cta_click, view_item, and the enriched page_view. GA4 blurs this line by letting any event be marked a key event with one toggle, and the blur is the trap. The audit found cta_click sitting on the wrong side of the line. Phase 0 of the reconstruction deleted the cta_click key event outright (a zero-risk Admin API change; the underlying event still collects, it simply no longer claims to be a conversion), which is the first act of conversion architecture: demarcation. Section 7.6 explains why that particular demotion mattered more than it appears.
The micro events are not discarded. They earn their keep in Explorations and funnels, where a practitioner reads them to understand behavior. form_start paired with contact_form_submit yields a form-abandonment rate. form_error with its bounded error_message codes (server_error, network_error, never raw user text) shows where submissions break. These are analysis inputs, and they are correctly scoped as ordinary events, retained under the fourteen-month window that Phase 0 set (raised from the TWO_MONTHS floor via a single dataRetentionSettings PATCH) so that a full season of leading-indicator data survives for year-over-year comparison.
7.3 The case for one canonical lead
A business with a single revenue mechanism should have a single canonical conversion. This is the most consequential design decision in the chapter, and the current property does not yet honor it, on purpose, for reasons Section 7.10 defends.
The argument for canonicalization is mechanical. When you eventually link Google Ads (the audit found google/cpc spending 46 sessions over thirty days against a property with no Ads link, so the link is coming), Ads imports GA4 key events as conversion actions and hands them to Smart Bidding. Smart Bidding needs a stable, unambiguous count of the thing it is paying to cause. If two key events both represent "a lead arrived," and a single lead can trigger both, the bidder sees inflated conversion volume and a diluted cost per conversion, and it recalibrates bids against a number that overstates reality. The remedy is to nominate one event as the canonical lead and let every other lead-shaped event be either a component of it or a diagnostic sibling that never reaches Ads.
For {COMPANY} the canonical lead is generate_lead, fired server-side from the contact API via the Measurement Protocol. The reasoning is that the server event is the one that can carry authoritative value (the office knows which service was requested), the one that can survive ad and privacy blockers because it originates from the backend rather than the browser, and the one that will later be enrichable with offline outcome data (stage, job_value, closed_at) through the loop in Chapter 11. Chapter 6 documents the transport fix that made generate_lead reach GA4 at all; the primary defect was that both api/contact/route.ts line 384 and api/careers/route.ts line 288 had posted to https://www.google.com/mp/collect, which returns HTTP 404, so across the property lifetime the server lead produced exactly zero data. Once the host was corrected to https://www.google-analytics.com/mp/collect (verified HTTP 204 against 404 with identical payloads via curl), generate_lead became a real, populated event, and Phase 1 promoted it to a key event. A production generate_lead sent to the live endpoint returned 204 and appeared in GA4 Realtime as a registered key event within about twenty seconds.
The client-side contact_form_submit and quote_form_submit remain key events for now. That is the deliberate double-count discussed in 7.5 and 7.10, and it is defensible only because no Ads link yet consumes the inflated total. The canonical target is unambiguous even while the interim state tolerates redundancy: at Ads-link time, generate_lead is the conversion action; the client form events revert to ordinary events used for GA-only reporting and blocker-loss measurement.
7.4 Counting methods and default values
GA4 exposes two counting methods for a key event: "once per event" and "once per session." The choice is not cosmetic. "Once per event" counts every occurrence; "once per session" counts at most one per session no matter how many times the event fires. For a lead conversion the correct method is "once per event," because a genuine second lead from the same session (a prospect who submits the contact form and then also submits a quote request) is a second unit of intent, and suppressing it understates demand. For a repeating micro gesture the "once per session" method is a partial mitigation, but the honest fix for a repeating gesture is not to count it as a conversion at all, which returns to the demarcation of 7.2.
Default value is the second dial. GA4 lets a key event carry a default monetary value and currency, applied when the event arrives without an explicit value. The property is configured for USD and the America/Los_Angeles timezone, so currency is settled. The question is what value a lead is worth, and the baseline answer was a flat 150 hard-coded into the Measurement Protocol payload for every lead regardless of service. A flat value is defensible as a placeholder and indefensible as a permanent design, because it tells a value-based bidder that a termite fumigation lead and a single-ant-visit lead are worth the same, which is false and which suppresses the exact optimization that value-based bidding exists to perform. Section 7.8 presents the differentiated model that replaced the flat 150.
One subtlety deserves naming. Default value at the property level and explicit value in the event payload are two mechanisms for the same slot, and they must not fight. The reconstruction sends explicit per-service value on every generate_lead, so the property-level default becomes a fallback that should rarely trigger. Keeping the explicit value authoritative, rather than leaning on a property default, means the value logic lives in versioned application code where it can be reviewed and revised, not buried in a GA4 settings screen that no code review ever sees.
7.5 The double-counting problem
Here is the defect that most inflates a lead count, and it is subtle because both events are individually correct. A single human submits the contact form once. The browser fires the client-side contact_form_submit. The backend, processing the same submission, fires the server-side generate_lead. One lead, two events. If both are key events and both feed Ads, the platform counts two conversions for one customer.
The reason GA4 cannot rescue you here is specific and worth stating precisely. GA4 performs automatic conversion deduplication for exactly one event: purchase, keyed on transaction_id. Two purchase events sharing a transaction_id collapse to one. GA4 offers no equivalent deduplication for any other event name. There is no lead_id-based dedupe for generate_lead, no cross-event dedupe between contact_form_submit and generate_lead. The platform treats them as independent occurrences because their names differ and no shared key binds them. So a client event and a server event that represent one lead will inflate every count that sums them, and the inflation is not a rounding error: at this property's volume (401 active users, 527 sessions, 29 leads captured in the database sample over the measured window), a systematic doubling of the lead conversion would halve the apparent cost per lead and mislead every downstream decision that reads it.
The architecture resolves this by canonicalization rather than by dedupe, because dedupe is unavailable. One event is the conversion (generate_lead); the others are not. The interim state keeps the client form events as key events only because the double-count is currently harmless: with no Ads link, the redundant conversions live entirely inside GA4 reporting, where a human analyst can see both and reason about them, and no automated bidder is misled. The moment an Ads link exists, the double-count stops being a reporting curiosity and becomes a bidding hazard, which is precisely why Section 7.10 ties the demotion of the client events to the Ads-link event rather than doing it prematurely.
Chapter 11's offline loop introduces a lead_id UUID generated per lead and stored on the contact_submissions row. That identifier is the future dedupe key: it lets the offline outcome upload target the same logical lead the online generate_lead created, so that enrichment updates a conversion rather than minting a second one. lead_id is not a substitute for canonicalization inside GA4 (GA4 still will not dedupe on it), but it is the join key that keeps the online conversion and the offline outcome describing one lead across systems.
7.6 How one toggle poisons bidding across 1,200 pages
cta_click as a key event is the most instructive anti-pattern in the property, because it shows how a single wrong toggle scales into systemic corruption. The site renders roughly 1,200 static pages, and nearly every one carries call-to-action buttons: "Get a quote," "Call now," service cards, location links. Each click fires cta_click with a cta_location parameter. Marked as a key event, every one of those clicks became a counted conversion.
Consider what this does to a value-based or even a conversion-count bidder once Ads is linked. The bidder learns that conversions are cheap and abundant, because a curious visitor clicking three CTAs on three pages generates three conversions while buying nothing. It then bids toward the traffic that maximizes CTA clicks, which is high-volume, low-intent browsing, not the narrower stream of visitors who actually submit a lead. The optimization target and the revenue target diverge, and spend flows toward clicks that never become jobs. Across 1,200 pages the noise is not incidental; it is the dominant signal, because click events vastly outnumber lead events. A property that fires thousands of cta_click events and dozens of generate_lead events, and calls both conversions, is training its bidder almost entirely on the noise.
There is a second, quieter harm. cta_click as a conversion pollutes GA4's own reporting surfaces (conversion rate, conversions by landing page, conversions by channel) with a metric that no longer means "lead," so every stakeholder who reads "conversions" reads a number contaminated by button curiosity. Phase 0 deleted the cta_click key event as a zero-risk change. The event still collects for micro-analysis (which CTAs draw clicks, in which cta_location), and its cta_label dimension was registered so that analysis is legible. It simply no longer claims to be a conversion. This is demarcation applied with a scalpel: keep the diagnostic, remove the false claim.
7.7 Why career applications must never reach Google Ads
career_application_submit is a real conversion for the HR function and a poison pill for the marketing function, and the two must not be conflated. A job application is a discrete, non-repeating, high-intent action, which is why it superficially resembles a lead and why it was toggled to a key event. But its intent is orthogonal to revenue. A person applying for a technician job is not a customer and will never become a booked pest-control job. If this event is imported into Google Ads as a conversion action, Smart Bidding will optimize ad spend toward generating job applications, spending the marketing budget to attract job seekers who produce zero revenue.
The scale is small (the careers form is low volume), which is exactly why the correct move is removal rather than elaborate handling. The reconstruction found a compounding defect: the careers route sent a server-side Measurement Protocol event named career_application, a different name than the client-side career_application_submit key event, so the server event was an orphan that matched no key event and populated no conversion. Rather than repair the server event and risk it later leaking into Ads, Phase 1 removed the careers server Measurement Protocol send entirely. The low-volume client career_application_submit key event already covers HR's need to count applications inside GA4. The governing rule for the eventual Ads link is explicit in the measurement plan: import generate_lead and only generate_lead; never import career_application_submit. The separation of a legitimate GA4 key event from an eligible-for-Ads conversion action is a distinction the platform does not enforce for you, so the architecture must enforce it by policy, and Chapter 14 (Anti-Pattern Catalog) records it as a named trap.
7.8 The per-service value model
Differentiated value is the mechanism by which a bidder learns to prefer profitable leads over cheap ones, and a flat value forecloses that learning entirely. The reconstruction replaced the flat 150 with a per-service map, applied in the Measurement Protocol payload as an explicit value on each generate_lead, keyed on the requested service:
termite 400
fumigation 500
commercial 400
bed-bug 250
rodent 180
general 150
pest 150
ant 100
wasp-bee 100
other 120
The spread from 100 to 500 is the entire point. A fumigation lead and an ant-visit lead differ in expected job value by roughly fivefold, and a value-based bidder that receives that spread can shift spend toward the search terms, audiences, and hours that produce fumigation and termite inquiries rather than treating all leads as interchangeable. Value-based Smart Bidding, given honest relative values, will pay more to acquire a lead worth 500 than one worth 100, which is the behavior the business wants. Given a flat value it cannot express that preference at all, and the differentiation the pest-control mix demands (seasonal termite and fumigation revenue against low-ticket ant calls) is invisible to the bidder.
The values feed value, and Chapter 9 (Identity, Attribution, and Value) treats the attribution consequences of carrying value on a Measurement Protocol event (session stitching, the ip_override geolocation fix, the removal of the synthetic client_id fallback that had minted phantom users). The relevant point for conversion architecture is narrower: the value must be differentiated, it must be attached at the event level in code, and it must survive to the conversion. A production payload validated against https://www.google-analytics.com/debug/mp/collect returned validationMessages: [], confirming the value-bearing event is well-formed.
A caveat on scale keeps this honest. At roughly 46 paid clicks and 500 sessions per month, the property sits below the volumes where value-based Smart Bidding stabilizes (industry guidance puts that near 15 to 30 conversions per month, and data-driven attribution near 400 conversions per 28 days). Differentiated value will not fully engage the bidder at this scale on day one. Building the value model now is still correct, because the values give the office honest cost-per-booked-job visibility immediately through the native insights dashboard, and because the model is in place and accumulating history for the day volume rises. Value-based bidding that arrives to find three years of differentiated conversion values is in a far better position than one that arrives to a flat 150.
7.9 The discipline of not inventing dollars
Every number in the value map is labeled provisional and internal, and the labels are not decoration. {COMPANY} carries a prior liability from AI-generated pricing that was published to the public site and had to be removed, which is documented in the project's own change history as a content-integrity failure. That history sets a hard rule for this work: a conversion value is an internal bidding signal, never a customer-facing price, and it must be presented as an estimate the business owns rather than a quote the business offers.
The rule has architectural consequences. The values live in application code, not in marketing copy, and never render on any page a prospect can see. They are described in the committed MEASUREMENT_PLAN.md as provisional internal estimates subject to revision as real job_value data accrues through the offline loop. The word "provisional" signals that these are placeholders to be replaced by observed closed-job values once Chapter 11's job_value and closed_at columns have collected enough won leads to compute real per-service averages. The word "internal" signals that no customer will ever be shown 500 and told that is the price of a fumigation. A world-class setup treats invented dollar figures as a liability to be quarantined inside the bidding layer, never a claim to be published, and it writes that quarantine into the plan so the next practitioner cannot mistake a bidding estimate for a price.
7.10 The deliberate deferral
Demoting contact_form_submit and quote_form_submit from key events is the obvious final step of canonicalization, and the reconstruction deliberately did not take it. The deferral is a design decision, not an oversight, and its justification is precise.
Today there is no Google Ads link. The double-count from Section 7.5 therefore lives entirely inside GA4 reporting, where a human analyst sees both the client form events and the server generate_lead and can reason about the relationship between them. In that GA-only context the redundancy is not merely harmless, it is useful: the client events and the server event, side by side, measure the blocker-loss gap directly. The database showed a 69 percent ga_client_id capture rate (20 of 29 leads carried a client id, 9 did not), and the missing 31 percent is ad-block and privacy-browser loss. The client-side form events, which blockers suppress, and the server-side generate_lead, which they do not, bracket that loss from both sides. Keeping both as key events during the pre-Ads period maximizes conversion capture and preserves the comparison.
The moment Ads is linked, the calculus inverts. Then the double-count leaves GA4 reporting and enters a bidding system that cannot dedupe it, and the redundancy that was diagnostic becomes corruption. So the plan binds the demotion to that event: at Ads-link time, choose generate_lead as the single canonical conversion action, demote contact_form_submit and quote_form_submit to ordinary events, and import only generate_lead. Deferring the demotion until the canonical conversion is chosen at Ads-link time avoids two failure modes at once. It avoids demoting prematurely and losing capture during a period when the redundancy costs nothing. And it avoids the far worse error of carrying three lead-shaped key events into an Ads link and letting the bidder train on an inflated count. The deferral is disciplined sequencing: do the harmless thing now, schedule the consequential thing to the moment it becomes consequential, and write the trigger condition into the measurement plan so the decision is not forgotten.
7.11 The target state
The conversion architecture this chapter specifies reduces to a short, enforceable contract. One canonical lead conversion, generate_lead, fired server-side, counted once per event, carrying a differentiated per-service value that is provisional, internal, and never published. A tier of micro conversions (form_start, form_error, cta_click, view_item, page_view) that inform analysis and never claim to be conversions and never reach Ads. A double-count tolerated today because it is GA-only and diagnostic, scheduled for resolution the instant an Ads link makes it harmful. And two events, cta_click and career_application_submit, held permanently outside the Ads-eligible conversion set because optimizing toward either would spend the marketing budget on button curiosity and job seekers rather than booked pest-control jobs.
Chapter 8 formalizes the naming and the custom-dimension registry that describe each of these events. Chapter 9 carries the value and identity signals through attribution. Chapter 11 closes the loop by writing real job_value back against the canonical conversion, at which point the provisional numbers in Section 7.8 begin their replacement by observed truth, and the conversion architecture stops estimating value and starts measuring it.