Our own conversion pipeline reported eight successful uploads to Google Analytics. Analytics had recorded none of them — and the endpoint that accepted every one of them answered 204.
An HTTP 204 from the GA4 Measurement Protocol is proof of transport and nothing else. It means the bytes arrived. It does not mean the payload parsed, and it does not mean a single event was written. Google's production collection endpoint returns the same 204 for a payload it accepts and a payload it discards, which means a server-side pipeline can log ninety days of clean successes into its own database while the property it is feeding stays empty. That is what happened to us, on our own site, between 2026-06-09 and 2026-08-02.
The generalizable part is not the bug. It is the shape of the mistake: we were reading the instrument that confirms delivery instead of the instrument that confirms acceptance, and those are two different endpoints with two different answers. We now run a four-step check before trusting any ingestion surface, and we call it Transport Is Not Acceptance.
Two instruments, one contradiction
The pipeline's own database was read on 2026-08-02. Filtered to the GA4 Measurement Protocol platform, the uploads table held eight rows marked success and three marked skipped, spanning 2026-06-09 through 2026-08-02. Read against a ninety-day window on the same day, GA4 returned zero generate_lead events.
A live probe on the same day narrowed it further. Within thirty minutes of firing a test lead through the site, GA4 realtime showed page_view 5, form_start 2, session_start 2 and first_visit 1 — and generate_lead 0. Client-side tagging was healthy. Every event the browser sent arrived. The server-side path was dead, and it had been reporting success the entire time it was dead.
The repair we almost shipped
The stated prime suspect was credentials. A rotated measurement ID and a fresh API secret were one deploy away, the change was cheap, and the reasoning was defensible enough that a reasonable operator would have shipped it before lunch.
It would have fixed nothing, consumed a deploy and a promote, and left the real defect in place with the leading hypothesis apparently eliminated. That last clause is the expensive part. A failed credential rotation does not just waste an afternoon; it retires the best-supported theory and sends the next investigator down the list, further from the answer than when they started.
What killed the credential hypothesis was the first isolation send. The same measurement ID and the same API secret collected cleanly the moment the consent block was absent from the payload. Credentials that work in one payload shape and fail in another are not the variable.
Step 1 — Find the endpoint that validates, not the one that accepts
The Measurement Protocol has a sibling endpoint whose entire job is to tell you what production will not. Production answers 204 and says nothing. The debug endpoint takes an identical payload and returns a validation report.
Ours, on 2026-08-03, read: "Unable to parse Measurement Protocol JSON payload. unknown enum value: 'granted'", with validationCode: VALUE_INVALID.
The worker was emitting ad_user_data: 'granted' in lowercase. The GA4 consent enum is uppercase. One unrecognized enum value discards the entire payload — not the offending field, the payload — and production still answers 204, in 54 ms, looking exactly like success. The standing lesson we wrote down that day: the endpoint that tells you the truth is not the one you are shipping to. This case has a second life in our Instruments That Lie series, filed under silent success — the failure family in which an instrument answers in the 200s while writing nothing.
Step 2 — Isolate one variable per send
Three sends, 2026-08-03, one variable changed each time:
- Minimal payload, no consent block. Landed clean. Credentials cleared.
- Full worker payload shape, lowercase consent.
VALUE_INVALID. Never landed. Defect reproduced. - Full worker payload shape, uppercase consent. Landed. Fix confirmed.
Send 1 eliminated the leading hypothesis. Send 2 reproduced the failure deliberately, which is the send most people skip and the only one that proves you understand the mechanism. Send 3 confirmed the repair. After the change, GA4 realtime generate_lead moved from 1 to 2 — and the increment was identified as the worker's own event by the marker on it, not inferred from the fact that a counter went up. The post-fix 204 came back in 85 ms. The extra 31 ms is the only externally visible difference between a payload that is written and a payload that is thrown away.
Step 3 — Prove the regression lock fails against the old code before you trust it
A test that passes against the broken source is not a lock. It is decoration that will pass forever, including on the day the defect returns.
The casing assertion was run against the pre-fix worker source first and confirmed to fail, then run against the fixed source and confirmed to pass. Only then was it wired into the suite. This is two extra minutes of work and it is the difference between a regression test and a comforting green square.
Step 4 — Return the database to baseline and verify
Probes write. A synthetic lead creates rows, and rows that are not cleaned up become the next investigator's phantom data.
Each probe run was reconciled against a pre-probe read: events 93 → 94 → 93, uploads 59 → 65 → 59, zero orphaned rows on completion. The baseline is not a nice-to-have — it is the reason the next person reading this table can trust that every row in it came from a real visitor.
The probe guard is asymmetric, and that is deliberate
When a synthetic probe runs, the reversible legs run for real: the pipeline database, GA4, the CRM. All three can be reconciled or deleted afterwards. The irreversible legs — Google Ads, Meta, TikTok, Microsoft — are cut at the guard, because an uploaded conversion cannot be un-sent. It enters a bidding model, and no amount of subsequent care removes its influence.
The GA4 marker is a plain synthetic: 1 parameter, deliberately not debug_mode. A debug hit can be swallowed by a Developer Traffic filter, which would destroy the evidence the probe exists to produce. The instrument you use to test the instrument has to survive the property's own configuration.
What we did not fix
The repair was casing only. When the consent block is absent from a payload entirely, the pipeline still defaults to granted, while another module in the same system defaults to denied on unknown input. Those two defaults contradict each other. The contradiction is real, it is open, and this post is not the announcement that it was resolved.
Nothing to disclose, and we checked rather than assumed
This was our own property. Client pipelines were checked directly rather than reasoned about: ProComfortSolutions was already normalizing the consent enum to uppercase, and United American Construction does not send GA4 Measurement Protocol events at all. Neither was affected. If a defect on our own stack had propagated to a client property, the disclosure would be in this paragraph instead of the all-clear.
What would prove this wrong
- A server-side payload carrying a lowercase consent enum that nonetheless appears in a GA4 report. That would mean casing is not the mechanism and the diagnosis is wrong.
- The debug endpoint returning valid for the pre-fix payload shape. Send 2 is reproducible by anyone with a spare property; if it validates for you, we mis-read our own output.
- A rebuild of the pre-fix worker whose events land anyway. That would put the cause somewhere we never looked.
- The post-fix realtime increment turning out not to carry the probe's marker. Then the 1 → 2 move is coincidence, not evidence, and step 2 proves nothing.
Each of those is cheap to run, which is the point. A claim that costs more to test than to believe is not a claim worth publishing.
What a 204 is worth
A 204 is a receipt for postage, not for delivery. Before you trust any ingestion endpoint — analytics, ads, CRM, webhook sink — find out whether it has a validation surface, and read that one instead. Anyone can run the four steps above against a spare property without asking us anything; there is nothing proprietary in them, and we will send the exact payload shapes to a team that would rather start from ours.
Get the kit, not just the theory.
We'll send the build checklist behind this post — and the next pillar when it ships. One email, no drip sequence. Unsubscribe in one click.