Files
2025-11-29 18:32:40 +08:00

9.1 KiB

GA4 Measurement Protocol Complete API Guide

API Overview

Base URL: https://www.google-analytics.com/mp/collect Debug URL: https://www.google-analytics.com/debug/mp/collect

Method: POST Content-Type: application/json

Authentication

API Secret Creation

  1. GA4 AdminData Streams
  2. Select your data stream
  3. Measurement Protocol API secrets section
  4. Click Create
  5. Name: "Server tracking" (or descriptive name)
  6. Click Create
  7. Copy secret value (shown only once!)

Storage:

  • Store secret securely (environment variables, secrets manager)
  • Never commit to version control
  • Regenerate if exposed

Measurement ID

Format: G-XXXXXXXXXX

Find in:

  • GA4 Admin → Data Streams → Web Stream
  • "Measurement ID" field at top

Request Format

Complete URL

POST https://www.google-analytics.com/mp/collect?measurement_id=G-XXXXXXXXXX&api_secret=YOUR_API_SECRET

Query Parameters:

  • measurement_id: GA4 Measurement ID
  • api_secret: API secret from GA4

Request Body Structure

{
  "client_id": "CLIENT_ID_HERE",
  "user_id": "USER_123",
  "timestamp_micros": "1234567890123456",
  "user_properties": {
    "user_tier": {
      "value": "premium"
    },
    "lifetime_value": {
      "value": 1250.50
    }
  },
  "consent": {
    "ad_storage": "granted",
    "analytics_storage": "granted",
    "ad_user_data": "granted",
    "ad_personalization": "granted"
  },
  "events": [
    {
      "name": "purchase",
      "params": {
        "transaction_id": "T_12345",
        "value": 99.99,
        "currency": "USD",
        "tax": 8.00,
        "shipping": 5.00,
        "items": [
          {
            "item_id": "SKU_123",
            "item_name": "Product Name",
            "price": 99.99,
            "quantity": 1
          }
        ]
      }
    }
  ]
}

Field Specifications

client_id (Required)

Type: String Format: UUID recommended Purpose: Unique client identifier

Recommendations:

  • Use UUID v4 format
  • Persist for same user across sessions
  • Store in database for logged-in users
  • Never use PII (email, name, etc.)

Example:

import uuid
client_id = str(uuid.uuid4())

user_id (Optional)

Type: String Purpose: Cross-device tracking

When to Use:

  • User is logged in
  • Want to track across devices
  • Have user identifier

Important:

  • Don't use email or PII directly
  • Hash or use internal ID
  • Must also send client_id

timestamp_micros (Optional)

Type: String (integer as string) Format: Microseconds since Unix epoch

Purpose:

  • Send historical events
  • Backfill data
  • Correct timing for delayed events

Limits:

  • Max 3 days in past
  • Max 72 hours in future

Example:

import time
timestamp_micros = str(int(time.time() * 1_000_000))

user_properties (Optional)

Type: Object Purpose: Set user-level properties

Format:

{
  "property_name": {
    "value": "property_value"
  }
}

Limits:

  • Max 25 user properties per request
  • Property name ≤24 characters
  • Value ≤36 characters

Example:

{
  "user_tier": {"value": "premium"},
  "signup_date": {"value": "2024-01-15"},
  "total_purchases": {"value": 12}
}

Type: Object Purpose: Set consent status

Fields:

  • ad_storage: "granted" | "denied"
  • analytics_storage: "granted" | "denied"
  • ad_user_data: "granted" | "denied"
  • ad_personalization: "granted" | "denied"

Example:

{
  "ad_storage": "denied",
  "analytics_storage": "granted"
}

events (Required)

Type: Array Limits: Max 25 events per request

Event Structure:

{
  "name": "event_name",
  "params": {
    "parameter": "value"
  }
}

Event Name Rules:

  • Lowercase letters, numbers, underscores
  • Max 40 characters
  • Cannot start with number
  • Avoid reserved names

Event Parameters:

  • Max 25 parameters per event
  • Parameter name ≤40 characters
  • String value ≤100 characters

Common Event Examples

Page View

{
  "client_id": "client_123",
  "events": [{
    "name": "page_view",
    "params": {
      "page_location": "https://example.com/products",
      "page_title": "Products Page",
      "page_referrer": "https://google.com"
    }
  }]
}

Purchase

{
  "client_id": "client_123",
  "events": [{
    "name": "purchase",
    "params": {
      "transaction_id": "T_12345",
      "value": 99.99,
      "currency": "USD",
      "tax": 8.00,
      "shipping": 5.00,
      "items": [{
        "item_id": "SKU_123",
        "item_name": "Blue T-Shirt",
        "price": 99.99,
        "quantity": 1,
        "item_category": "Apparel"
      }]
    }
  }]
}

Lead Generation

{
  "client_id": "client_123",
  "events": [{
    "name": "generate_lead",
    "params": {
      "currency": "USD",
      "value": 50.00,
      "form_name": "contact_form",
      "lead_source": "website"
    }
  }]
}

Sign Up

{
  "client_id": "client_123",
  "user_id": "user_456",
  "events": [{
    "name": "sign_up",
    "params": {
      "method": "email"
    }
  }]
}

Custom Event

{
  "client_id": "client_123",
  "events": [{
    "name": "subscription_renewed",
    "params": {
      "subscription_tier": "premium",
      "renewal_amount": 29.99,
      "currency": "USD",
      "subscription_id": "SUB_789"
    }
  }]
}

Debug Endpoint Validation

Using cURL

curl -X POST \
  "https://www.google-analytics.com/debug/mp/collect?measurement_id=G-XXXXXXXXXX&api_secret=YOUR_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "test_123",
    "events": [{
      "name": "purchase",
      "params": {
        "transaction_id": "T_TEST",
        "value": 99.99,
        "currency": "USD"
      }
    }]
  }'

Valid Response

Status: 200 OK

Body (Valid):

{
  "validationMessages": []
}

Empty array = No validation errors

Invalid Response

Body (Invalid):

{
  "validationMessages": [
    {
      "fieldPath": "events[0].name",
      "description": "Event name must be 40 characters or fewer.",
      "validationCode": "NAME_INVALID"
    },
    {
      "fieldPath": "events[0].params.value",
      "description": "Event parameter value is invalid.",
      "validationCode": "VALUE_INVALID"
    }
  ]
}

Rate Limits and Quotas

No Official Published Limits

Recommendations:

  • Batch events when possible (max 25/request)
  • Implement exponential backoff for retries
  • Monitor for rate limit responses
  • Don't exceed ~1 request per second per client

Best Practices:

  • Queue events on server
  • Batch send periodically
  • Retry failed requests with backoff

Error Handling

Python Example

import requests
import time

def send_with_retry(payload, max_retries=3):
    for attempt in range(max_retries):
        try:
            response = requests.post(ENDPOINT, json=payload)

            if response.status_code == 204:
                return True  # Success

            if response.status_code == 429:  # Rate limited
                wait = (2 ** attempt) + random.random()
                time.sleep(wait)
                continue

            # Other error
            print(f"Error: {response.status_code}")
            return False

        except requests.RequestException as e:
            print(f"Request failed: {e}")
            if attempt < max_retries - 1:
                time.sleep(2 ** attempt)

    return False

Node.js Example

async function sendWithRetry(payload, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const response = await axios.post(ENDPOINT, payload);

      if (response.status === 204) {
        return true;  // Success
      }

      if (response.status === 429) {  // Rate limited
        const wait = Math.pow(2, attempt) * 1000;
        await new Promise(resolve => setTimeout(resolve, wait));
        continue;
      }

      console.error('Error:', response.status);
      return false;

    } catch (error) {
      console.error('Request failed:', error.message);
      if (attempt < maxRetries - 1) {
        await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
      }
    }
  }

  return false;
}

Testing Checklist

  • API secret generated and stored securely
  • Measurement ID correct format (G-XXXXXXXXXX)
  • client_id generated (UUID format)
  • Events validated using debug endpoint
  • No validation errors in debug response
  • Events appear in GA4 DebugView (with debug_mode=true)
  • Parameters have correct data types
  • No PII in parameters
  • Error handling implemented
  • Retry logic with exponential backoff
  • Events batched when appropriate
  • Consent parameters set correctly

Common Issues

Events Not Appearing:

  • Wrong Measurement ID
  • Wrong API secret
  • Events not validated first
  • Network/firewall blocking requests

Validation Errors:

  • Event name too long (>40 characters)
  • Reserved event name used
  • Invalid parameter values
  • Wrong data types

Missing Data:

  • client_id not consistent
  • timestamp_micros out of range
  • Parameters missing or misspelled