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.
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.