Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:32:40 +08:00
commit 0ea8352871
72 changed files with 30043 additions and 0 deletions

View File

@@ -0,0 +1,408 @@
---
name: ga4-measurement-protocol
description: Complete guide to GA4 Measurement Protocol for server-side event tracking including API authentication, request format, validation, and implementation examples. Use when implementing server-side tracking, sending events from backend, working with Measurement Protocol API, integrating CRM with GA4, or tracking offline conversions. Covers API secrets, debug endpoint, Python/Node.js/PHP examples, and rate limits.
---
# GA4 Measurement Protocol
## Overview
The GA4 Measurement Protocol allows server-side event collection, enabling data transmission to GA4 from any HTTP-capable environment including backend servers, mobile apps, kiosks, and IoT devices.
## When to Use This Skill
Invoke this skill when:
- Implementing server-side event tracking
- Sending events from backend/server environments
- Tracking offline conversions or transactions
- Integrating CRM systems with GA4
- Sending events from mobile app backends
- Tracking server-to-server transactions
- Implementing purchase tracking from payment processors
- Tracking subscription renewals or recurring payments
- Sending lead generation events from forms backend
- Implementing custom server-side analytics
- Working with headless CMS or API-first architectures
- Debugging Measurement Protocol requests
- Validating event payloads before production
## Core Capabilities
### API Endpoints
**Production Endpoint:**
```
POST https://www.google-analytics.com/mp/collect
```
**Debug Endpoint (Validation):**
```
POST https://www.google-analytics.com/debug/mp/collect
```
**Key Difference:** Debug endpoint returns validation messages without storing data.
### Authentication Requirements
**Two Credentials Required:**
1. **Measurement ID** (format: `G-XXXXXXXXXX`)
- Find in: GA4 Admin → Data Streams → Web Stream details
2. **API Secret**
- Generate in: Data Streams → Measurement Protocol API secrets → Create
**Generating API Secret:**
1. GA4 Admin → Data Streams
2. Click your data stream
3. Scroll to "Measurement Protocol API secrets"
4. Click "Create"
5. Enter nickname (e.g., "Server-side tracking")
6. Click "Create"
7. **Copy secret immediately** (cannot retrieve later)
### Request Structure
**URL Format:**
```
https://www.google-analytics.com/mp/collect?measurement_id={MEASUREMENT_ID}&api_secret={API_SECRET}
```
**Headers:**
```
Content-Type: application/json
```
**Body (JSON):**
```json
{
"client_id": "unique_client_identifier",
"user_id": "optional_user_id",
"timestamp_micros": "1234567890123456",
"user_properties": {
"property_name": {
"value": "property_value"
}
},
"consent": {
"ad_storage": "granted",
"analytics_storage": "granted"
},
"events": [
{
"name": "event_name",
"params": {
"parameter_name": "parameter_value",
"value": 123.45,
"currency": "USD"
}
}
]
}
```
### Required Fields
- **client_id**: Unique identifier for client (UUID recommended)
- **events**: Array of event objects (max 25 events per request)
- **events[].name**: Event name (string, ≤40 characters)
### Optional Fields
- **user_id**: User identifier for cross-device tracking
- **timestamp_micros**: Event timestamp in microseconds (UTC)
- **user_properties**: User-level properties
- **consent**: Consent status (ad_storage, analytics_storage)
### Common Event Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| `session_id` | string | Session identifier |
| `engagement_time_msec` | integer | Engagement time in milliseconds |
| `page_location` | string | Full URL |
| `page_title` | string | Page title |
| `value` | number | Monetary value |
| `currency` | string | ISO 4217 currency code (USD, EUR) |
| `transaction_id` | string | Unique transaction ID |
| `items` | array | E-commerce items array |
### Python Implementation
**Using Requests Library:**
```python
import requests
import json
import uuid
MEASUREMENT_ID = "G-XXXXXXXXXX"
API_SECRET = "your_api_secret"
ENDPOINT = f"https://www.google-analytics.com/mp/collect?measurement_id={MEASUREMENT_ID}&api_secret={API_SECRET}"
def send_event(event_name, params=None):
payload = {
"client_id": str(uuid.uuid4()),
"events": [{
"name": event_name,
"params": params or {}
}]
}
response = requests.post(
ENDPOINT,
headers={"Content-Type": "application/json"},
data=json.dumps(payload)
)
return response.status_code == 204
# Send page view
send_event("page_view", {
"page_location": "https://example.com/page",
"page_title": "Example Page"
})
# Send purchase
send_event("purchase", {
"transaction_id": "T_12345",
"value": 99.99,
"currency": "USD",
"items": [{
"item_id": "SKU_123",
"item_name": "Product Name",
"price": 99.99,
"quantity": 1
}]
})
```
**Using ga4mp Library:**
```python
# Install: pip install ga4mp
from ga4mp import GtagMP
ga = GtagMP(
measurement_id="G-XXXXXXXXXX",
api_secret="your_api_secret",
client_id="unique_client_id"
)
# Send event
ga.send_event(
event_name="purchase",
event_parameters={
"transaction_id": "T_12345",
"value": 99.99,
"currency": "USD",
"items": [{
"item_id": "SKU_123",
"item_name": "Product Name",
"price": 99.99,
"quantity": 1
}]
}
)
```
### Node.js Implementation
```javascript
const axios = require('axios');
const { v4: uuidv4 } = require('uuid');
const MEASUREMENT_ID = 'G-XXXXXXXXXX';
const API_SECRET = 'your_api_secret';
const ENDPOINT = `https://www.google-analytics.com/mp/collect?measurement_id=${MEASUREMENT_ID}&api_secret=${API_SECRET}`;
async function sendEvent(eventName, params = {}) {
const payload = {
client_id: uuidv4(),
events: [{
name: eventName,
params: params
}]
};
try {
const response = await axios.post(ENDPOINT, payload, {
headers: { 'Content-Type': 'application/json' }
});
return response.status === 204;
} catch (error) {
console.error('Error sending event:', error);
return false;
}
}
// Send purchase event
sendEvent('purchase', {
transaction_id: 'T_12345',
value: 99.99,
currency: 'USD',
items: [{
item_id: 'SKU_123',
item_name: 'Product',
price: 99.99,
quantity: 1
}]
});
```
### PHP Implementation
```php
<?php
// Using php-GA4-Measurement-Protocol library
// Install: composer require br33f/php-ga4-measurement-protocol
use Br33f\Ga4\MeasurementProtocol\Dto\Event\PurchaseEvent;
use Br33f\Ga4\MeasurementProtocol\Dto\Request\MeasurementRequest;
use Br33f\Ga4\MeasurementProtocol\Service;
$measurementId = 'G-XXXXXXXXXX';
$apiSecret = 'your_api_secret';
$service = new Service($apiSecret, $measurementId);
$event = new PurchaseEvent();
$event->setTransactionId('T_12345')
->setValue(99.99)
->setCurrency('USD');
$request = new MeasurementRequest();
$request->setClientId('unique_client_id')
->addEvent($event);
$service->send($request);
?>
```
### Validation with Debug Endpoint
**Send to Debug Endpoint:**
```bash
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_client",
"events": [{
"name": "test_event",
"params": {
"test_param": "test_value"
}
}]
}'
```
**Response Format:**
```json
{
"validationMessages": [
{
"fieldPath": "events[0].name",
"description": "Event name must be 40 characters or fewer",
"validationCode": "NAME_INVALID"
}
]
}
```
**Empty Response = Valid:**
- No validationMessages = payload valid
- Status 200 = request processed
### Validation Codes
| Code | Description | Fix |
|------|-------------|-----|
| `NAME_INVALID` | Invalid event/parameter name | Use lowercase, underscores, ≤40 chars |
| `NAME_RESERVED` | Reserved name used | Check GA4 reserved names list |
| `VALUE_INVALID` | Invalid parameter value | Check data type, format |
| `VALUE_REQUIRED` | Required value missing | Add required parameter |
| `VALUE_OUT_OF_BOUNDS` | Value exceeds limits | Check numeric ranges |
| `EXCEEDED_MAX_ENTITIES` | Too many events | Max 25 events per request |
### Best Practices
1. **Always Validate First:**
- Use debug endpoint before production
- Test with sample data
- Verify response is empty (valid)
2. **Use Consistent client_id:**
- Same user = same client_id across sessions
- Store in database for logged-in users
- Use UUID format for anonymity
3. **Include session_id:**
- Maintain session continuity
- Generate unique session ID
- Keep consistent within session
4. **Batch Events:**
- Send up to 25 events per request
- More efficient than individual requests
- Reduces API calls
5. **Handle Errors Gracefully:**
- Implement retry logic with exponential backoff
- Log failed requests
- Queue events for retry
6. **Set Proper Timestamps:**
- Use `timestamp_micros` for historical data
- Convert to microseconds: `timestamp_ms * 1000`
- Max 3 days in past, 72 hours in future
7. **Respect Consent:**
- Set consent parameters appropriately
- Match frontend consent status
- Required for GDPR compliance
## Integration with Other Skills
- **ga4-setup** - Initial GA4 property and data stream setup
- **ga4-debugview** - Testing Measurement Protocol events in DebugView
- **ga4-recommended-events** - Sending recommended event structures
- **ga4-custom-events** - Server-side custom event implementation
- **ga4-user-tracking** - Implementing User ID server-side
- **ga4-privacy-compliance** - Setting consent parameters correctly
- **ga4-bigquery** - Analyzing server-side events in BigQuery
## References
- **references/measurement-protocol-complete.md** - Full API reference and examples
- **references/authentication-setup.md** - API secrets and authentication guide
- **references/event-validation.md** - Validation patterns and troubleshooting
- **references/implementation-examples.md** - Real-world implementation patterns (Python, Node.js, PHP)
- **references/rate-limits-best-practices.md** - Rate limits, batching, and optimization
## Quick Reference
**Generate API Secret:**
Admin → Data Streams → Measurement Protocol API secrets → Create
**Endpoint:**
- Production: `/mp/collect`
- Debug: `/debug/mp/collect`
**Required Fields:**
- client_id (UUID recommended)
- events array
- event name
**Max Limits:**
- 25 events per request
- 40 characters per event name
- 25 event parameters per event
- 25 user properties per request
**Validation:**
- Send to debug endpoint
- Empty response = valid
- Check validationMessages array

View File

@@ -0,0 +1,370 @@
"""
GA4 Measurement Protocol - Complete Python Implementation Example
Demonstrates server-side event tracking with validation, error handling, and batching
"""
import requests
import json
import uuid
import time
from typing import Dict, List, Optional
from datetime import datetime
class GA4MeasurementProtocol:
"""GA4 Measurement Protocol client with validation and error handling"""
def __init__(self, measurement_id: str, api_secret: str, debug: bool = False):
"""
Initialize GA4 Measurement Protocol client
Args:
measurement_id: GA4 Measurement ID (G-XXXXXXXXXX)
api_secret: API secret from GA4 Data Streams
debug: Use debug endpoint for validation
"""
self.measurement_id = measurement_id
self.api_secret = api_secret
self.debug = debug
self.endpoint = self._get_endpoint()
def _get_endpoint(self) -> str:
"""Get appropriate endpoint (production or debug)"""
base = "debug/mp" if self.debug else "mp"
return f"https://www.google-analytics.com/{base}/collect?measurement_id={self.measurement_id}&api_secret={self.api_secret}"
def send_event(
self,
client_id: str,
event_name: str,
event_params: Optional[Dict] = None,
user_id: Optional[str] = None,
user_properties: Optional[Dict] = None,
timestamp_micros: Optional[int] = None,
consent: Optional[Dict] = None
) -> bool:
"""
Send single event to GA4
Args:
client_id: Unique client identifier (UUID)
event_name: Event name (lowercase, underscores, ≤40 chars)
event_params: Event parameters dictionary
user_id: Optional user ID for cross-device tracking
user_properties: Optional user properties
timestamp_micros: Optional event timestamp in microseconds
consent: Optional consent status
Returns:
bool: True if successful, False otherwise
"""
payload = self._build_payload(
client_id=client_id,
events=[{
"name": event_name,
"params": event_params or {}
}],
user_id=user_id,
user_properties=user_properties,
timestamp_micros=timestamp_micros,
consent=consent
)
return self._send_request(payload)
def send_batch(
self,
client_id: str,
events: List[Dict],
user_id: Optional[str] = None,
user_properties: Optional[Dict] = None,
consent: Optional[Dict] = None
) -> bool:
"""
Send batch of events (max 25)
Args:
client_id: Unique client identifier
events: List of event dictionaries with 'name' and 'params'
user_id: Optional user ID
user_properties: Optional user properties
consent: Optional consent status
Returns:
bool: True if successful, False otherwise
"""
if len(events) > 25:
raise ValueError("Maximum 25 events per batch")
payload = self._build_payload(
client_id=client_id,
events=events,
user_id=user_id,
user_properties=user_properties,
consent=consent
)
return self._send_request(payload)
def _build_payload(
self,
client_id: str,
events: List[Dict],
user_id: Optional[str] = None,
user_properties: Optional[Dict] = None,
timestamp_micros: Optional[int] = None,
consent: Optional[Dict] = None
) -> Dict:
"""Build request payload"""
payload = {
"client_id": client_id,
"events": events
}
if user_id:
payload["user_id"] = user_id
if user_properties:
# Format user properties
formatted_props = {
key: {"value": value}
for key, value in user_properties.items()
}
payload["user_properties"] = formatted_props
if timestamp_micros:
payload["timestamp_micros"] = str(timestamp_micros)
if consent:
payload["consent"] = consent
return payload
def _send_request(self, payload: Dict, max_retries: int = 3) -> bool:
"""
Send request with retry logic
Args:
payload: Request payload
max_retries: Maximum retry attempts
Returns:
bool: True if successful
"""
for attempt in range(max_retries):
try:
response = requests.post(
self.endpoint,
headers={"Content-Type": "application/json"},
data=json.dumps(payload),
timeout=10
)
if response.status_code == 204:
return True
if response.status_code == 200 and self.debug:
# Debug endpoint returns validation messages
validation = response.json()
if not validation.get("validationMessages"):
print("✓ Validation passed")
return True
else:
print("✗ Validation errors:")
for msg in validation["validationMessages"]:
print(f" - {msg['fieldPath']}: {msg['description']}")
return False
if response.status_code == 429:
# Rate limited - exponential backoff
wait_time = (2 ** attempt) + (time.time() % 1)
print(f"Rate limited. Waiting {wait_time:.2f}s...")
time.sleep(wait_time)
continue
print(f"Error: HTTP {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
# ============================================================================
# USAGE EXAMPLES
# ============================================================================
def example_page_view():
"""Example: Track page view"""
ga = GA4MeasurementProtocol(
measurement_id="G-XXXXXXXXXX",
api_secret="your_api_secret",
debug=True # Use debug endpoint for testing
)
success = ga.send_event(
client_id=str(uuid.uuid4()),
event_name="page_view",
event_params={
"page_location": "https://example.com/products",
"page_title": "Products Page",
"page_referrer": "https://google.com"
}
)
print(f"Page view sent: {success}")
def example_purchase():
"""Example: Track purchase"""
ga = GA4MeasurementProtocol(
measurement_id="G-XXXXXXXXXX",
api_secret="your_api_secret"
)
success = ga.send_event(
client_id="client_12345",
event_name="purchase",
event_params={
"transaction_id": f"T_{int(time.time())}",
"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"
}]
}
)
print(f"Purchase sent: {success}")
def example_user_signup():
"""Example: Track user signup with user properties"""
ga = GA4MeasurementProtocol(
measurement_id="G-XXXXXXXXXX",
api_secret="your_api_secret"
)
success = ga.send_event(
client_id=str(uuid.uuid4()),
user_id="user_456",
event_name="sign_up",
event_params={
"method": "email"
},
user_properties={
"user_tier": "free",
"signup_date": datetime.now().strftime("%Y-%m-%d")
}
)
print(f"Sign up sent: {success}")
def example_batch_events():
"""Example: Send multiple events in batch"""
ga = GA4MeasurementProtocol(
measurement_id="G-XXXXXXXXXX",
api_secret="your_api_secret"
)
events = [
{
"name": "view_item",
"params": {
"currency": "USD",
"value": 29.99,
"items": [{
"item_id": "SKU_123",
"item_name": "Product",
"price": 29.99
}]
}
},
{
"name": "add_to_cart",
"params": {
"currency": "USD",
"value": 29.99,
"items": [{
"item_id": "SKU_123",
"item_name": "Product",
"price": 29.99,
"quantity": 1
}]
}
},
{
"name": "begin_checkout",
"params": {
"currency": "USD",
"value": 34.99,
"items": [{
"item_id": "SKU_123",
"item_name": "Product",
"price": 29.99,
"quantity": 1
}]
}
}
]
success = ga.send_batch(
client_id="client_789",
events=events
)
print(f"Batch sent: {success}")
def example_with_consent():
"""Example: Send event with consent parameters"""
ga = GA4MeasurementProtocol(
measurement_id="G-XXXXXXXXXX",
api_secret="your_api_secret"
)
success = ga.send_event(
client_id=str(uuid.uuid4()),
event_name="page_view",
event_params={
"page_location": "https://example.com"
},
consent={
"ad_storage": "denied",
"analytics_storage": "granted",
"ad_user_data": "denied",
"ad_personalization": "denied"
}
)
print(f"Event with consent sent: {success}")
if __name__ == "__main__":
# Run examples
print("=== GA4 Measurement Protocol Examples ===\n")
print("1. Page View:")
example_page_view()
print("\n2. Purchase:")
example_purchase()
print("\n3. User Signup:")
example_user_signup()
print("\n4. Batch Events:")
example_batch_events()
print("\n5. With Consent:")
example_with_consent()

View File

@@ -0,0 +1,479 @@
# 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 Admin****Data 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
```json
{
"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:**
```python
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:**
```python
import time
timestamp_micros = str(int(time.time() * 1_000_000))
```
### user_properties (Optional)
**Type:** Object
**Purpose:** Set user-level properties
**Format:**
```json
{
"property_name": {
"value": "property_value"
}
}
```
**Limits:**
- Max 25 user properties per request
- Property name ≤24 characters
- Value ≤36 characters
**Example:**
```json
{
"user_tier": {"value": "premium"},
"signup_date": {"value": "2024-01-15"},
"total_purchases": {"value": 12}
}
```
### consent (Optional)
**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:**
```json
{
"ad_storage": "denied",
"analytics_storage": "granted"
}
```
### events (Required)
**Type:** Array
**Limits:** Max 25 events per request
**Event Structure:**
```json
{
"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
```json
{
"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
```json
{
"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
```json
{
"client_id": "client_123",
"events": [{
"name": "generate_lead",
"params": {
"currency": "USD",
"value": 50.00,
"form_name": "contact_form",
"lead_source": "website"
}
}]
}
```
### Sign Up
```json
{
"client_id": "client_123",
"user_id": "user_456",
"events": [{
"name": "sign_up",
"params": {
"method": "email"
}
}]
}
```
### Custom Event
```json
{
"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
```bash
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):**
```json
{
"validationMessages": []
}
```
**Empty array = No validation errors**
### Invalid Response
**Body (Invalid):**
```json
{
"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
```python
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
```javascript
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