@friedbotstudio/create-baseline 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/obj/template/.claude/commands/grant-push.md +19 -0
- package/obj/template/.claude/commands/init-project.md +26 -4
- package/obj/template/.claude/hooks/consent_gate_grant.mjs +107 -0
- package/obj/template/.claude/hooks/git_commit_guard.mjs +224 -0
- package/obj/template/.claude/hooks/harness_continuation.sh +101 -34
- package/obj/template/.claude/hooks/lib/common.mjs +283 -0
- package/obj/template/.claude/hooks/lib/common.sh +1 -1
- package/obj/template/.claude/hooks/memory_session_start.sh +20 -6
- package/obj/template/.claude/hooks/memory_stop.sh +161 -2
- package/obj/template/.claude/hooks/spec_approval_guard.sh +1 -1
- package/obj/template/.claude/hooks/swarm_approval_guard.sh +1 -1
- package/obj/template/.claude/hooks/tests/fixtures/ac008_byte_equal_reference.txt +7 -7
- package/obj/template/.claude/hooks/tests/fixtures/memory_stop_landmark_baseline.txt +21 -0
- package/obj/template/.claude/hooks/tests/fixtures/regenerate-ac008.sh +47 -0
- package/obj/template/.claude/hooks/tests/memory_session_start_test.sh +7 -3
- package/obj/template/.claude/hooks/tests/memory_stop_intent_test.sh +329 -0
- package/obj/template/.claude/hooks/tests/regenerate_ac008_test.sh +99 -0
- package/obj/template/.claude/memory/README.md +8 -3
- package/obj/template/.claude/memory/backlog.md +12 -0
- package/obj/template/.claude/project.json +6 -1
- package/obj/template/.claude/settings.json +3 -4
- package/obj/template/.claude/skills/audit-baseline/audit.sh +39 -21
- package/obj/template/.claude/skills/audit-baseline/tests/fixtures/_pending_opener_only.md +3 -0
- package/obj/template/.claude/skills/audit-baseline/tests/fixtures/preamble_full_empty_body.md +4 -0
- package/obj/template/.claude/skills/audit-baseline/tests/fixtures/preamble_full_with_entries.md +9 -0
- package/obj/template/.claude/skills/audit-baseline/tests/fixtures/preamble_no_opener.md +3 -0
- package/obj/template/.claude/skills/audit-baseline/tests/fixtures/preamble_opener_only.md +3 -0
- package/obj/template/.claude/skills/audit-baseline/tests/preamble_check_test.sh +147 -0
- package/obj/template/.claude/skills/chore/SKILL.md +5 -3
- package/obj/template/.claude/skills/commit/SKILL.md +5 -4
- package/obj/template/.claude/skills/copywriting/LICENSE +21 -0
- package/obj/template/.claude/skills/copywriting/NOTICE +23 -0
- package/obj/template/.claude/skills/copywriting/SKILL.md +1 -1
- package/obj/template/.claude/skills/design-ui/SKILL.md +23 -5
- package/obj/template/.claude/skills/design-ui/references/design-vs-development.md +26 -5
- package/obj/template/.claude/skills/design-ui/references/orchestration.md +1 -0
- package/obj/template/.claude/skills/design-ui/references/state-machine.md +5 -3
- package/obj/template/.claude/skills/documentation/LICENSE +202 -0
- package/obj/template/.claude/skills/documentation/NOTICE +22 -0
- package/obj/template/.claude/skills/google-analytics/SKILL.md +129 -0
- package/obj/template/.claude/skills/google-analytics/references/audiences.md +389 -0
- package/obj/template/.claude/skills/google-analytics/references/bigquery.md +470 -0
- package/obj/template/.claude/skills/google-analytics/references/custom-dimensions.md +355 -0
- package/obj/template/.claude/skills/google-analytics/references/custom-events.md +383 -0
- package/obj/template/.claude/skills/google-analytics/references/data-management.md +416 -0
- package/obj/template/.claude/skills/google-analytics/references/debugview.md +364 -0
- package/obj/template/.claude/skills/google-analytics/references/events-fundamentals.md +398 -0
- package/obj/template/.claude/skills/google-analytics/references/gtag.md +502 -0
- package/obj/template/.claude/skills/google-analytics/references/gtm-integration.md +483 -0
- package/obj/template/.claude/skills/google-analytics/references/measurement-protocol.md +519 -0
- package/obj/template/.claude/skills/google-analytics/references/privacy.md +441 -0
- package/obj/template/.claude/skills/google-analytics/references/recommended-events.md +464 -0
- package/obj/template/.claude/skills/google-analytics/references/reporting.md +397 -0
- package/obj/template/.claude/skills/google-analytics/references/setup.md +344 -0
- package/obj/template/.claude/skills/google-analytics/references/user-tracking.md +417 -0
- package/obj/template/.claude/skills/harness/SKILL.md +3 -1
- package/obj/template/.claude/skills/humanizer/LICENSE +21 -0
- package/obj/template/.claude/skills/humanizer/NOTICE +21 -0
- package/obj/template/.claude/skills/impeccable/LICENSE +202 -0
- package/obj/template/.claude/skills/impeccable/NOTICE +24 -0
- package/obj/template/.claude/skills/memory-flush/SKILL.md +20 -4
- package/obj/template/.claude/skills/memory-flush/sweep.py +74 -6
- package/obj/template/.claude/skills/memory-flush/tests/run.sh +300 -1
- package/obj/template/.claude/skills/optimize-seo/SKILL.md +313 -0
- package/obj/template/.claude/skills/optimize-seo/scripts/pagespeed.mjs +197 -0
- package/obj/template/.claude/skills/pagespeed-insights/LICENSE.md +37 -0
- package/obj/template/.claude/skills/pagespeed-insights/SKILL.md +446 -0
- package/obj/template/.claude/skills/pagespeed-insights/reference.md +50 -0
- package/obj/template/.claude/skills/tdd/SKILL.md +2 -1
- package/obj/template/.claude/skills/tdd/drift_check.py +180 -0
- package/obj/template/.claude/skills/tdd/tests/drift_check_test.sh +190 -0
- package/obj/template/.claude/skills/tdd/tests/run.sh +21 -0
- package/obj/template/.claude/skills/technical-tutorials/LICENSE +21 -0
- package/obj/template/.claude/skills/technical-tutorials/NOTICE +23 -0
- package/obj/template/.claude/skills/technical-tutorials/SKILL.md +1 -1
- package/obj/template/.claude/skills/triage/SKILL.md +8 -3
- package/obj/template/CLAUDE.md +37 -26
- package/obj/template/docs/init/seed.md +38 -23
- package/obj/template/manifest.json +80 -33
- package/package.json +1 -1
- package/src/CLAUDE.template.md +37 -26
- package/src/memory/backlog.template.md +12 -0
- package/src/project.template.json +6 -1
- package/src/seed.template.md +38 -23
- package/src/settings.template.json +3 -4
- package/obj/template/.claude/hooks/consent_gate_grant.sh +0 -89
- package/obj/template/.claude/hooks/git_commit_guard.sh +0 -93
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
# GA4 Measurement Protocol
|
|
2
|
+
|
|
3
|
+
Complete guide to GA4 Measurement Protocol for server-side event tracking.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The GA4 Measurement Protocol allows server-side event collection, enabling data transmission to GA4 from any HTTP-capable environment including backend servers, mobile app backends, kiosks, and IoT devices.
|
|
8
|
+
|
|
9
|
+
## When to Use Measurement Protocol
|
|
10
|
+
|
|
11
|
+
| Use Case | Description |
|
|
12
|
+
|----------|-------------|
|
|
13
|
+
| Server-side tracking | Events from backend systems |
|
|
14
|
+
| Offline conversions | Import CRM/POS data |
|
|
15
|
+
| Payment processing | Track post-checkout events |
|
|
16
|
+
| Subscription renewals | Server-initiated events |
|
|
17
|
+
| Lead enrichment | Enhance leads from backend |
|
|
18
|
+
| IoT devices | Non-browser environments |
|
|
19
|
+
| Headless architectures | API-first systems |
|
|
20
|
+
|
|
21
|
+
## API Endpoints
|
|
22
|
+
|
|
23
|
+
### Production Endpoint
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
POST https://www.google-analytics.com/mp/collect
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Debug Endpoint
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
POST https://www.google-analytics.com/debug/mp/collect
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Key Difference:** Debug returns validation messages without storing data.
|
|
36
|
+
|
|
37
|
+
## Authentication
|
|
38
|
+
|
|
39
|
+
### Required Credentials
|
|
40
|
+
|
|
41
|
+
1. **Measurement ID** (G-XXXXXXXXXX)
|
|
42
|
+
- Location: GA4 Admin -> Data Streams -> Web Stream
|
|
43
|
+
|
|
44
|
+
2. **API Secret**
|
|
45
|
+
- Location: Data Streams -> Measurement Protocol API secrets
|
|
46
|
+
|
|
47
|
+
### Generating API Secret
|
|
48
|
+
|
|
49
|
+
1. GA4 Admin -> Data Streams
|
|
50
|
+
2. Click your data stream
|
|
51
|
+
3. Scroll to "Measurement Protocol API secrets"
|
|
52
|
+
4. Click "Create"
|
|
53
|
+
5. Enter nickname (e.g., "Server-side tracking")
|
|
54
|
+
6. Click "Create"
|
|
55
|
+
7. **Copy secret immediately** (shown once only)
|
|
56
|
+
8. Store securely
|
|
57
|
+
|
|
58
|
+
## Request Structure
|
|
59
|
+
|
|
60
|
+
### URL Format
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
https://www.google-analytics.com/mp/collect?measurement_id={MEASUREMENT_ID}&api_secret={API_SECRET}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Headers
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
Content-Type: application/json
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Request Body (JSON)
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"client_id": "unique_client_identifier",
|
|
77
|
+
"user_id": "optional_user_id",
|
|
78
|
+
"timestamp_micros": "1234567890123456",
|
|
79
|
+
"user_properties": {
|
|
80
|
+
"property_name": {
|
|
81
|
+
"value": "property_value"
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"consent": {
|
|
85
|
+
"ad_storage": "granted",
|
|
86
|
+
"analytics_storage": "granted"
|
|
87
|
+
},
|
|
88
|
+
"events": [
|
|
89
|
+
{
|
|
90
|
+
"name": "event_name",
|
|
91
|
+
"params": {
|
|
92
|
+
"parameter_name": "parameter_value",
|
|
93
|
+
"value": 123.45,
|
|
94
|
+
"currency": "USD"
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Required and Optional Fields
|
|
102
|
+
|
|
103
|
+
### Required Fields
|
|
104
|
+
|
|
105
|
+
| Field | Type | Description |
|
|
106
|
+
|-------|------|-------------|
|
|
107
|
+
| client_id | string | Unique client identifier (UUID recommended) |
|
|
108
|
+
| events | array | Array of event objects (max 25) |
|
|
109
|
+
| events[].name | string | Event name (max 40 chars) |
|
|
110
|
+
|
|
111
|
+
### Optional Fields
|
|
112
|
+
|
|
113
|
+
| Field | Type | Description |
|
|
114
|
+
|-------|------|-------------|
|
|
115
|
+
| user_id | string | User ID for cross-device |
|
|
116
|
+
| timestamp_micros | integer | Event timestamp (microseconds) |
|
|
117
|
+
| user_properties | object | User-level properties |
|
|
118
|
+
| consent | object | Consent status |
|
|
119
|
+
| non_personalized_ads | boolean | Disable ad personalisation |
|
|
120
|
+
|
|
121
|
+
## Common Event Parameters
|
|
122
|
+
|
|
123
|
+
| Parameter | Type | Description |
|
|
124
|
+
|-----------|------|-------------|
|
|
125
|
+
| session_id | string | Session identifier |
|
|
126
|
+
| engagement_time_msec | integer | Engagement time (ms) |
|
|
127
|
+
| page_location | string | Full URL |
|
|
128
|
+
| page_title | string | Page title |
|
|
129
|
+
| value | number | Monetary value |
|
|
130
|
+
| currency | string | ISO 4217 code |
|
|
131
|
+
| transaction_id | string | Unique transaction ID |
|
|
132
|
+
| items | array | E-commerce items |
|
|
133
|
+
|
|
134
|
+
## Implementation Examples
|
|
135
|
+
|
|
136
|
+
### Python Implementation
|
|
137
|
+
|
|
138
|
+
**Using Requests Library:**
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
import requests
|
|
142
|
+
import json
|
|
143
|
+
import uuid
|
|
144
|
+
|
|
145
|
+
MEASUREMENT_ID = "G-XXXXXXXXXX"
|
|
146
|
+
API_SECRET = "your_api_secret"
|
|
147
|
+
ENDPOINT = f"https://www.google-analytics.com/mp/collect?measurement_id={MEASUREMENT_ID}&api_secret={API_SECRET}"
|
|
148
|
+
|
|
149
|
+
def send_event(client_id, event_name, params=None):
|
|
150
|
+
payload = {
|
|
151
|
+
"client_id": client_id,
|
|
152
|
+
"events": [{
|
|
153
|
+
"name": event_name,
|
|
154
|
+
"params": params or {}
|
|
155
|
+
}]
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
response = requests.post(
|
|
159
|
+
ENDPOINT,
|
|
160
|
+
headers={"Content-Type": "application/json"},
|
|
161
|
+
data=json.dumps(payload)
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
return response.status_code == 204
|
|
165
|
+
|
|
166
|
+
# Send page view
|
|
167
|
+
send_event(
|
|
168
|
+
client_id="user_123.session_456",
|
|
169
|
+
event_name="page_view",
|
|
170
|
+
params={
|
|
171
|
+
"page_location": "https://example.com/page",
|
|
172
|
+
"page_title": "Example Page"
|
|
173
|
+
}
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Send purchase
|
|
177
|
+
send_event(
|
|
178
|
+
client_id="user_123.session_456",
|
|
179
|
+
event_name="purchase",
|
|
180
|
+
params={
|
|
181
|
+
"transaction_id": "T_12345",
|
|
182
|
+
"value": 99.99,
|
|
183
|
+
"currency": "USD",
|
|
184
|
+
"items": [{
|
|
185
|
+
"item_id": "SKU_123",
|
|
186
|
+
"item_name": "Product Name",
|
|
187
|
+
"price": 99.99,
|
|
188
|
+
"quantity": 1
|
|
189
|
+
}]
|
|
190
|
+
}
|
|
191
|
+
)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**Using ga4mp Library:**
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
# Install: pip install ga4mp
|
|
198
|
+
from ga4mp import GtagMP
|
|
199
|
+
|
|
200
|
+
ga = GtagMP(
|
|
201
|
+
measurement_id="G-XXXXXXXXXX",
|
|
202
|
+
api_secret="your_api_secret",
|
|
203
|
+
client_id="unique_client_id"
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# Send event
|
|
207
|
+
ga.send_event(
|
|
208
|
+
event_name="purchase",
|
|
209
|
+
event_parameters={
|
|
210
|
+
"transaction_id": "T_12345",
|
|
211
|
+
"value": 99.99,
|
|
212
|
+
"currency": "USD",
|
|
213
|
+
"items": [{
|
|
214
|
+
"item_id": "SKU_123",
|
|
215
|
+
"item_name": "Product Name",
|
|
216
|
+
"price": 99.99,
|
|
217
|
+
"quantity": 1
|
|
218
|
+
}]
|
|
219
|
+
}
|
|
220
|
+
)
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Node.js Implementation
|
|
224
|
+
|
|
225
|
+
```javascript
|
|
226
|
+
const axios = require('axios');
|
|
227
|
+
const { v4: uuidv4 } = require('uuid');
|
|
228
|
+
|
|
229
|
+
const MEASUREMENT_ID = 'G-XXXXXXXXXX';
|
|
230
|
+
const API_SECRET = 'your_api_secret';
|
|
231
|
+
const ENDPOINT = `https://www.google-analytics.com/mp/collect?measurement_id=${MEASUREMENT_ID}&api_secret=${API_SECRET}`;
|
|
232
|
+
|
|
233
|
+
async function sendEvent(clientId, eventName, params = {}) {
|
|
234
|
+
const payload = {
|
|
235
|
+
client_id: clientId,
|
|
236
|
+
events: [{
|
|
237
|
+
name: eventName,
|
|
238
|
+
params: params
|
|
239
|
+
}]
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
const response = await axios.post(ENDPOINT, payload, {
|
|
244
|
+
headers: { 'Content-Type': 'application/json' }
|
|
245
|
+
});
|
|
246
|
+
return response.status === 204;
|
|
247
|
+
} catch (error) {
|
|
248
|
+
console.error('Error sending event:', error);
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Send purchase event
|
|
254
|
+
sendEvent('client_123', 'purchase', {
|
|
255
|
+
transaction_id: 'T_12345',
|
|
256
|
+
value: 99.99,
|
|
257
|
+
currency: 'USD',
|
|
258
|
+
items: [{
|
|
259
|
+
item_id: 'SKU_123',
|
|
260
|
+
item_name: 'Product',
|
|
261
|
+
price: 99.99,
|
|
262
|
+
quantity: 1
|
|
263
|
+
}]
|
|
264
|
+
});
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### PHP Implementation
|
|
268
|
+
|
|
269
|
+
```php
|
|
270
|
+
<?php
|
|
271
|
+
// Using php-GA4-Measurement-Protocol library
|
|
272
|
+
// Install: composer require br33f/php-ga4-measurement-protocol
|
|
273
|
+
|
|
274
|
+
use Br33f\Ga4\MeasurementProtocol\Dto\Event\PurchaseEvent;
|
|
275
|
+
use Br33f\Ga4\MeasurementProtocol\Dto\Request\MeasurementRequest;
|
|
276
|
+
use Br33f\Ga4\MeasurementProtocol\Service;
|
|
277
|
+
|
|
278
|
+
$measurementId = 'G-XXXXXXXXXX';
|
|
279
|
+
$apiSecret = 'your_api_secret';
|
|
280
|
+
|
|
281
|
+
$service = new Service($apiSecret, $measurementId);
|
|
282
|
+
|
|
283
|
+
$event = new PurchaseEvent();
|
|
284
|
+
$event->setTransactionId('T_12345')
|
|
285
|
+
->setValue(99.99)
|
|
286
|
+
->setCurrency('USD');
|
|
287
|
+
|
|
288
|
+
$request = new MeasurementRequest();
|
|
289
|
+
$request->setClientId('unique_client_id')
|
|
290
|
+
->addEvent($event);
|
|
291
|
+
|
|
292
|
+
$service->send($request);
|
|
293
|
+
?>
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### cURL Example
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
curl -X POST "https://www.google-analytics.com/mp/collect?measurement_id=G-XXXXXXXXXX&api_secret=YOUR_SECRET" \
|
|
300
|
+
-H "Content-Type: application/json" \
|
|
301
|
+
-d '{
|
|
302
|
+
"client_id": "client_123",
|
|
303
|
+
"events": [{
|
|
304
|
+
"name": "purchase",
|
|
305
|
+
"params": {
|
|
306
|
+
"transaction_id": "T_12345",
|
|
307
|
+
"value": 99.99,
|
|
308
|
+
"currency": "USD"
|
|
309
|
+
}
|
|
310
|
+
}]
|
|
311
|
+
}'
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## Validation with Debug Endpoint
|
|
315
|
+
|
|
316
|
+
### Send to Debug Endpoint
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
curl -X POST "https://www.google-analytics.com/debug/mp/collect?measurement_id=G-XXXXXXXXXX&api_secret=YOUR_SECRET" \
|
|
320
|
+
-H "Content-Type: application/json" \
|
|
321
|
+
-d '{
|
|
322
|
+
"client_id": "test_client",
|
|
323
|
+
"events": [{
|
|
324
|
+
"name": "test_event",
|
|
325
|
+
"params": {
|
|
326
|
+
"test_param": "test_value"
|
|
327
|
+
}
|
|
328
|
+
}]
|
|
329
|
+
}'
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Response Format
|
|
333
|
+
|
|
334
|
+
```json
|
|
335
|
+
{
|
|
336
|
+
"validationMessages": [
|
|
337
|
+
{
|
|
338
|
+
"fieldPath": "events[0].name",
|
|
339
|
+
"description": "Event name must be 40 characters or fewer",
|
|
340
|
+
"validationCode": "NAME_INVALID"
|
|
341
|
+
}
|
|
342
|
+
]
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Empty Response = Valid
|
|
347
|
+
|
|
348
|
+
- No validationMessages = payload valid
|
|
349
|
+
- Status 200 = request processed
|
|
350
|
+
- Production returns 204 (no content)
|
|
351
|
+
|
|
352
|
+
## Validation Codes
|
|
353
|
+
|
|
354
|
+
| Code | Description | Fix |
|
|
355
|
+
|------|-------------|-----|
|
|
356
|
+
| NAME_INVALID | Invalid event/parameter name | snake_case, max 40 chars |
|
|
357
|
+
| NAME_RESERVED | Reserved name used | Check reserved names |
|
|
358
|
+
| VALUE_INVALID | Invalid parameter value | Check data type |
|
|
359
|
+
| VALUE_REQUIRED | Required value missing | Add required parameter |
|
|
360
|
+
| VALUE_OUT_OF_BOUNDS | Value exceeds limits | Check numeric ranges |
|
|
361
|
+
| EXCEEDED_MAX_ENTITIES | Too many events | Max 25 per request |
|
|
362
|
+
|
|
363
|
+
## Best Practices
|
|
364
|
+
|
|
365
|
+
### 1. Always Validate First
|
|
366
|
+
|
|
367
|
+
```python
|
|
368
|
+
DEBUG_ENDPOINT = f"https://www.google-analytics.com/debug/mp/collect?measurement_id={MEASUREMENT_ID}&api_secret={API_SECRET}"
|
|
369
|
+
|
|
370
|
+
def validate_event(payload):
|
|
371
|
+
response = requests.post(
|
|
372
|
+
DEBUG_ENDPOINT,
|
|
373
|
+
headers={"Content-Type": "application/json"},
|
|
374
|
+
data=json.dumps(payload)
|
|
375
|
+
)
|
|
376
|
+
return response.json()
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### 2. Use Consistent client_id
|
|
380
|
+
|
|
381
|
+
- Same user = same client_id across sessions
|
|
382
|
+
- Store in database for logged-in users
|
|
383
|
+
- Use UUID format for anonymity
|
|
384
|
+
|
|
385
|
+
### 3. Include session_id
|
|
386
|
+
|
|
387
|
+
- Maintain session continuity
|
|
388
|
+
- Generate unique session ID
|
|
389
|
+
- Keep consistent within session
|
|
390
|
+
|
|
391
|
+
### 4. Batch Events
|
|
392
|
+
|
|
393
|
+
```python
|
|
394
|
+
payload = {
|
|
395
|
+
"client_id": "client_123",
|
|
396
|
+
"events": [
|
|
397
|
+
{"name": "event_1", "params": {}},
|
|
398
|
+
{"name": "event_2", "params": {}},
|
|
399
|
+
{"name": "event_3", "params": {}}
|
|
400
|
+
] # Up to 25 events per request
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### 5. Handle Errors Gracefully
|
|
405
|
+
|
|
406
|
+
```python
|
|
407
|
+
def send_with_retry(payload, max_retries=3):
|
|
408
|
+
for attempt in range(max_retries):
|
|
409
|
+
try:
|
|
410
|
+
response = requests.post(ENDPOINT, json=payload)
|
|
411
|
+
if response.status_code == 204:
|
|
412
|
+
return True
|
|
413
|
+
except Exception as e:
|
|
414
|
+
if attempt < max_retries - 1:
|
|
415
|
+
time.sleep(2 ** attempt) # Exponential backoff
|
|
416
|
+
else:
|
|
417
|
+
log_error(e, payload)
|
|
418
|
+
return False
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### 6. Set Proper Timestamps
|
|
422
|
+
|
|
423
|
+
```python
|
|
424
|
+
import time
|
|
425
|
+
|
|
426
|
+
# For historical data (max 3 days past)
|
|
427
|
+
timestamp_micros = int(time.time() * 1_000_000)
|
|
428
|
+
|
|
429
|
+
payload = {
|
|
430
|
+
"client_id": "client_123",
|
|
431
|
+
"timestamp_micros": str(timestamp_micros),
|
|
432
|
+
"events": [...]
|
|
433
|
+
}
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
### 7. Respect Consent
|
|
437
|
+
|
|
438
|
+
```python
|
|
439
|
+
payload = {
|
|
440
|
+
"client_id": "client_123",
|
|
441
|
+
"consent": {
|
|
442
|
+
"ad_storage": "denied",
|
|
443
|
+
"analytics_storage": "granted"
|
|
444
|
+
},
|
|
445
|
+
"events": [...]
|
|
446
|
+
}
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
## Limits
|
|
450
|
+
|
|
451
|
+
| Limit | Value |
|
|
452
|
+
|-------|-------|
|
|
453
|
+
| Events per request | 25 |
|
|
454
|
+
| Event name length | 40 characters |
|
|
455
|
+
| Parameter name length | 40 characters |
|
|
456
|
+
| Parameter value length | 100 characters |
|
|
457
|
+
| Parameters per event | 25 |
|
|
458
|
+
| User properties per request | 25 |
|
|
459
|
+
| Timestamp range | 3 days past, 72 hours future |
|
|
460
|
+
|
|
461
|
+
## Common Issues
|
|
462
|
+
|
|
463
|
+
### Events Not Appearing
|
|
464
|
+
|
|
465
|
+
**Causes:**
|
|
466
|
+
- Wrong Measurement ID
|
|
467
|
+
- Invalid API secret
|
|
468
|
+
- Validation errors
|
|
469
|
+
- Consent blocking
|
|
470
|
+
|
|
471
|
+
**Solutions:**
|
|
472
|
+
1. Validate with debug endpoint
|
|
473
|
+
2. Verify credentials
|
|
474
|
+
3. Check consent parameters
|
|
475
|
+
|
|
476
|
+
### Duplicate Events
|
|
477
|
+
|
|
478
|
+
**Causes:**
|
|
479
|
+
- Same request sent multiple times
|
|
480
|
+
- Missing deduplication logic
|
|
481
|
+
|
|
482
|
+
**Solutions:**
|
|
483
|
+
1. Implement idempotency
|
|
484
|
+
2. Track sent events
|
|
485
|
+
3. Use unique transaction IDs
|
|
486
|
+
|
|
487
|
+
### Missing User Association
|
|
488
|
+
|
|
489
|
+
**Cause:** Different client_id values
|
|
490
|
+
|
|
491
|
+
**Solution:** Store and reuse client_id from frontend
|
|
492
|
+
|
|
493
|
+
## Quick Reference
|
|
494
|
+
|
|
495
|
+
### Endpoint
|
|
496
|
+
|
|
497
|
+
```
|
|
498
|
+
Production: /mp/collect
|
|
499
|
+
Debug: /debug/mp/collect
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### Required Fields
|
|
503
|
+
|
|
504
|
+
- client_id (UUID recommended)
|
|
505
|
+
- events array
|
|
506
|
+
- event name
|
|
507
|
+
|
|
508
|
+
### Max Limits
|
|
509
|
+
|
|
510
|
+
- 25 events per request
|
|
511
|
+
- 40 characters per event name
|
|
512
|
+
- 25 parameters per event
|
|
513
|
+
- 25 user properties per request
|
|
514
|
+
|
|
515
|
+
### Validation
|
|
516
|
+
|
|
517
|
+
- Send to debug endpoint
|
|
518
|
+
- Empty response = valid
|
|
519
|
+
- Check validationMessages array
|