IntegrationsAnalyticsGoogle Analytics 4

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

  1. Go to analytics.google.com.
  2. Click Admin (gear icon) in the bottom-left.
  3. In the Account column, click Create Account (or use an existing account).
  4. Enter an account name and configure your data sharing preferences.
  5. Click Next, then enter a property name (e.g. “My Website”).
  6. Set your reporting timezone and currency.
  7. Click Create.

Step 2: Create a Web Data Stream

  1. In Admin > Property > Data Streams, click Add Stream > Web.
  2. Enter your website URL and a stream name.
  3. 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

  1. In the Data Stream details page, scroll down to Measurement Protocol API secrets.
  2. Click Create.
  3. Give it a descriptive name (e.g. “Datafly Signal”).
  4. 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.

  1. In the Data Stream details page, click the Enhanced Measurement toggle.
  2. 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

FieldRequiredDescription
measurement_idYesYour GA4 Measurement ID (e.g. G-XXXXXXXXXX). Found in GA4 Admin > Data Streams.
api_secretYesMeasurement Protocol API secret. Create one in GA4 Admin > Data Streams > Measurement Protocol API secrets.

Management UI Setup

  1. Go to Integrations > Add Integration > Google Analytics 4.
  2. Enter your measurement_id and api_secret.
  3. Select the consent categories (typically analytics).
  4. 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 the session_id event 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 EventGA4 EventNotes
page (page view)page_viewIncludes page_location, page_title, page_referrer
Product Purchased / Order CompletedpurchaseRequires transaction_id, value, currency
Product Addedadd_to_cartRequires items array
Product Removedremove_from_cartRequires items array
Checkout Startedbegin_checkoutRequires items array, value, currency
Products SearchedsearchRequires search_term
Product Viewedview_itemRequires items array
Product List Viewedview_item_listRequires items array, item_list_name
Custom eventsPassed throughAny _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

ParameterTypeDescription
session_idstringServer-generated session identifier
session_numberintegerIncrementing session count per visitor
engagement_time_msecintegerTime in milliseconds the user was engaged (minimum 100)
page_locationstringFull URL of the page
page_titlestringDocument title
page_referrerstringReferrer URL

E-commerce Parameters

ParameterTypeDescription
transaction_idstringUnique order/transaction identifier
valuenumberTotal monetary value
currencystringISO 4217 currency code (e.g. USD, EUR)
itemsarrayArray of item objects

Item Parameters

ParameterTypeDescription
item_idstringProduct SKU or identifier
item_namestringProduct name
pricenumberUnit price
quantityintegerNumber of units
item_categorystringProduct category
item_brandstringProduct 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.js library.
  • 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:

SettingDefault
rate_limit_rps100
rate_limit_burst200

If you experience 429 Too Many Requests responses, reduce rate_limit_rps or contact Google to discuss higher quotas.