@walkeros/server-destination-datamanager 0.3.1
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 +666 -0
- package/dist/examples/index.d.mts +153 -0
- package/dist/examples/index.d.ts +153 -0
- package/dist/examples/index.js +235 -0
- package/dist/examples/index.mjs +213 -0
- package/dist/index.d.mts +372 -0
- package/dist/index.d.ts +372 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1 -0
- package/dist/index.mjs.map +1 -0
- package/dist/schemas.d.mts +5 -0
- package/dist/schemas.d.ts +5 -0
- package/dist/schemas.js +1 -0
- package/dist/schemas.js.map +1 -0
- package/dist/schemas.mjs +1 -0
- package/dist/schemas.mjs.map +1 -0
- package/package.json +65 -0
package/README.md
ADDED
|
@@ -0,0 +1,666 @@
|
|
|
1
|
+
# @walkeros/server-destination-datamanager
|
|
2
|
+
|
|
3
|
+
Google Data Manager server destination for walkerOS - send conversion events and
|
|
4
|
+
audience data to Google Ads, Display & Video 360, and Google Analytics 4 through
|
|
5
|
+
a single unified API.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Multi-platform reach**: Single API call sends data to Google Ads, DV360, and
|
|
10
|
+
GA4
|
|
11
|
+
- **Privacy-first**: SHA-256 hashing of PII with Gmail-specific email
|
|
12
|
+
normalization
|
|
13
|
+
- **DMA compliance**: Built-in consent management for EEA/UK/Switzerland
|
|
14
|
+
- **Explicit mapping**: Transform walkerOS events to Data Manager format using
|
|
15
|
+
declarative mapping rules
|
|
16
|
+
- **Type-safe**: Full TypeScript support with comprehensive type definitions
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @walkeros/server-destination-datamanager
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
### Minimal Configuration
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { destinationDataManager } from '@walkeros/server-destination-datamanager';
|
|
30
|
+
import { startFlow } from '@walkeros/collector';
|
|
31
|
+
|
|
32
|
+
const { collector, elb } = await startFlow({
|
|
33
|
+
destinations: {
|
|
34
|
+
datamanager: {
|
|
35
|
+
...destinationDataManager,
|
|
36
|
+
config: {
|
|
37
|
+
settings: {
|
|
38
|
+
// OAuth 2.0 access token with datamanager scope
|
|
39
|
+
accessToken: 'ya29.c.xxx',
|
|
40
|
+
|
|
41
|
+
// Destination accounts
|
|
42
|
+
destinations: [
|
|
43
|
+
{
|
|
44
|
+
operatingAccount: {
|
|
45
|
+
accountId: '123-456-7890',
|
|
46
|
+
accountType: 'GOOGLE_ADS',
|
|
47
|
+
},
|
|
48
|
+
productDestinationId: 'AW-CONVERSION-123',
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Track a conversion
|
|
58
|
+
await elb('order complete', {
|
|
59
|
+
id: 'ORDER-123',
|
|
60
|
+
total: 99.99,
|
|
61
|
+
currency: 'USD',
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Complete Configuration
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import { destinationDataManager } from '@walkeros/server-destination-datamanager';
|
|
69
|
+
|
|
70
|
+
const config = {
|
|
71
|
+
...destinationDataManager,
|
|
72
|
+
config: {
|
|
73
|
+
settings: {
|
|
74
|
+
accessToken: 'ya29.c.xxx',
|
|
75
|
+
|
|
76
|
+
// Multiple destinations (max 10)
|
|
77
|
+
destinations: [
|
|
78
|
+
{
|
|
79
|
+
operatingAccount: {
|
|
80
|
+
accountId: '123-456-7890',
|
|
81
|
+
accountType: 'GOOGLE_ADS',
|
|
82
|
+
},
|
|
83
|
+
productDestinationId: 'AW-CONVERSION-123',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
operatingAccount: {
|
|
87
|
+
accountId: '987654321',
|
|
88
|
+
accountType: 'GOOGLE_ANALYTICS_PROPERTY',
|
|
89
|
+
},
|
|
90
|
+
productDestinationId: 'G-XXXXXXXXXX',
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
|
|
94
|
+
// Optional settings
|
|
95
|
+
eventSource: 'WEB', // Default event source
|
|
96
|
+
batchSize: 100, // Events per batch (max 2000)
|
|
97
|
+
batchInterval: 5000, // Batch flush interval in ms
|
|
98
|
+
validateOnly: false, // Test mode (validate without ingestion)
|
|
99
|
+
testEventCode: 'TEST12345', // For debugging
|
|
100
|
+
|
|
101
|
+
// Request-level consent
|
|
102
|
+
consent: {
|
|
103
|
+
adUserData: 'CONSENT_GRANTED',
|
|
104
|
+
adPersonalization: 'CONSENT_GRANTED',
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
// Guided helpers (apply to all events)
|
|
108
|
+
userData: {
|
|
109
|
+
email: 'user.id',
|
|
110
|
+
phone: 'data.phone',
|
|
111
|
+
},
|
|
112
|
+
userId: 'user.id',
|
|
113
|
+
clientId: 'user.device',
|
|
114
|
+
sessionAttributes: 'context.sessionAttributes',
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
// Event mapping
|
|
118
|
+
mapping: {
|
|
119
|
+
order: {
|
|
120
|
+
complete: {
|
|
121
|
+
name: 'purchase',
|
|
122
|
+
data: {
|
|
123
|
+
map: {
|
|
124
|
+
transactionId: 'data.id',
|
|
125
|
+
conversionValue: 'data.total',
|
|
126
|
+
currency: { key: 'data.currency', value: 'USD' },
|
|
127
|
+
eventName: { value: 'purchase' }, // For GA4
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Authentication
|
|
138
|
+
|
|
139
|
+
### OAuth 2.0 Setup
|
|
140
|
+
|
|
141
|
+
1. **Create a Google Cloud Project**
|
|
142
|
+
- Go to [Google Cloud Console](https://console.cloud.google.com/)
|
|
143
|
+
- Create a new project or select existing
|
|
144
|
+
|
|
145
|
+
2. **Enable Data Manager API**
|
|
146
|
+
- Navigate to APIs & Services > Library
|
|
147
|
+
- Search for "Google Data Manager API"
|
|
148
|
+
- Click Enable
|
|
149
|
+
|
|
150
|
+
3. **Create Service Account**
|
|
151
|
+
- Go to APIs & Services > Credentials
|
|
152
|
+
- Click "Create Credentials" > "Service Account"
|
|
153
|
+
- Grant necessary permissions
|
|
154
|
+
- Download JSON key file
|
|
155
|
+
|
|
156
|
+
4. **Get Access Token**
|
|
157
|
+
```bash
|
|
158
|
+
gcloud auth application-default print-access-token --scopes=https://www.googleapis.com/auth/datamanager
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Required Scope
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
https://www.googleapis.com/auth/datamanager
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Guided Mapping Helpers
|
|
168
|
+
|
|
169
|
+
Define common fields once in Settings instead of repeating them in every event
|
|
170
|
+
mapping:
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
{
|
|
174
|
+
settings: {
|
|
175
|
+
accessToken: 'ya29.c.xxx',
|
|
176
|
+
destinations: [...],
|
|
177
|
+
|
|
178
|
+
// Guided helpers (apply to all events)
|
|
179
|
+
userData: {
|
|
180
|
+
email: 'user.id',
|
|
181
|
+
phone: 'data.phone',
|
|
182
|
+
firstName: 'data.firstName',
|
|
183
|
+
lastName: 'data.lastName',
|
|
184
|
+
},
|
|
185
|
+
userId: 'user.id',
|
|
186
|
+
clientId: 'user.device',
|
|
187
|
+
sessionAttributes: 'context.sessionAttributes',
|
|
188
|
+
|
|
189
|
+
// Consent mapping (string = field name, boolean = static value)
|
|
190
|
+
consentAdUserData: 'marketing', // Read event.consent.marketing
|
|
191
|
+
consentAdPersonalization: 'personalization', // Read event.consent.personalization
|
|
192
|
+
// OR use static values:
|
|
193
|
+
// consentAdUserData: true, // Always CONSENT_GRANTED
|
|
194
|
+
},
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Precedence**: Settings helpers < config.data < event mapping
|
|
199
|
+
|
|
200
|
+
Event mappings always override Settings:
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
{
|
|
204
|
+
settings: {
|
|
205
|
+
userId: 'user.id', // Default for all events
|
|
206
|
+
},
|
|
207
|
+
mapping: {
|
|
208
|
+
order: {
|
|
209
|
+
complete: {
|
|
210
|
+
data: {
|
|
211
|
+
map: {
|
|
212
|
+
userId: 'data.customerId', // Override for this event
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Data Mapping
|
|
222
|
+
|
|
223
|
+
### Event Data Mapping
|
|
224
|
+
|
|
225
|
+
All event data must be explicitly mapped. The destination does not auto-extract
|
|
226
|
+
fields from events.
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
{
|
|
230
|
+
mapping: {
|
|
231
|
+
order: {
|
|
232
|
+
complete: {
|
|
233
|
+
name: 'purchase',
|
|
234
|
+
data: {
|
|
235
|
+
map: {
|
|
236
|
+
// Transaction data
|
|
237
|
+
transactionId: 'data.id',
|
|
238
|
+
conversionValue: 'data.total',
|
|
239
|
+
currency: { key: 'data.currency', value: 'USD' },
|
|
240
|
+
eventName: { value: 'purchase' },
|
|
241
|
+
|
|
242
|
+
// User identification
|
|
243
|
+
userId: 'user.id',
|
|
244
|
+
email: 'user.id', // Will be SHA-256 hashed
|
|
245
|
+
phone: 'data.phone', // Will be SHA-256 hashed
|
|
246
|
+
|
|
247
|
+
// Attribution identifiers
|
|
248
|
+
gclid: 'context.gclid',
|
|
249
|
+
gbraid: 'context.gbraid',
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Attribution Identifiers
|
|
259
|
+
|
|
260
|
+
Attribution identifiers must be explicitly mapped:
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
{
|
|
264
|
+
mapping: {
|
|
265
|
+
order: {
|
|
266
|
+
complete: {
|
|
267
|
+
data: {
|
|
268
|
+
map: {
|
|
269
|
+
gclid: 'context.gclid', // From URL: ?gclid=TeSter
|
|
270
|
+
gbraid: 'context.gbraid', // iOS attribution
|
|
271
|
+
wbraid: 'context.wbraid', // Web-to-app
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Consent Mapping
|
|
281
|
+
|
|
282
|
+
Map your consent field names to Data Manager's required fields:
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
{
|
|
286
|
+
settings: {
|
|
287
|
+
// Map from your consent field names
|
|
288
|
+
consentAdUserData: 'marketing', // Read event.consent.marketing
|
|
289
|
+
consentAdPersonalization: 'personalization', // Read event.consent.personalization
|
|
290
|
+
},
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Your event with standard consent field names
|
|
294
|
+
await elb('order complete', { total: 99.99 }, {
|
|
295
|
+
consent: {
|
|
296
|
+
marketing: true,
|
|
297
|
+
personalization: false,
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// Becomes Data Manager format
|
|
302
|
+
{
|
|
303
|
+
consent: {
|
|
304
|
+
adUserData: 'CONSENT_GRANTED',
|
|
305
|
+
adPersonalization: 'CONSENT_DENIED'
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
**Static values** for always-on consent:
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
{
|
|
314
|
+
settings: {
|
|
315
|
+
consentAdUserData: true, // Always CONSENT_GRANTED
|
|
316
|
+
consentAdPersonalization: false, // Always CONSENT_DENIED
|
|
317
|
+
},
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
**Fallback**: Without consent mapping, uses `event.consent.marketing` →
|
|
322
|
+
`adUserData` and `event.consent.personalization` → `adPersonalization`.
|
|
323
|
+
|
|
324
|
+
## Event Mapping Examples
|
|
325
|
+
|
|
326
|
+
### E-commerce Purchase
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
{
|
|
330
|
+
mapping: {
|
|
331
|
+
order: {
|
|
332
|
+
complete: {
|
|
333
|
+
name: 'purchase',
|
|
334
|
+
data: {
|
|
335
|
+
map: {
|
|
336
|
+
transactionId: 'data.id',
|
|
337
|
+
conversionValue: 'data.total',
|
|
338
|
+
currency: { key: 'data.currency', value: 'USD' },
|
|
339
|
+
eventName: { value: 'purchase' },
|
|
340
|
+
|
|
341
|
+
// Map nested products to cart items
|
|
342
|
+
cartData: {
|
|
343
|
+
map: {
|
|
344
|
+
items: {
|
|
345
|
+
loop: [
|
|
346
|
+
'nested',
|
|
347
|
+
{
|
|
348
|
+
condition: (entity) => entity.entity === 'product',
|
|
349
|
+
map: {
|
|
350
|
+
merchantProductId: 'data.id',
|
|
351
|
+
price: 'data.price',
|
|
352
|
+
quantity: { key: 'data.quantity', value: 1 },
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
],
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Lead Generation
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
{
|
|
371
|
+
mapping: {
|
|
372
|
+
lead: {
|
|
373
|
+
submit: {
|
|
374
|
+
name: 'generate_lead',
|
|
375
|
+
data: {
|
|
376
|
+
map: {
|
|
377
|
+
eventName: { value: 'generate_lead' },
|
|
378
|
+
conversionValue: { value: 10 },
|
|
379
|
+
currency: { value: 'USD' },
|
|
380
|
+
},
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### Page View (GA4)
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
{
|
|
392
|
+
mapping: {
|
|
393
|
+
page: {
|
|
394
|
+
view: {
|
|
395
|
+
name: 'page_view',
|
|
396
|
+
data: {
|
|
397
|
+
map: {
|
|
398
|
+
eventName: { value: 'page_view' },
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
},
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
## Account Types
|
|
408
|
+
|
|
409
|
+
### Google Ads
|
|
410
|
+
|
|
411
|
+
```typescript
|
|
412
|
+
{
|
|
413
|
+
operatingAccount: {
|
|
414
|
+
accountId: '123-456-7890', // Format: XXX-XXX-XXXX
|
|
415
|
+
accountType: 'GOOGLE_ADS',
|
|
416
|
+
},
|
|
417
|
+
productDestinationId: 'AW-CONVERSION-123', // Conversion action ID
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### Display & Video 360
|
|
422
|
+
|
|
423
|
+
```typescript
|
|
424
|
+
{
|
|
425
|
+
operatingAccount: {
|
|
426
|
+
accountId: '12345', // Advertiser ID
|
|
427
|
+
accountType: 'DISPLAY_VIDEO_ADVERTISER',
|
|
428
|
+
},
|
|
429
|
+
productDestinationId: 'FL-ACTIVITY-123', // Floodlight activity ID
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Google Analytics 4
|
|
434
|
+
|
|
435
|
+
```typescript
|
|
436
|
+
{
|
|
437
|
+
operatingAccount: {
|
|
438
|
+
accountId: '123456789', // Property ID
|
|
439
|
+
accountType: 'GOOGLE_ANALYTICS_PROPERTY',
|
|
440
|
+
},
|
|
441
|
+
productDestinationId: 'G-XXXXXXXXXX', // Measurement ID
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
## Data Formatting
|
|
446
|
+
|
|
447
|
+
### Email Normalization
|
|
448
|
+
|
|
449
|
+
- Trim whitespace
|
|
450
|
+
- Convert to lowercase
|
|
451
|
+
- Remove dots (.) for gmail.com/googlemail.com
|
|
452
|
+
- SHA-256 hash
|
|
453
|
+
|
|
454
|
+
**Example:**
|
|
455
|
+
|
|
456
|
+
```
|
|
457
|
+
Input: John.Doe@Gmail.com
|
|
458
|
+
Output: <SHA-256 hash of "johndoe@gmail.com">
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
### Phone Normalization
|
|
462
|
+
|
|
463
|
+
- Convert to E.164 format: `+[country][number]`
|
|
464
|
+
- Remove all non-digit characters except leading +
|
|
465
|
+
- SHA-256 hash
|
|
466
|
+
|
|
467
|
+
**Example:**
|
|
468
|
+
|
|
469
|
+
```
|
|
470
|
+
Input: (800) 555-0100
|
|
471
|
+
Output: <SHA-256 hash of "+18005550100">
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### Address Formatting
|
|
475
|
+
|
|
476
|
+
- **Names**: Lowercase, remove titles/suffixes, SHA-256 hash
|
|
477
|
+
- **Region Code**: ISO-3166-1 alpha-2, NOT hashed (e.g., "US")
|
|
478
|
+
- **Postal Code**: NOT hashed
|
|
479
|
+
|
|
480
|
+
## Consent Management (DMA)
|
|
481
|
+
|
|
482
|
+
### Required for EEA/UK/Switzerland
|
|
483
|
+
|
|
484
|
+
```typescript
|
|
485
|
+
// Event-level consent
|
|
486
|
+
await elb('order complete', {
|
|
487
|
+
total: 99.99,
|
|
488
|
+
}, {
|
|
489
|
+
consent: {
|
|
490
|
+
marketing: true,
|
|
491
|
+
personalization: false,
|
|
492
|
+
},
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
// Request-level consent (applies to all events)
|
|
496
|
+
{
|
|
497
|
+
settings: {
|
|
498
|
+
consent: {
|
|
499
|
+
adUserData: 'CONSENT_GRANTED',
|
|
500
|
+
adPersonalization: 'CONSENT_GRANTED',
|
|
501
|
+
},
|
|
502
|
+
},
|
|
503
|
+
}
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
## Testing
|
|
507
|
+
|
|
508
|
+
### Validate Mode
|
|
509
|
+
|
|
510
|
+
Test requests without actually ingesting data:
|
|
511
|
+
|
|
512
|
+
```typescript
|
|
513
|
+
{
|
|
514
|
+
settings: {
|
|
515
|
+
validateOnly: true, // Validates structure without ingestion
|
|
516
|
+
testEventCode: 'TEST12345', // For debugging
|
|
517
|
+
},
|
|
518
|
+
}
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
### Test Event Code
|
|
522
|
+
|
|
523
|
+
Add test event code for debugging in production:
|
|
524
|
+
|
|
525
|
+
```typescript
|
|
526
|
+
{
|
|
527
|
+
settings: {
|
|
528
|
+
testEventCode: 'TEST12345',
|
|
529
|
+
},
|
|
530
|
+
}
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
## Debug Mode
|
|
534
|
+
|
|
535
|
+
Enable debug logging to see API requests and responses:
|
|
536
|
+
|
|
537
|
+
```typescript
|
|
538
|
+
{
|
|
539
|
+
settings: {
|
|
540
|
+
logLevel: 'debug', // Shows all API calls and responses
|
|
541
|
+
},
|
|
542
|
+
}
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
**Log levels**: `debug` (all), `info`, `warn`, `error`, `none` (default).
|
|
546
|
+
|
|
547
|
+
Debug mode logs:
|
|
548
|
+
|
|
549
|
+
- Event processing details
|
|
550
|
+
- API request payload and destination count
|
|
551
|
+
- API response status and request ID
|
|
552
|
+
- Validation errors with full context
|
|
553
|
+
|
|
554
|
+
## Deduplication with gtag
|
|
555
|
+
|
|
556
|
+
Prevent double-counting between client-side gtag and server-side Data Manager:
|
|
557
|
+
|
|
558
|
+
### Transaction ID Matching
|
|
559
|
+
|
|
560
|
+
Use the same transaction ID across both platforms:
|
|
561
|
+
|
|
562
|
+
```javascript
|
|
563
|
+
// Client-side gtag
|
|
564
|
+
gtag('event', 'conversion', {
|
|
565
|
+
transaction_id: 'ORDER-123',
|
|
566
|
+
send_to: 'AW-CONVERSION/xxx',
|
|
567
|
+
});
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
```typescript
|
|
571
|
+
// Server-side walkerOS
|
|
572
|
+
await elb('order complete', {
|
|
573
|
+
id: 'ORDER-123', // Must map to transactionId via mapping config
|
|
574
|
+
});
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
Google deduplicates using `transactionId` within 14 days. You must explicitly
|
|
578
|
+
map the transaction ID in your configuration.
|
|
579
|
+
|
|
580
|
+
### GCLID Attribution
|
|
581
|
+
|
|
582
|
+
Map GCLID from your event structure:
|
|
583
|
+
|
|
584
|
+
```typescript
|
|
585
|
+
{
|
|
586
|
+
mapping: {
|
|
587
|
+
order: {
|
|
588
|
+
complete: {
|
|
589
|
+
data: {
|
|
590
|
+
map: {
|
|
591
|
+
gclid: 'context.gclid', // From URL parameter
|
|
592
|
+
transactionId: 'data.id',
|
|
593
|
+
},
|
|
594
|
+
},
|
|
595
|
+
},
|
|
596
|
+
},
|
|
597
|
+
},
|
|
598
|
+
}
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
GCLID is captured by walkerOS browser source from URL parameters (`?gclid=xxx`)
|
|
602
|
+
and stored in `context.gclid`.
|
|
603
|
+
|
|
604
|
+
## Rate Limits
|
|
605
|
+
|
|
606
|
+
- **300 requests per minute** per project
|
|
607
|
+
- **100,000 requests per day** per project
|
|
608
|
+
- Error code: `RESOURCE_EXHAUSTED` (HTTP 429)
|
|
609
|
+
|
|
610
|
+
## Conversion Window
|
|
611
|
+
|
|
612
|
+
**CRITICAL**: Events must occur within the last **14 days**. Older events will
|
|
613
|
+
be rejected.
|
|
614
|
+
|
|
615
|
+
## API Reference
|
|
616
|
+
|
|
617
|
+
### Settings
|
|
618
|
+
|
|
619
|
+
| Property | Type | Required | Description |
|
|
620
|
+
| -------------------------- | -------------- | -------- | ------------------------------------------- |
|
|
621
|
+
| `accessToken` | string | ✓ | OAuth 2.0 access token |
|
|
622
|
+
| `destinations` | Destination[] | ✓ | Array of destination accounts (max 10) |
|
|
623
|
+
| `eventSource` | EventSource | | Default event source (WEB, APP, etc.) |
|
|
624
|
+
| `batchSize` | number | | Max events per batch (max 2000) |
|
|
625
|
+
| `batchInterval` | number | | Batch flush interval in ms |
|
|
626
|
+
| `validateOnly` | boolean | | Validate without ingestion |
|
|
627
|
+
| `url` | string | | Override API endpoint |
|
|
628
|
+
| `consent` | Consent | | Request-level consent |
|
|
629
|
+
| `testEventCode` | string | | Test event code for debugging |
|
|
630
|
+
| `userData` | object | | Guided helper: User data mapping |
|
|
631
|
+
| `userId` | string | | Guided helper: First-party user ID |
|
|
632
|
+
| `clientId` | string | | Guided helper: GA4 client ID |
|
|
633
|
+
| `sessionAttributes` | string | | Guided helper: Privacy-safe attribution |
|
|
634
|
+
| `consentAdUserData` | string/boolean | | Consent mapping: Field name or static value |
|
|
635
|
+
| `consentAdPersonalization` | string/boolean | | Consent mapping: Field name or static value |
|
|
636
|
+
|
|
637
|
+
### Event Fields
|
|
638
|
+
|
|
639
|
+
| Field | Type | Max Length | Description |
|
|
640
|
+
| ----------------- | ------ | ---------- | -------------------------------- |
|
|
641
|
+
| `transactionId` | string | 512 | Transaction ID for deduplication |
|
|
642
|
+
| `clientId` | string | 255 | GA client ID |
|
|
643
|
+
| `userId` | string | 256 | First-party user ID |
|
|
644
|
+
| `conversionValue` | number | | Conversion value |
|
|
645
|
+
| `currency` | string | 3 | ISO 4217 currency code |
|
|
646
|
+
| `eventName` | string | 40 | Event name (required for GA4) |
|
|
647
|
+
| `eventSource` | string | | WEB, APP, IN_STORE, PHONE, OTHER |
|
|
648
|
+
|
|
649
|
+
## Resources
|
|
650
|
+
|
|
651
|
+
- [Google Data Manager API Documentation](https://developers.google.com/data-manager/api)
|
|
652
|
+
- [Data Formatting Guidelines](https://developers.google.com/data-manager/api/devguides/concepts/formatting)
|
|
653
|
+
- [DMA Compliance](https://developers.google.com/data-manager/api/devguides/concepts/dma)
|
|
654
|
+
- [walkerOS Documentation](https://www.elbwalker.com/docs/)
|
|
655
|
+
|
|
656
|
+
## License
|
|
657
|
+
|
|
658
|
+
MIT
|
|
659
|
+
|
|
660
|
+
## Support
|
|
661
|
+
|
|
662
|
+
For issues and questions:
|
|
663
|
+
|
|
664
|
+
- [GitHub Issues](https://github.com/elbwalker/walkerOS/issues)
|
|
665
|
+
- [walkerOS Documentation](https://www.elbwalker.com/docs/)
|
|
666
|
+
- [Google Data Manager Support](https://developers.google.com/data-manager/api/support/contact)
|