Tutorial: Reading a GA4 Collect Request Parameter by Parameter

Contents
A GA4 hit is one HTTP request, and everything the property will ever know about that moment is in its query string. Sixty parameters, all abbreviated to two or three characters, none of them documented in the interface.
Reading them is worth learning for one reason: it is the only place where what the browser actually sent can be seen. Every report, every debug view and every consent indicator is downstream of this request, and a value that is not here was never sent.

Finding the Request
The request goes to /g/collect, either at Google directly or at the server container if one is in use. In the browser’s network panel, a filter on collect reduces the list to exactly these hits.
https://www.google-analytics.com/g/collect
?v=2&tid=G-AB12CD34>m=45je55h0v9182736&_p=1737041234567
&cid=1234567890.1737041234&ul=de-de&sr=2560x1440&_s=2
&sid=1737041234&sct=4&seg=1&dl=https%3A%2F%2Fshop.example%2Fkasse
&dt=Kasse&dr=https%3A%2F%2Fwww.google.com%2F&en=purchase
&ep.payment_type=card&epn.value=129.9&cu=EUR&_et=4312
&pr1=idSKU-42~nmStuhl~caMoebel~pr64.95~qt2&gcs=G111
Two practical notes about where it hides. Small hits travel as a GET and the parameters are in the address; larger ones switch to a POST, and then the address carries only part of them while the rest sits in the request body. And several events can be batched into one POST, one per line – a body with three lines is three hits, not one.
A request that never appears is a finding in itself, and it has three usual causes: consent is denied and the tag is holding back, an ad blocker removed the script, or the tag never fired. The three are told apart in the tag debugger, not here.
The Parameters That Identify
The first group answers who is sending and to which property. These parameters are on every hit and change rarely.
| Parameter | Meaning |
|---|---|
| v=2 | Protocol version – 2 is GA4, 1 was Universal Analytics |
| tid | Measurement ID of the target property |
| cid | Client ID, the value from the _ga cookie without its prefix |
| sid | Session ID – the epoch second at which the session started |
| sct | Session count for this client |
| seg | 1 once the session counts as engaged, 0 before that |
| _p | Random number per page load, groups the hits of one page |
| _s | Counter within the page load – the first hit is 1 |
Two of these are more useful than they look. The session ID is a timestamp, so it says exactly when the session began – a session that keeps restarting is visible here before any report shows it. And _s makes gaps obvious: a page whose hits jump from 1 to 3 lost one on the way.
The Parameters That Describe the Page
The second group is the context, and it is where most wrong values originate, because these are the parameters a tag can overwrite.
dl is the page location and arrives percent-encoded; it is the source of the page path and of every query parameter GA4 later reports. dr is the referrer and empty on a direct entry. dt is the document title, taken at the moment the hit fires – which is why a single-page application that fires before the title changes reports the previous page’s title.
ul is the browser language and sr the screen resolution. Both come from the browser and are the two fields that most often reveal an automated visitor: a resolution of 800×600 paired with an English locale on a German shop is a pattern rather than a person.
Worth checking here rather than in a report: whether dl carries parameters that should not be in analytics at all. Everything after the question mark ends up in the page dimension, and a form that puts an email address in the URL sends it here first.
The Event and Its Parameters
The third group is what actually happened, and it follows a simple naming rule.
en=purchase the event name
ep.payment_type=card event parameter, text
epn.value=129.9 event parameter, number
up.plan=premium user property, text
upn.credits=40 user property, number
_et=4312 engagement time in milliseconds since the last hit
cu=EUR currency
The distinction between ep. and epn. is the one that costs the most time when it is wrong. A number sent as text lands in a text dimension, cannot be summed, and cannot be turned into a metric afterwards – and nothing reports an error, because a text parameter with the content 129.9 is perfectly valid.
The engagement time is cumulative per hit, not per session: each request carries the milliseconds since the previous one. A page that sends 0 here throughout is not being looked at, or the timer is being reset by something.
The E-Commerce Payload in One Field
Items do not each get their own parameter. They are packed into pr1, pr2 and so on, one field per item, with two-letter codes and a tilde as separator.
pr1=idSKU-42~nmStuhl~caMoebel~pr64.95~qt2~brNordholz~vaEiche
| Code | GA4 field | Code | GA4 field |
|---|---|---|---|
| id | item_id | pr | price |
| nm | item_name | qt | quantity |
| br | item_brand | ds | discount |
| ca | item_category | cp | coupon |
| c2 … c5 | item_category2 … 5 | ln | item_list_name |
| va | item_variant | lp | index |
Three things become checkable once this is readable. Whether every item carries an id – an item without one is dropped silently. Whether pr is a plain number: a price arriving as 64,95 with a comma, or with a currency symbol, is not a number and the revenue stays empty. And whether the sum of price times quantity matches epn.value, because the two are sent independently and a mismatch between them is invisible in every report.
The number of items has a ceiling that is easy to hit. A request has a length limit, and a cart with two hundred lines exceeds it – GA4 then switches to a POST, and beyond that it truncates. A purchase whose item list is shorter in the reports than in the shop is almost always this.
The Consent Fields
The last group is short and decides more than the rest together.
gcs=G111 ad_storage granted, analytics_storage granted
gcs=G100 both denied
gcs=G101 ad_storage denied, analytics_storage granted
gcd=13t3t3t3t5 the defaults that were set before the choice
The two digits after G1 are ad storage and analytics storage in that order, 1 for granted and 0 for denied. A hit with G100 is a cookieless ping: it counts towards modelling but writes no identifier, and the client ID in it is generated per request rather than persisted.
The field to compare it against is gcd, which records what the defaults were. A container whose defaults arrive as granted while the banner has not been answered yet is a misconfiguration that only shows here – the reports look normal, because the data is being collected.
One last check that takes ten seconds and settles a whole class of arguments: reload the page with consent denied and look for the request. If a hit with gcs=G100 goes out, consent mode is working as designed. If nothing goes out at all, the tag is being blocked entirely – which is a different setup with different reports, and worth knowing which one is in place.