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.

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.