Google Analytics 4
Datafly Signal delivers events to Google Analytics 4 server-to-server using the GA4 Measurement Protocol. This bypasses the need for the gtag.js client-side library entirely.
Prerequisites
Before configuring GA4 in Datafly Signal, you need a GA4 property, a web data stream, and a Measurement Protocol API secret. Follow the steps below to set these up in Google Analytics.
Step 1: Create a Google Analytics 4 Property
- Go to analytics.google.com.
- Click Admin (gear icon) in the bottom-left.
- In the Account column, click Create Account (or use an existing account).
- Enter an account name and configure your data sharing preferences.
- Click Next, then enter a property name (e.g. “My Website”).
- Set your reporting timezone and currency.
- Click Create.
Step 2: Create a Web Data Stream
- In Admin > Property > Data Streams, click Add Stream > Web.
- Enter your website URL and a stream name.
- Note the Measurement ID (format:
G-XXXXXXXXXX) — you will need this when configuring the integration in Signal.
Step 3: Create a Measurement Protocol API Secret
- In the Data Stream details page, scroll down to Measurement Protocol API secrets.
- Click Create.
- Give it a descriptive name (e.g. “Datafly Signal”).
- Copy the generated secret value — you will need this when configuring the integration in Signal.
The API secret is only shown once at creation time. Store it securely. If you lose it, you will need to create a new one.
Step 4: Disable Enhanced Measurement (Optional)
Since Signal handles all event tracking server-side, you may want to disable GA4’s Enhanced Measurement to avoid duplicate events — particularly if you previously had gtag.js installed on your site.
- In the Data Stream details page, click the Enhanced Measurement toggle.
- Disable individual events you no longer need (e.g. scrolls, outbound clicks, file downloads) or turn off Enhanced Measurement entirely.
If you are migrating from a client-side GA4 setup, disabling Enhanced Measurement prevents GA4 from collecting events that Signal is now handling server-side. You can always re-enable individual events later.
API Endpoint
POST https://www.google-analytics.com/mp/collect?measurement_id={measurement_id}&api_secret={api_secret}All events are sent as JSON payloads to the Measurement Protocol endpoint. Authentication is handled via the api_secret query parameter.
Configuration
| Field | Required | Description |
|---|---|---|
measurement_id | Yes | Your GA4 Measurement ID (e.g. G-XXXXXXXXXX). Found in GA4 Admin > Data Streams. |
api_secret | Yes | Measurement Protocol API secret. Create one in GA4 Admin > Data Streams > Measurement Protocol API secrets. |
Management UI Setup
- Go to Integrations > Add Integration > Google Analytics 4.
- Enter your
measurement_idandapi_secret. - Select the consent categories (typically
analytics). - Click Save.
Management API Setup
curl -X POST http://localhost:8084/v1/admin/integrations \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"source_id": "src_abc123",
"vendor": "google_analytics_4",
"name": "GA4 Production",
"enabled": true,
"config": {
"measurement_id": "G-XXXXXXXXXX",
"api_secret": "your_api_secret"
},
"consent_categories": ["analytics"]
}'Identity
client_id
The Measurement Protocol requires a client_id to identify the user/device. Datafly Signal self-generates this value in the standard GA4 format:
{random_number}.{timestamp_seconds}For example: 1234567890.1706540000
The client_id is generated on the first event for a visitor and persisted by the Identity Hub for the lifetime of the visitor session. It is stored as a first-party cookie by Datafly.js and included in all subsequent events.
user_id
If your application calls _df.identify(userId), the user_id is forwarded to GA4 for cross-device reporting.
Session Handling
GA4’s Measurement Protocol does not automatically create sessions. Datafly Signal manages sessions server-side:
session_id: A unique numeric identifier generated at the start of each session. Stored in the Identity Hub and included with every event as thesession_idevent parameter.session_number: An incrementing counter for the visitor, tracking how many sessions they have had.- Session timeout: 30 minutes of inactivity (configurable).
{
"client_id": "1234567890.1706540000",
"events": [
{
"name": "page_view",
"params": {
"session_id": "1706540000",
"session_number": 3,
"engagement_time_msec": 100
}
}
]
}The engagement_time_msec parameter is required for events to appear in standard GA4 reports. Datafly Signal sets this to a minimum of 100 milliseconds to ensure events are counted as engaged.
Event Mapping
Datafly Signal maps standard event names to their GA4 equivalents:
| Datafly Event | GA4 Event | Notes |
|---|---|---|
page (page view) | page_view | Includes page_location, page_title, page_referrer |
Product Purchased / Order Completed | purchase | Requires transaction_id, value, currency |
Product Added | add_to_cart | Requires items array |
Product Removed | remove_from_cart | Requires items array |
Checkout Started | begin_checkout | Requires items array, value, currency |
Products Searched | search | Requires search_term |
Product Viewed | view_item | Requires items array |
Product List Viewed | view_item_list | Requires items array, item_list_name |
| Custom events | Passed through | Any _df.track("event_name") is sent as-is |
Example: Purchase Event
Datafly.js call:
_df.track("Order Completed", {
order_id: "ORD-001",
total: 129.99,
currency: "USD",
products: [
{ product_id: "SKU-A", name: "Widget", price: 49.99, quantity: 2 },
{ product_id: "SKU-B", name: "Gadget", price: 30.01, quantity: 1 }
]
});GA4 Measurement Protocol payload sent by Datafly:
{
"client_id": "1234567890.1706540000",
"events": [
{
"name": "purchase",
"params": {
"transaction_id": "ORD-001",
"value": 129.99,
"currency": "USD",
"session_id": "1706540000",
"session_number": 3,
"engagement_time_msec": 100,
"items": [
{
"item_id": "SKU-A",
"item_name": "Widget",
"price": 49.99,
"quantity": 2
},
{
"item_id": "SKU-B",
"item_name": "Gadget",
"price": 30.01,
"quantity": 1
}
]
}
}
]
}Example: Page View
GA4 Measurement Protocol payload:
{
"client_id": "1234567890.1706540000",
"events": [
{
"name": "page_view",
"params": {
"page_location": "https://example.com/products/widgets",
"page_title": "Widgets | Example Store",
"page_referrer": "https://www.google.com/",
"session_id": "1706540000",
"session_number": 3,
"engagement_time_msec": 100
}
}
]
}Custom Events
Any event not in the mapping table is sent to GA4 with its original name. All event properties are forwarded as GA4 event parameters.
_df.track("newsletter_signup", { method: "footer_form" });{
"client_id": "1234567890.1706540000",
"events": [
{
"name": "newsletter_signup",
"params": {
"method": "footer_form",
"session_id": "1706540000",
"session_number": 1,
"engagement_time_msec": 100
}
}
]
}GA4 event names must be 40 characters or fewer, use only letters, numbers, and underscores, and must start with a letter. Datafly Signal automatically sanitises event names to comply with these requirements.
Parameters
Event-Level Parameters
| Parameter | Type | Description |
|---|---|---|
session_id | string | Server-generated session identifier |
session_number | integer | Incrementing session count per visitor |
engagement_time_msec | integer | Time in milliseconds the user was engaged (minimum 100) |
page_location | string | Full URL of the page |
page_title | string | Document title |
page_referrer | string | Referrer URL |
E-commerce Parameters
| Parameter | Type | Description |
|---|---|---|
transaction_id | string | Unique order/transaction identifier |
value | number | Total monetary value |
currency | string | ISO 4217 currency code (e.g. USD, EUR) |
items | array | Array of item objects |
Item Parameters
| Parameter | Type | Description |
|---|---|---|
item_id | string | Product SKU or identifier |
item_name | string | Product name |
price | number | Unit price |
quantity | integer | Number of units |
item_category | string | Product category |
item_brand | string | Product brand |
Limitations
- Google Signals: Demographic and interest data from Google Signals is not available with server-side Measurement Protocol. These features require the client-side
gtag.jslibrary. - Automatic events: GA4 automatic events (e.g.
scroll,outbound_click,file_download) are not collected unless you explicitly track them with Datafly.js. - Real-time reports: Events sent via the Measurement Protocol may take a few seconds longer to appear in GA4 real-time reports compared to client-side collection.
- BigQuery export: Events sent via the Measurement Protocol are included in BigQuery exports but may have limited attribution data.
- Parameter limits: GA4 allows a maximum of 25 event parameters per event and 25 user properties per project.
Debugging
Use the GA4 debug endpoint to validate payloads without sending data to production:
POST https://www.google-analytics.com/debug/mp/collect?measurement_id={measurement_id}&api_secret={api_secret}The debug endpoint returns validation messages for each event:
{
"validationMessages": [
{
"fieldPath": "events[0].params.value",
"description": "Expected type NUMBER, got STRING.",
"validationCode": "VALUE_INVALID"
}
]
}To enable debug mode for your integration, set debug_mode: true in the integration configuration. This routes events through the debug endpoint and logs the validation responses.
{
"config": {
"measurement_id": "G-XXXXXXXXXX",
"api_secret": "your_api_secret",
"debug_mode": true
}
}Events sent to the debug endpoint are not recorded in your GA4 property. Disable debug_mode before going to production.
Rate Limits
Google’s Measurement Protocol does not publish strict rate limits, but the following defaults are used:
| Setting | Default |
|---|---|
rate_limit_rps | 100 |
rate_limit_burst | 200 |
If you experience 429 Too Many Requests responses, reduce rate_limit_rps or contact Google to discuss higher quotas.