GA4 gtag Inbound
If your site already runs Google Analytics 4 through gtag.js, Datafly Signal can receive those hits directly. You set gtag’s server_container_url to your Signal collection endpoint and gtag sends every event to Signal instead of to Google. Signal translates each hit into a standard Signal event and runs it through your pipeline, so it reaches GA4 and every other integration you have configured.
This is a migration first step, not the end state. gtag.js still loads from Google, so ad blockers that block googletagmanager.com still stop these hits. Use it to get your existing tagging flowing through Signal on day one, then move to the first-party Datafly.js collector as described in Migrating from gtag.js.
Set it up
Turn on GA4 inbound for the pipeline
In the management UI, open Pipelines → your pipeline → Settings → GA4 inbound (gtag). Switch it on and, optionally, list the GA4 measurement IDs (G-XXXXXXXXXX) this pipeline should accept. Save.
Hits are refused until GA4 inbound is on. When you list measurement IDs, hits for any other GA4 property are refused too.
Point gtag at Signal
Copy the server_container_url shown on the card into your gtag config:
gtag('config', 'G-XXXXXXXXXX', {
server_container_url: 'https://collect.yourdomain.com/ga/<pipeline_key>'
});gtag adds /g/collect to that URL, so hits arrive at https://collect.yourdomain.com/ga/<pipeline_key>/g/collect. If you use Google Tag Manager, set the same value as the server_container_url configuration parameter on your Google tag.
Add a GA4 integration to the pipeline
Once hits go to Signal they no longer go to Google directly. Add the Google Analytics 4 integration to the pipeline so the events are delivered to your GA4 property.
Check events in the Event Debugger
Turn on debug mode for the pipeline and browse your site. gtag hits appear in the Event Debugger like any other event, with context.library.name set to gtag. If hits are refused, the debugger shows why (for example, GA4 inbound switched off or a measurement ID that isn’t on the list).
Your collection endpoint should be a first-party subdomain of your site (for example collect.yourdomain.com), the same one the Datafly.js collector uses. The pipeline’s allowed-domains list still applies to gtag hits.
How hits are translated
gtag sends shared parameters on the URL and, when it batches, one event per line in the request body. Signal merges the two for each event and maps them as follows.
Events
| gtag | Signal event |
|---|---|
en=page_view | type page, event page (the same as datafly.page()) |
any other en | type track, event name unchanged (for example add_to_cart, purchase, scroll) |
ep.<name> | properties.<name> (string) |
epn.<name> | properties.<name> (number) |
up.<name> / upn.<name> | properties.user_properties.<name> |
cu | properties.currency |
pr1 … prN | properties.items[] (see below) |
_dbg=1 or ep.debug_mode | properties.debug_mode = true |
tid | properties.ga_measurement_id |
_p | properties.ga_page_load_id |
Identity
| gtag | Signal |
|---|---|
cid | context.vendor_ids.ga_client_id, and stored under the visitor’s identity |
uid | user_id |
The anonymous ID is your existing _dfid cookie when there is one (for example when Datafly.js also runs on the site). Otherwise Signal derives a stable anonymous ID from the GA client ID and sets the _dfid cookie, persistent only when the hit’s consent allows it.
Page, device and session
| gtag | Signal |
|---|---|
dl | context.page.url, plus path and search |
dt | context.page.title |
dr | context.page.referrer |
utm_* in dl | context.campaign |
click IDs in dl (gclid, fbclid, msclkid …) | click_ids, the same list Datafly.js captures |
ul | context.locale |
sr | context.screen.width / height |
uap, uapv, uam | context.device.os, os_version, model |
sid | context.session.id (converted to milliseconds) |
sct | context.session.number |
_ss, _nsi | context.session.is_new |
_fv | context.session.is_first_ever |
seg | context.session.engaged |
_et | context.session.engagement_time |
IP address, user agent, geolocation and network come from the request, exactly as for collector events.
Consent
gcs and gcd (Google Consent Mode) are decoded into context.consent.google (ad_storage, analytics_storage, ad_user_data, ad_personalization). Signal also sets the consent categories your integrations are gated on:
- analytics follows
analytics_storage - marketing is granted only when
ad_storageis granted andad_user_datais not denied
A hit with no Consent Mode signal is treated like a collector event from a pipeline with no consent provider.
Ecommerce items
Each prN parameter becomes one entry in properties.items, in the GA4 item schema:
| Item key | Field | Item key | Field | |
|---|---|---|---|---|
id | item_id | ln | item_list_name | |
nm | item_name | li | item_list_id | |
br | item_brand | af | affiliation | |
ca, c2–c5 | item_category – item_category5 | cp | coupon | |
va | item_variant | ds | discount | |
pr | price | lo | location_id | |
qt | quantity | pi, pn | promotion_id, promotion_name | |
lp | index | cn, cs | creative_name, creative_slot |
Custom item parameters (k0/v0 pairs) become fields named after the key. Event-level ecommerce parameters such as ep.transaction_id, epn.value, epn.tax and epn.shipping arrive as ordinary properties.
Limits and behaviour
- The endpoint accepts
GETandPOSTand returns204 No Content. - Up to 50 events per request and 200 items per event. Bodies over 1 MB are refused.
- Pipeline rate limits, monthly quota, IP blocklist and allowed domains apply as for any collector traffic. A
GEThit without anOriginheader is checked against itsReferer. - gtag cannot sign requests or send challenge tokens, so server-type pipelines and pipelines that require challenge tokens refuse gtag hits.
- gtag doesn’t report a browser timezone. Bot detection doesn’t count that (or a missing session on cookieless pings) against gtag hits.