Machina

Chapter 5·Part II · Diagnosis

Audit Findings: What a Passing Property Was Hiding

The property presented well. Over the thirty days preceding the audit, GA4 property 534525683 recorded 2,328 events, 401 active users, and 527 sessions, with a daily event count that moved between 24 and 113. Data flowed. The Realtime card showed users. Six events carried the key-event star. Eight custom dimensions sat registered and populated. A stack engineer glancing at the property would have found nothing to alarm them, and that impression is precisely the problem this chapter documents. A property can satisfy every surface check GA4 offers and still fail at the one job it exists to do here, which is to count the leads that {COMPANY} turns into booked pest-control jobs. The audit method described in Chapter 4 (Auditing Methodology: Programmatic and Adversarial) read the configuration through the Admin API and interrogated the collection path adversarially. What it returned was a ranked list of defects that a dashboard would never surface, because the dashboard was reporting confidently on data that was either missing, misattributed, or aimed at the wrong target.

This chapter presents those findings as a severity-ranked report. Each finding names the mechanism that produced it and the business consequence that followed, because a finding stated only as a symptom ("the server event has no data") gives an operator nothing to act on. The severity scale runs from P0 to P3. A P0 defect destroys the primary measurement outright and has done so for the property's entire life. A P1 defect corrupts a decision the business is about to make with money, principally automated bidding. A P2 defect silently loses or distorts data that the business needs but has not yet missed. A P3 defect is dead weight: harmless, but it clutters the property and misleads future readers.

P0: The Measurement Protocol posted to a 404 host, and had since day one

The single most consequential defect sat in two server routes, api/contact/route.ts at line 384 and api/careers/route.ts at line 288. Both posted their server-side Measurement Protocol event to https://www.google.com/mp/collect. That host is wrong. The documented and only functional Measurement Protocol collection endpoint is https://www.google-analytics.com/mp/collect. The distinction looks cosmetic. It is total.

The audit did not infer this from documentation alone. It reproduced the failure live. Sending the identical JSON payload to each host with curl produced two different HTTP status codes:

# Wrong host, as shipped in both routes
curl -s -o /dev/null -w '%{http_code}' \
  -X POST 'https://www.google.com/mp/collect?measurement_id=G-WH410Z73V1&api_secret=REDACTED' \
  -H 'Content-Type: application/json' \
  --data '{"client_id":"...","events":[{"name":"generate_lead","params":{...}}]}'
# -> 404

# Documented host
curl -s -o /dev/null -w '%{http_code}' \
  -X POST 'https://www.google-analytics.com/mp/collect?measurement_id=G-WH410Z73V1&api_secret=REDACTED' \
  -H 'Content-Type: application/json' \
  --data '{"client_id":"...","events":[{"name":"generate_lead","params":{...}}]}'
# -> 204

The wrong host returned 404, meaning the URL path /mp/collect does not exist on www.google.com; the request was rejected before any GA4 system saw a payload. The correct host returned 204, the standard Measurement Protocol acknowledgement, which is a bare "accepted, no content" and which the endpoint returns for well-formed and malformed payloads alike. The mechanism of the failure is therefore not a data-quality problem inside GA4. It is a delivery problem outside GA4. The generate_lead event that the contact API constructed on every form submission, with client ID, currency USD, a value, and lead metadata, was serialized, posted, and thrown away by a Google web server that has nothing to do with analytics ingestion.

The business consequence compounds because of what generate_lead was for. This event was the server-side, ad-blocker-resistant record of a lead. {COMPANY}'s revenue arrives entirely offline, through phone calls and web-form submissions that the office later books as jobs; there is no online transaction to measure. The server event existed so that a lead would be counted even when a visitor's browser blocked the client-side tag. Because the host was wrong, that event reached GA4 zero times across the property's lifetime, from creation on 2026-04-24 through the audit. The failure was silent in a specific and damning way: because no generate_lead ever arrived, no one in the property's history ever had reason to mark it a key event. The absence of the event masked the absence of the data. A missing conversion cannot advertise its own absence.

That left exactly one working lead signal, the client-side contact_form_submit fired by gtag in the browser. Ad blockers, tracking-protection browsers, and privacy extensions suppress that request at the network layer, and the property had no server-side fallback because the fallback was posting to a 404. The property was measuring leads only for the subset of visitors who permit third-party analytics to run, and it was blind to the rest, with no way to quantify the gap from inside GA4. Chapter 6 (The Server-Side Measurement Protocol) documents the repair in full; the finding here is that the property's flagship resilience feature was inert from birth.

Two secondary defects rode inside the same payload and would have degraded the event even after the host was corrected. First, the client_id fell back to the string server.<timestamp> whenever the _ga cookie was absent from the request. A synthetic client ID is not a stable identity; each fabricated value mints a brand-new phantom user in GA4, so a fixed-host version would have inflated the user count with one-session ghosts, one per cookieless lead. Second, the payload omitted both session_id and engagement_time_msec. GA4 requires session_id to stitch a Measurement Protocol event onto the browser session that generated it; without it, and without an engagement-time signal, the event attributes to (direct)/(none) regardless of how the visitor actually arrived. A corrected host with an uncorrected payload would have produced counted-but-misattributed conversions, which is a subtler failure than zero data and arguably harder to detect. Both defects are catalogued here because they belong to the same root artifact, and both are addressed in Chapter 6 and Chapter 9 (Identity, Attribution, and Value).

P1: The conversion architecture was poised to spend money badly

The second severity tier concerns the six key events and how they would behave the moment someone linked this property to the Google Ads account that is already spending. That link did not exist at audit time, which is itself a finding discussed below, but the traffic mix showed google/cpc at 46 sessions over thirty days. Money was moving. When the link is created, GA4 key events become Google Ads conversions, and the bidding algorithm begins to optimize toward them. The conversion configuration the audit found would have handed that algorithm the wrong targets.

cta_click was a key event. This is the most dangerous of the P1 findings. A "click on a call-to-action" is a micro-interaction that fires on button presses across the site; it is not a lead. Marking it a key event tells GA4, and by extension any linked Ads account, that a CTA click is a conversion worth bidding toward. Smart Bidding optimizes for whatever it is told to value, so a conversion defined as "clicked a button" trains the system to buy clicks from users who click buttons and never submit forms or call. The mechanism is a target-definition error; the consequence is wasted ad spend aimed at a worthless outcome. The audit's Phase 0 deleted this key-event designation as a zero-risk Admin API change, described in Chapter 7 (Conversion Architecture).

purchase was a key event that never fired. GA4 ships purchase as a default key event. This property has no ecommerce and sends no purchase event, so the designation was inert, a dead default rather than an active hazard. It still deserved cleanup, and it produced an instructive API result: the Admin API refused to delete it, returning INVALID_ARGUMENT with the message "The event cannot be deleted." GA4 protects certain default key events from deletion. The finding stands as harmless clutter that also demonstrates a boundary of the automation surface, relevant to Chapter 15 (Access and Permissions Model).

generate_lead was not a key event. The server event that should have been the property's canonical conversion carried no key-event star. As established in the P0 section, this was not an oversight of neglect; it was the rational consequence of an event that had never once appeared in the property. You do not mark a conversion for data you have never seen. Promoting generate_lead to a key event only made sense after the host fix made it real, which is the sequencing Chapter 6 and Chapter 7 follow.

The client and server events double-counted by design. Even with everything working, contact_form_submit (client) and generate_lead (server) both fire for a single lead, and both contact_form_submit and quote_form_submit describe form-driven leads. Counting one lead as two or three conversions overstates volume and, once an Ads link exists, feeds inflated conversion counts into bidding. The audit's judgment here was deliberate and is worth stating precisely, because it is a defer rather than a fix: with no Google Ads link present, the double-count is harmless GA-only reporting noise, and keeping both form events as key events maximizes lead capture until the business chooses one canonical conversion at the moment the Ads link is created. Demoting the duplicates was therefore recorded as a deliberate deferral, not an omission, and Chapter 7 and Chapter 14 (Anti-Pattern Catalog) carry the reasoning. The finding is that the architecture was primed to miscount, and the remediation is timed to the link event rather than the audit.

Underneath all four items sits a structural finding: the Google Ads account was spending while GA4 was not linked to it. The 30-day traffic mix records google/cpc at 46 sessions, which means a live Ads account is buying clicks, yet the property held no Google Ads link and no BigQuery link. The consequence is that the paid clicks arrive in GA4 as traffic but cannot flow back to Ads as conversions, so the bidding algorithm optimizes with no outcome signal, and the business cannot compute cost per lead inside either tool. This is the join that the entire offline-revenue loop depends on, and Chapter 11 (The Offline-Revenue Loop) treats it as the central activation problem. At audit time it was simply absent.

P2: The silent losses

The third tier collects defects that lost or distorted real data quietly, without any surface symptom to prompt investigation.

Event data retention sat at TWO_MONTHS, the floor. GA4 lets a property set event-level retention to two months or fourteen; this property was at the two-month minimum. The nuance that makes this matter, and that is easy to get wrong, is what the setting actually governs. GA4's standard aggregate reports retain their data effectively indefinitely, so top-line trend reporting was never at risk. The two-month floor bites only Explorations and funnels that pivot on event-scoped or user-scoped custom dimensions. That is exactly the analysis this seasonal business needs: performance by service and by county over a termite-swarm spring and a rodent-heavy cool season, sliced on the service_slug and location_slug dimensions. A two-month window cannot hold a year-over-year seasonal comparison. The mechanism is a retention cap on dimensioned event data; the consequence is that the property's most valuable analysis, seasonal demand by service and geography, was structurally impossible. Phase 0 raised the setting to FOURTEEN_MONTHS with a single dataRetentionSettings PATCH. The change is not retroactive and takes roughly 24 hours to apply, which means the lost two-month horizon could only be rebuilt going forward, not recovered. This is detailed in Chapter 7 and Chapter 12 (Warehousing and Reporting).

Enhanced Measurement auto-events collided with the custom events. All seven Enhanced Measurement options were on, including pageChangesEnabled and formInteractionsEnabled. Both created duplicate data against the property's hand-built taxonomy. Page-changes tracking fires a GA4 page_view on client-side route changes, and the site already fires its own page_view per route through gtag, so single-page navigations double-counted views. Form-interactions tracking is the subtler collision, and the adversarial verify pass in Chapter 4 is what caught it: with formInteractionsEnabled on, GA4 auto-fires its own form_start and form_submit, which duplicate the site's custom form_start and contact_form_submit/quote_form_submit events. The mechanism is GA4's built-in automatic events shadowing bespoke events of near-identical intent; the consequence is inflated page_view and form-engagement counts that no one had reconciled. A related item in the same settings block: site search was enabled, but the site has no search box, so the setting could only ever produce noise or nothing. Chapter 8 (Event Taxonomy and Custom Dimensions) covers the taxonomy hygiene; the fix switched pageChangesEnabled and formInteractionsEnabled off.

view_item was sent as flat parameters instead of an items array. The client fired view_item with item_category, item_id, and item_name as flat top-level params. GA4's ecommerce events require an items[] array; a flat-param view_item is syntactically accepted (the endpoint returns its usual acknowledgement) but never populates the item-scoped reports, so the data went nowhere useful. Because the business has no ecommerce, the audit classified retiring this as low priority and left it in place as harmless dead weight, a P3-flavored item recorded inside the P2 tier for completeness. The finding matters less for the lost data than as a specimen of a common failure mode: a malformed event that returns a success code and silently populates nothing.

The synthetic client_id minted phantom users. Described above as a rider on the P0 payload, this belongs to the silent-loss tier on its own merits. Every cookieless lead that fell through to the server.<timestamp> fallback would have created a distinct one-time user in GA4, inflating the active-user count with identities that never browsed and never return. Against a 401-user base, a steady trickle of fabricated single-session users distorts every per-user metric. Chapter 9 treats identity in full.

The careers server event was an orphan. The careers route sent a Measurement Protocol event named career_application, while the client fired career_application_submit. Two names for one action means GA4 registered them as two unrelated events, and the server-named career_application matched no key event and no report anyone was reading, so it was an orphan even before the 404 host guaranteed it never arrived. The finding is a naming-consistency defect; the remediation, given the careers form's low volume, was to remove the server event entirely and let the client key event cover it, rather than to rename and maintain a second path. Chapter 8 records the taxonomy rule this violated.

The baseline, read in context

The raw numbers deserve a second look now that the defects are on the table, because the baseline is not neutral evidence. Over thirty days: 2,328 events, 401 active users, 527 sessions. The traffic mix, by sessions, ran (direct)/(none) 234, google/organic 192, google/cpc 46, yahoo/organic 16, bing/organic 12, ig/social 4, facebook.com/referral 3, blue.bbb.org/referral 2, chatgpt.com/ai-assistant 2, and yelp.com/referral 1.

Two features of that mix carry findings. First, (direct)/(none) at 234 sessions is the largest single bucket, larger than google/organic. Some direct traffic is genuine, but an oversized direct bucket is the classic signature of attribution loss, and this property had two mechanisms manufacturing exactly that: the Measurement Protocol payload omitted session_id and engagement time, which routes any server event to (direct)/(none), and the client tag's own load timing left a window in which a fast visitor could submit before the _ga cookie was set. The direct bucket was not simply demand; it was partly the property's own attribution leakage rendered as a channel. Second, google/cpc at 46 sessions confirms the unlinked-Ads finding with a number. Real paid clicks, roughly 46 per month, were arriving and converting into leads that GA4 could not report back to the account that paid for them.

That volume also sets honest expectations for everything downstream, and the audit recorded it as a constraint rather than a defect. Roughly 46 paid clicks and about 500 sessions per month sit below the thresholds at which Google's data-driven attribution (historically near 400 conversions per 28 days) and value-based Smart Bidding (roughly 15 to 30 conversions per month) stabilize. The consequence is not that the offline-revenue loop is pointless; it is that the loop's near-term value is cost-per-booked-job visibility for the operator rather than fully-engaged automated bidding. Chapter 16 (Lessons, Limitations, Future Work) returns to this ceiling.

Findings, ranked

SeverityFindingMechanismBusiness consequence
P0MP posted to www.google.com/mp/collect (both routes)Wrong host returns 404; payload never ingestedgenerate_lead reached GA4 zero times for the property's life; only ad-blockable client event survived
P0Synthetic client_id = server.<timestamp>Fabricated ID per cookieless leadPhantom one-session users; distorted per-user metrics
P0MP payload missing session_id, engagement_time_msecGA4 cannot stitch to sessionServer conversions attribute to (direct)/(none)
P1cta_click marked key eventMicro-interaction defined as conversionTrains Smart Bidding to buy button-clickers, not leads
P1generate_lead not a key eventEvent never seen, so never promotedCanonical lead conversion uncounted
P1Client + server + quote events double-countMultiple key events per single leadInflated conversions; deferred to Ads-link time
P1Google Ads spending while GA4 unlinkedNo product link despite google/cpc trafficNo conversion signal to bidding; no cost-per-lead
P1purchase key event (dead)GA4 default, never fired, undeletableHarmless clutter; API refuses deletion
P2Retention at TWO_MONTHSFloor caps dimensioned event dataSeasonal by-service, by-county analysis impossible
P2EM pageChanges + formInteractions onAuto-events shadow custom eventsDouble-counted page_view, form_start, form_submit
P2Orphaned careers event name mismatchcareer_application vs career_application_submitUnjoinable, unread server event
P3view_item sent as flat paramsNo items[] arrayItem reports never populate; harmless dead weight
P3Site search on with no search boxSetting enabled against absent featureNoise-only configuration

The through-line across every tier is that GA4 gives an operator almost no negative feedback. A 404 returns nothing to the application logic that swallowed it; a malformed view_item returns the same acknowledgement as a valid one; an over-broad key event looks identical to a well-chosen one until the bidding money follows it; a two-month retention cap surfaces only when someone tries to build the report it forbids. The property passed every check it was capable of running on itself. The audit's contribution was to run the checks the property could not, then rank what it found by the money and the decisions each defect touched. Chapter 6 begins the reconstruction with the P0 host fix.