Reading a GA4 Collect Payload, Parameter by Parameter
Published

The GA4 DebugView is a comfortable place to work and a bad place to debug. It shows you what Google decided to display after processing, it lags by seconds to minutes, it needs debug mode enabled, and it quietly omits things. When a hit is malformed, DebugView often shows you nothing at all — which looks identical to a tag that never fired.
The raw request has none of those problems. It is the actual bytes leaving the browser, available immediately, with every parameter visible. Learning to read it is the difference between "the tag isn't working" and "the tag fires but session_start is being set because the linker parameter expired."
Here is what is in one.
Where to find the request
GA4 hits go to https://www.google-analytics.com/g/collect (or /mp/collect for Measurement Protocol, or your own domain if you run server-side GTM). In browser devtools, open the Network tab and filter for collect.
Most parameters are in the query string. On a POST — which GA4 uses when the payload gets long, or when batching — additional events are in the request body, one per line, and the query string carries only the shared parameters.
A typical hit looks like this, decoded and split for readability:
1v=22tid=G-XXXXXXXXXX3gtm=45je55h1v9186062431za2004_p=17358420195cid=1234567890.17358420196ul=en-gb7sr=2560x14408_s=29sid=173584201910sct=411seg=112dl=https%3A%2F%2Fexample.com%2Fcheckout13dt=Checkout14en=add_to_cart15ep.item_list_name=Search%20Results16epn.value=49.9917ep.currency=GBP18_et=324119gcs=G111
Twenty parameters, and almost every tracking bug in GA4 is visible in one of them.
The parameters that identify the hit
v=2 — protocol version. GA4 is always 2. If you see v=1, you are looking at a Universal Analytics hit from something that was never migrated.
tid — the Measurement ID, G-XXXXXXXXXX. First thing to check, always. Staging IDs reaching production is one of the most common findings in an audit, and it is invisible everywhere except here and in the tag config.
en — the event name. page_view, add_to_cart, purchase, or whatever you named it. If en is missing, the hit is a configuration/init hit rather than an event.
gtm — an encoded container fingerprint. You cannot read much from it directly, but if two hits on the same page have different gtm values, two different containers or two different loads of the same container are on the page.
The parameters that identify the user and session
cid — the client ID, from the _ga cookie. Format is 1234567890.1735842019: a random number and the timestamp the cookie was created. This is the device identifier that everything unauthenticated is joined on.
If cid changes between two hits in the same visit, the cookie is being rewritten — usually a cross-domain problem, sometimes a cookie-consent script clearing storage after the first hit. That is a session split, and it means two "users" where there was one.
sid — the session ID, a Unix timestamp of when the session started. Same sid across hits means the same session. A new sid mid-journey means the session restarted, which is what a broken cross-domain setup looks like from the wire.
sct — session count. How many sessions this client ID has had. sct=1 on a user you know is a returning visitor means their cookie was lost.
seg — session engaged. 1 if the session has met the engagement threshold (10+ seconds, a key event, or 2+ page views). seg=0 on hits that should obviously be engaged is worth investigating.
_p — a random page-load identifier. Every hit from the same page load shares it. This is the fastest way to spot duplicates: two page_view hits with the same _p are two tags on one page load, not a user who refreshed.
_s — the hit sequence number within the session. Increments per hit. Gaps mean hits are being dropped, usually by an ad blocker or a network failure.
_p is the duplicate detector. If you take one thing from this article, take this. Filter your network tab to collect, look at en and _p together, and any repeated pair is a double-fire. It takes five seconds and it catches the most common data-quality bug in GA4.
The parameters that carry your data
This is where custom tracking lives, and the prefixes matter.
ep.<name> — event parameter, string value. ep.item_list_name=Search%20Results.
epn.<name> — event parameter, numeric value. epn.value=49.99. GA4 splits string and numeric parameters into different prefixes because they map to different underlying types. If a value you expect to be numeric arrives as ep. rather than epn., it is being sent as a string, and it will not aggregate as a metric.
up.<name> and upn.<name> — user properties, string and numeric. These are user-scoped and persist across the session.
uid — the user ID, if you set one. Its presence is the single check for whether your user identity setup is working. It should appear on every hit after authentication, not just the login hit.
Now the important part: a parameter appearing here does not mean it will appear in any report. ep.item_list_name is collected and stored, but unless item_list_name is registered as a custom dimension in GA4, no report, exploration, or audience can ever reach it. This is the registration gap, and it is the most expensive silent failure in GA4 — the payload looks perfect, the reports show nothing. Custom dimensions vs event parameters covers it in full.
The parameters that describe the page and device
dl — document location, URL-encoded. Check this for two things: PII in query strings (email addresses in a ?email= parameter are a policy violation), and unexpected UTM parameters on internal navigation, which restarts attribution.
dr — document referrer. Your own domain appearing here is a self-referral.
dt — document title.
ul — user language. sr — screen resolution. vp — viewport size. Rarely interesting, occasionally decisive when a mobile-only bug is in play.
_et — engagement time in milliseconds since the last hit. This is what feeds "average engagement time." If every hit shows a tiny _et, something is reloading the page or the measurement is being reset.
The consent parameters
gcs — Google Consent State, a four-character code. G1 is a fixed prefix, then one digit for ad_storage and one for analytics_storage: 1 granted, 0 denied.
gcs=G111— both grantedgcs=G100— both deniedgcs=G101— ads denied, analytics granted
gcd — the fuller consent signal covering all four Consent Mode v2 types, including ad_user_data and ad_personalization.
dma, npa — regional and non-personalised-ads flags.
If you are implementing Consent Mode v2, gcs is your verification. Load the page, decline consent, and check that hits either do not fire or carry gcs=G100. Accept, and confirm the value updates on subsequent hits. Nothing in the GTM interface proves this; the payload does.
A debugging workflow using the payload
Once you can read the parameters, the process is mechanical.
Reproduce with the network tab open, filtered to collect.
Check tid first. Wrong property is the cheapest possible finding and it invalidates everything else.
Check en and _p. Right event names? Any duplicate pairs?
Check cid and sid are stable across the whole journey. Instability here means a session-splitting bug, and it will corrupt every attribution report.
Check your ep. and epn. parameters are present and correctly typed. Then check they are registered in GA4, because presence is not sufficiency.
Check gcs matches the consent state you actually granted.
Check dl for PII.
That sequence catches the large majority of GA4 tracking bugs, and it takes about two minutes once the workflow is familiar.
How Tagfire helps
Reading a raw payload in the network tab works, but it is slow and it is easy to miss a parameter you were not looking for.
The Payload Decoder takes any GA4, GTM, or Measurement Protocol request — paste the URL or the full request — and explains every parameter in plain English, including the ones this article does not cover. It runs on the marketing site with no account and no sign-up.
The Tag Debugger captures hits automatically as you browse, decoded and grouped by event, so you do not have to open devtools at all. It flags duplicate _p values, shows the consent state on each hit, and lets you compare hits side by side. When you find a journey that matters, you can record it and replay it on a schedule so the same parameters get checked every week without anybody opening a browser.
The GA4 Sync Checker closes the loop on the registration gap: it compares the parameters your tags actually send against the custom dimensions GA4 has registered and names what is missing on each side.
The live debugger documentation covers the capture workflow, and the payload decoder section covers reading a decoded hit.

Live Tag Debugging for GA4 and GTM
How to debug GA4 and GTM tags by watching hits fire in real time — what GTM Preview and DebugView miss, a repeatable workflow, and how to turn a debugging session into an automated check.

Custom Dimensions vs Event Parameters: The Registration Gap
Why a parameter can be collected perfectly and still be invisible in every GA4 report — how registration works, the limits that bite, and how to find the gap in your own property.

The 2026 GA4 + GTM Setup Checklist
A complete technical checklist for Google Analytics 4 and Google Tag Manager — collection, configuration, consent, campaign tracking, and governance. Verify each item before you trust a single report.