cdp-edge 1.18.0 → 2.0.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.
Files changed (34) hide show
  1. package/README.md +308 -308
  2. package/bin/cdp-edge.js +61 -61
  3. package/dist/commands/analyze.js +52 -52
  4. package/dist/commands/infra.js +54 -54
  5. package/dist/commands/install.js +187 -0
  6. package/dist/commands/server.js +174 -174
  7. package/dist/commands/setup.js +19 -1
  8. package/dist/commands/validate.js +84 -84
  9. package/dist/index.js +12 -12
  10. package/extracted-skill/tracking-events-generator/advanced-matching.js +364 -364
  11. package/extracted-skill/tracking-events-generator/anti-blocking.js +285 -285
  12. package/extracted-skill/tracking-events-generator/cdpTrack.js +641 -641
  13. package/extracted-skill/tracking-events-generator/engagement-scoring.js +226 -226
  14. package/extracted-skill/tracking-events-generator/evals/evals.json +235 -235
  15. package/extracted-skill/tracking-events-generator/integration-test.js +497 -497
  16. package/extracted-skill/tracking-events-generator/micro-events.js +992 -992
  17. package/extracted-skill/tracking-events-generator/models/pinterest/conversions-api-template.js +144 -144
  18. package/extracted-skill/tracking-events-generator/models/pinterest/event-mappings.json +48 -48
  19. package/extracted-skill/tracking-events-generator/models/pinterest/tag-template.js +28 -28
  20. package/extracted-skill/tracking-events-generator/models/reddit/conversions-api-template.js +205 -205
  21. package/extracted-skill/tracking-events-generator/models/reddit/event-mappings.json +56 -56
  22. package/extracted-skill/tracking-events-generator/models/reddit/pixel-template.js +19 -19
  23. package/extracted-skill/tracking-events-generator/models/scenarios/behavior-engine.js +425 -425
  24. package/package.json +76 -76
  25. package/server-edge-tracker/schema.sql +265 -265
  26. package/server-edge-tracker/worker.js +4160 -4160
  27. package/server-edge-tracker/wrangler.toml +103 -103
  28. package/templates/pinterest/conversions-api-template.js +144 -144
  29. package/templates/pinterest/event-mappings.json +48 -48
  30. package/templates/pinterest/tag-template.js +28 -28
  31. package/templates/reddit/conversions-api-template.js +205 -205
  32. package/templates/reddit/event-mappings.json +56 -56
  33. package/templates/reddit/pixel-template.js +19 -19
  34. package/templates/scenarios/behavior-engine.js +425 -425
@@ -1,103 +1,103 @@
1
- name = "server-edge-tracker"
2
- main = "worker.js"
3
- compatibility_date = "2025-01-01"
4
- compatibility_flags = ["nodejs_compat"]
5
-
6
- # ── Worker Routes — same-domain tracking (imune a bloqueios) ─────────────────
7
- # Substituir SEU_DOMINIO pelo domínio do cliente antes do deploy
8
- # [[routes]]
9
- # pattern = "SEU_DOMINIO/track*"
10
- # zone_name = "SEU_DOMINIO"
11
- #
12
- # [[routes]]
13
- # pattern = "*.SEU_DOMINIO/track*"
14
- # zone_name = "SEU_DOMINIO"
15
-
16
- [[routes]]
17
- pattern = "lancamentosabc.com.br/track*"
18
- zone_name = "lancamentosabc.com.br"
19
-
20
- [[routes]]
21
- pattern = "*.lancamentosabc.com.br/track*"
22
- zone_name = "lancamentosabc.com.br"
23
-
24
- # ── Variáveis públicas (não são segredos) ─────────────────────────────────────
25
- [vars]
26
- META_PIXEL_ID = "SEU_META_PIXEL_ID"
27
- GA4_MEASUREMENT_ID = "G-XXXXXXXXXX"
28
- TIKTOK_PIXEL_ID = "CXXXXXXXXXXXXXXX"
29
- SITE_DOMAIN = "SEU_DOMINIO"
30
-
31
- # ── Banco D1 ──────────────────────────────────────────────────────────────────
32
- # Após criar o banco com "wrangler d1 create cdp-edge-db",
33
- # substitua o database_id pelo ID retornado no terminal.
34
- [[d1_databases]]
35
- binding = "DB"
36
- database_name = "cdp-edge-db"
37
- database_id = "7867d38f-5fa8-4c17-b465-386211422c09"
38
-
39
- # ── Queues — Retry + Dead Letter Queue ───────────────────────────────────────
40
- # Produtor: worker envia eventos com falha para cdp-edge-retry
41
- # Consumidor: worker processa a fila e reaplica; falhas vão para cdp-edge-dlq
42
- # Criar com: wrangler queues create cdp-edge-retry
43
- # wrangler queues create cdp-edge-dlq
44
- [[queues.producers]]
45
- binding = "RETRY_QUEUE"
46
- queue = "cdp-edge-retry"
47
-
48
- [[queues.consumers]]
49
- queue = "cdp-edge-retry"
50
- max_batch_size = 10
51
- max_batch_timeout = 30
52
- max_retries = 3
53
- dead_letter_queue = "cdp-edge-dlq"
54
-
55
- # ── KV Namespace — Geo/Session Cache ─────────────────────────────────────────
56
- # Cache de geolocalização e sessões recentes (TTL: 1h por padrão)
57
- # Criar com: wrangler kv namespace create GEO_CACHE
58
- # wrangler kv namespace create GEO_CACHE --preview
59
- [[kv_namespaces]]
60
- binding = "GEO_CACHE"
61
- id = "821b6c1ccb4b475985439b801c1fdbe0"
62
- preview_id = "d2d9198f47e340ee905a8dc566b09e95"
63
-
64
- # ── R2 Bucket — Audit Logs ────────────────────────────────────────────────────
65
- # ⚠️ PENDENTE: Habilitar R2 no Cloudflare Dashboard antes de descomentar
66
- # Dashboard → R2 → Enable → depois: wrangler r2 bucket create cdp-edge-logs
67
- # [[r2_buckets]]
68
- # binding = "AUDIT_LOGS"
69
- # bucket_name = "cdp-edge-logs"
70
-
71
- # ── Cron Triggers — Intelligence Agent ───────────────────────────────────────
72
- # Semanal: domingo 02:00 UTC — check de versões de API + relatório diário
73
- # Mensal: 1º do mês 03:00 UTC — auditoria de taxa de erro + alertas críticos
74
- [triggers]
75
- crons = ["0 2 * * 7", "0 3 1 * *"]
76
-
77
- # ── Cloudflare Workers AI ─────────────────────────────────────────────────────
78
- # Habilita env.AI para LTV Prediction (Fase 4)
79
- # Plano gratuito inclui 10.000 neurônios/dia — suficiente para enrichment
80
- [ai]
81
- binding = "AI"
82
-
83
- # ── Secrets (NÃO ficam aqui — configurar via CLI) ─────────────────────────────
84
- # wrangler secret put META_ACCESS_TOKEN ← token Meta CAPI (obrigatório)
85
- # wrangler secret put GA4_API_SECRET ← secret GA4 Measurement Protocol (obrigatório)
86
- # wrangler secret put TIKTOK_ACCESS_TOKEN ← token TikTok Events API (opcional)
87
- # wrangler secret put META_TEST_CODE ← só para testes (remover em produção)
88
- # wrangler secret put META_AD_ACCOUNT_ID ← ID da conta de anúncios Meta (act_XXXXXXXXX) — Customer Match
89
- # wrangler secret put META_AUDIENCE_ID ← ID da Custom Audience Meta — Customer Match automático
90
- # wrangler secret put WHATSAPP_TOKEN ← Token WhatsApp Cloud API (Meta Business Suite)
91
- # wrangler secret put WHATSAPP_PHONE_NUMBER_ID ← ID do número WhatsApp (Meta Business Suite → Phone Numbers)
92
- # wrangler secret put RESEND_API_KEY ← API Key do Resend (resend.com)
93
- # wrangler secret put RESEND_FROM_EMAIL ← Remetente verificado ex: "CDP Edge <noreply@seudominio.com.br>"
94
- # wrangler secret put WA_WEBHOOK_VERIFY_TOKEN ← Token de verificação do webhook WhatsApp (você define — qualquer string segura)
95
- # wrangler secret put PINTEREST_ACCESS_TOKEN ← Bearer token Pinterest Conversions API
96
- # wrangler secret put PINTEREST_AD_ACCOUNT_ID ← ID da conta de anúncios Pinterest (ex: 549755813XXX)
97
- # wrangler secret put REDDIT_ACCESS_TOKEN ← Bearer token Reddit Conversions API
98
- # wrangler secret put REDDIT_AD_ACCOUNT_ID ← ID da conta de anúncios Reddit (ex: t2_XXXXXXX)
99
- # wrangler secret put LINKEDIN_ACCESS_TOKEN ← OAuth2 Bearer token LinkedIn Marketing API
100
- # wrangler secret put LINKEDIN_CONVERSION_ID ← ID da conversão LinkedIn (ex: 12345678)
101
- # wrangler secret put LINKEDIN_AD_ACCOUNT_ID ← ID da conta de anúncios LinkedIn
102
- # wrangler secret put SPOTIFY_ACCESS_TOKEN ← Bearer token Spotify Advertising API
103
- # wrangler secret put SPOTIFY_AD_ACCOUNT_ID ← ID da conta de anúncios Spotify Ads
1
+ name = "server-edge-tracker"
2
+ main = "worker.js"
3
+ compatibility_date = "2025-01-01"
4
+ compatibility_flags = ["nodejs_compat"]
5
+
6
+ # ── Worker Routes — same-domain tracking (imune a bloqueios) ─────────────────
7
+ # Substituir SEU_DOMINIO pelo domínio do cliente antes do deploy
8
+ # [[routes]]
9
+ # pattern = "SEU_DOMINIO/track*"
10
+ # zone_name = "SEU_DOMINIO"
11
+ #
12
+ # [[routes]]
13
+ # pattern = "*.SEU_DOMINIO/track*"
14
+ # zone_name = "SEU_DOMINIO"
15
+
16
+ [[routes]]
17
+ pattern = "lancamentosabc.com.br/track*"
18
+ zone_name = "lancamentosabc.com.br"
19
+
20
+ [[routes]]
21
+ pattern = "*.lancamentosabc.com.br/track*"
22
+ zone_name = "lancamentosabc.com.br"
23
+
24
+ # ── Variáveis públicas (não são segredos) ─────────────────────────────────────
25
+ [vars]
26
+ META_PIXEL_ID = "SEU_META_PIXEL_ID"
27
+ GA4_MEASUREMENT_ID = "G-XXXXXXXXXX"
28
+ TIKTOK_PIXEL_ID = "CXXXXXXXXXXXXXXX"
29
+ SITE_DOMAIN = "SEU_DOMINIO"
30
+
31
+ # ── Banco D1 ──────────────────────────────────────────────────────────────────
32
+ # Após criar o banco com "wrangler d1 create cdp-edge-db",
33
+ # substitua o database_id pelo ID retornado no terminal.
34
+ [[d1_databases]]
35
+ binding = "DB"
36
+ database_name = "cdp-edge-db"
37
+ database_id = "7867d38f-5fa8-4c17-b465-386211422c09"
38
+
39
+ # ── Queues — Retry + Dead Letter Queue ───────────────────────────────────────
40
+ # Produtor: worker envia eventos com falha para cdp-edge-retry
41
+ # Consumidor: worker processa a fila e reaplica; falhas vão para cdp-edge-dlq
42
+ # Criar com: wrangler queues create cdp-edge-retry
43
+ # wrangler queues create cdp-edge-dlq
44
+ [[queues.producers]]
45
+ binding = "RETRY_QUEUE"
46
+ queue = "cdp-edge-retry"
47
+
48
+ [[queues.consumers]]
49
+ queue = "cdp-edge-retry"
50
+ max_batch_size = 10
51
+ max_batch_timeout = 30
52
+ max_retries = 3
53
+ dead_letter_queue = "cdp-edge-dlq"
54
+
55
+ # ── KV Namespace — Geo/Session Cache ─────────────────────────────────────────
56
+ # Cache de geolocalização e sessões recentes (TTL: 1h por padrão)
57
+ # Criar com: wrangler kv namespace create GEO_CACHE
58
+ # wrangler kv namespace create GEO_CACHE --preview
59
+ [[kv_namespaces]]
60
+ binding = "GEO_CACHE"
61
+ id = "821b6c1ccb4b475985439b801c1fdbe0"
62
+ preview_id = "d2d9198f47e340ee905a8dc566b09e95"
63
+
64
+ # ── R2 Bucket — Audit Logs ────────────────────────────────────────────────────
65
+ # ⚠️ PENDENTE: Habilitar R2 no Cloudflare Dashboard antes de descomentar
66
+ # Dashboard → R2 → Enable → depois: wrangler r2 bucket create cdp-edge-logs
67
+ # [[r2_buckets]]
68
+ # binding = "AUDIT_LOGS"
69
+ # bucket_name = "cdp-edge-logs"
70
+
71
+ # ── Cron Triggers — Intelligence Agent ───────────────────────────────────────
72
+ # Semanal: domingo 02:00 UTC — check de versões de API + relatório diário
73
+ # Mensal: 1º do mês 03:00 UTC — auditoria de taxa de erro + alertas críticos
74
+ [triggers]
75
+ crons = ["0 2 * * 7", "0 3 1 * *"]
76
+
77
+ # ── Cloudflare Workers AI ─────────────────────────────────────────────────────
78
+ # Habilita env.AI para LTV Prediction (Fase 4)
79
+ # Plano gratuito inclui 10.000 neurônios/dia — suficiente para enrichment
80
+ [ai]
81
+ binding = "AI"
82
+
83
+ # ── Secrets (NÃO ficam aqui — configurar via CLI) ─────────────────────────────
84
+ # wrangler secret put META_ACCESS_TOKEN ← token Meta CAPI (obrigatório)
85
+ # wrangler secret put GA4_API_SECRET ← secret GA4 Measurement Protocol (obrigatório)
86
+ # wrangler secret put TIKTOK_ACCESS_TOKEN ← token TikTok Events API (opcional)
87
+ # wrangler secret put META_TEST_CODE ← só para testes (remover em produção)
88
+ # wrangler secret put META_AD_ACCOUNT_ID ← ID da conta de anúncios Meta (act_XXXXXXXXX) — Customer Match
89
+ # wrangler secret put META_AUDIENCE_ID ← ID da Custom Audience Meta — Customer Match automático
90
+ # wrangler secret put WHATSAPP_TOKEN ← Token WhatsApp Cloud API (Meta Business Suite)
91
+ # wrangler secret put WHATSAPP_PHONE_NUMBER_ID ← ID do número WhatsApp (Meta Business Suite → Phone Numbers)
92
+ # wrangler secret put RESEND_API_KEY ← API Key do Resend (resend.com)
93
+ # wrangler secret put RESEND_FROM_EMAIL ← Remetente verificado ex: "CDP Edge <noreply@seudominio.com.br>"
94
+ # wrangler secret put WA_WEBHOOK_VERIFY_TOKEN ← Token de verificação do webhook WhatsApp (você define — qualquer string segura)
95
+ # wrangler secret put PINTEREST_ACCESS_TOKEN ← Bearer token Pinterest Conversions API
96
+ # wrangler secret put PINTEREST_AD_ACCOUNT_ID ← ID da conta de anúncios Pinterest (ex: 549755813XXX)
97
+ # wrangler secret put REDDIT_ACCESS_TOKEN ← Bearer token Reddit Conversions API
98
+ # wrangler secret put REDDIT_AD_ACCOUNT_ID ← ID da conta de anúncios Reddit (ex: t2_XXXXXXX)
99
+ # wrangler secret put LINKEDIN_ACCESS_TOKEN ← OAuth2 Bearer token LinkedIn Marketing API
100
+ # wrangler secret put LINKEDIN_CONVERSION_ID ← ID da conversão LinkedIn (ex: 12345678)
101
+ # wrangler secret put LINKEDIN_AD_ACCOUNT_ID ← ID da conta de anúncios LinkedIn
102
+ # wrangler secret put SPOTIFY_ACCESS_TOKEN ← Bearer token Spotify Advertising API
103
+ # wrangler secret put SPOTIFY_AD_ACCOUNT_ID ← ID da conta de anúncios Spotify Ads
@@ -1,144 +1,144 @@
1
- /**
2
- * Pinterest Conversions API Server Template — CDP Edge Quantum Tier
3
- *
4
- * Este template contém a função de envio para a Conversions API do Pinterest
5
- * Uso: Incluir no worker.js gerado pelo Server Tracking Agent
6
- */
7
-
8
- /**
9
- * Função principal de envio para Pinterest Conversions API
10
- * @param {Object} env - Variáveis de ambiente do Cloudflare Worker
11
- * @param {Object} eventData - Dados do evento a ser enviado
12
- * @returns {Promise<Object>} - Resposta da API
13
- */
14
- export async function sendPinterestApi(env, eventData) {
15
- const {
16
- email, phone, userId, clientIp, userAgent, pageUrl,
17
- eventId, value, currency, orderId, productName, productId
18
- } = eventData;
19
-
20
- // Verificar se as credenciais estão configuradas
21
- if (!env.PINTEREST_ACCESS_TOKEN || !env.PINTEREST_AD_ACCOUNT_ID) {
22
- console.warn('Pinterest Conversions API: Credenciais não configuradas');
23
- return { success: false, error: 'MISSING_CREDENTIALS' };
24
- }
25
-
26
- // Função de hash SHA-256
27
- async function sha256(str) {
28
- if (!str) return undefined;
29
- const buf = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(str.toLowerCase().trim()));
30
- return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
31
- }
32
-
33
- // Normalizar evento — Pinterest usa nomes específicos
34
- const pinterestEventMap = {
35
- 'PageView': 'pagevisit',
36
- 'Lead': 'lead',
37
- 'Purchase': 'checkout',
38
- 'AddToCart': 'addtocart',
39
- 'InitiateCheckout': 'checkout',
40
- 'ViewContent': 'pagevisit',
41
- 'CompleteRegistration': 'signup',
42
- 'Search': 'search',
43
- };
44
- const pinterestEvent = pinterestEventMap[eventName] || 'custom';
45
-
46
- // User data com hashing
47
- const userData = {};
48
- if (email) userData.em = [await sha256(email)];
49
- if (phone) userData.ph = [await sha256(phone.replace(/\D/g, ''))];
50
- if (userId) userData.external_id = [await sha256(userId)];
51
- if (clientIp) userData.client_ip_address = clientIp; // sem hash
52
- if (userAgent) userData.client_user_agent = userAgent; // sem hash
53
-
54
- // Payload da Conversions API
55
- const payload = {
56
- data: [{
57
- event_name: pinterestEvent,
58
- action_source: 'web',
59
- event_time: Math.floor(Date.now() / 1000),
60
- event_id: eventId, // deduplicação com browser tag
61
- event_source_url: pageUrl || '',
62
- user_data: userData,
63
- custom_data: {
64
- currency: currency || 'BRL',
65
- value: value ? String(value) : '0',
66
- order_id: orderId || undefined,
67
- content_ids: productId ? [productId] : undefined,
68
- content_name: productName || undefined,
69
- content_type: 'product',
70
- },
71
- // Para lead: adicionar lead_type
72
- ...(eventName === 'Lead' ? { custom_data: { ...{}, lead_type: 'Newsletter' } } : {}),
73
- }],
74
- };
75
-
76
- try {
77
- const resp = await fetch(
78
- `https://api.pinterest.com/v5/ad_accounts/${env.PINTEREST_AD_ACCOUNT_ID}/events`,
79
- {
80
- method: 'POST',
81
- headers: {
82
- 'Content-Type': 'application/json',
83
- 'Authorization': `Bearer ${env.PINTEREST_ACCESS_TOKEN}`,
84
- },
85
- body: JSON.stringify(payload),
86
- }
87
- );
88
-
89
- const result = await resp.json();
90
-
91
- // Resposta de sucesso: { num_events_received: 1, num_events_processed: 1 }
92
- if (result.num_events_received === 1 && result.num_events_processed === 1) {
93
- return { success: true, result };
94
- } else {
95
- return { success: false, error: 'API_ERROR', result };
96
- }
97
-
98
- } catch (error) {
99
- console.error('Pinterest Conversions API Error:', error);
100
- return { success: false, error: error.message };
101
- }
102
- }
103
-
104
- /**
105
- * Função de Enhanced Match — re-inicializa o pixel com dados hasheados
106
- * @param {string} email - Email do usuário (opcional)
107
- * @param {string} phone - Telefone do usuário (opcional)
108
- */
109
- export async function reinitPinterestWithEnhancedMatch(email, phone) {
110
- if (!email && !phone) return;
111
-
112
- const hashedEmail = email ? await sha256Email(email) : '';
113
- const hashedPhone = phone ? await sha256Phone(phone.replace(/\D/g, '')) : '';
114
-
115
- // Re-load com dados hasheados para Enhanced Match
116
- if (typeof pintrk !== 'undefined') {
117
- pintrk('load', '{PINTEREST_TAG_ID}', {
118
- em: hashedEmail,
119
- ph: hashedPhone
120
- });
121
- }
122
- }
123
-
124
- /**
125
- * Funções de hash para Enhanced Match no browser
126
- */
127
- export async function sha256Email(str) {
128
- if (!str) return '';
129
- const normalized = str.toLowerCase().trim();
130
- const buf = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(normalized));
131
- return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
132
- }
133
-
134
- export async function sha256Phone(str) {
135
- if (!str) return '';
136
- const normalized = str.replace(/\D/g, '').toLowerCase().trim();
137
- const buf = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(normalized));
138
- return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
139
- }
140
-
141
- export default {
142
- sendPinterestApi,
143
- reinitPinterestWithEnhancedMatch
144
- };
1
+ /**
2
+ * Pinterest Conversions API Server Template — CDP Edge Quantum Tier
3
+ *
4
+ * Este template contém a função de envio para a Conversions API do Pinterest
5
+ * Uso: Incluir no worker.js gerado pelo Server Tracking Agent
6
+ */
7
+
8
+ /**
9
+ * Função principal de envio para Pinterest Conversions API
10
+ * @param {Object} env - Variáveis de ambiente do Cloudflare Worker
11
+ * @param {Object} eventData - Dados do evento a ser enviado
12
+ * @returns {Promise<Object>} - Resposta da API
13
+ */
14
+ export async function sendPinterestApi(env, eventData) {
15
+ const {
16
+ email, phone, userId, clientIp, userAgent, pageUrl,
17
+ eventId, value, currency, orderId, productName, productId
18
+ } = eventData;
19
+
20
+ // Verificar se as credenciais estão configuradas
21
+ if (!env.PINTEREST_ACCESS_TOKEN || !env.PINTEREST_AD_ACCOUNT_ID) {
22
+ console.warn('Pinterest Conversions API: Credenciais não configuradas');
23
+ return { success: false, error: 'MISSING_CREDENTIALS' };
24
+ }
25
+
26
+ // Função de hash SHA-256
27
+ async function sha256(str) {
28
+ if (!str) return undefined;
29
+ const buf = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(str.toLowerCase().trim()));
30
+ return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
31
+ }
32
+
33
+ // Normalizar evento — Pinterest usa nomes específicos
34
+ const pinterestEventMap = {
35
+ 'PageView': 'pagevisit',
36
+ 'Lead': 'lead',
37
+ 'Purchase': 'checkout',
38
+ 'AddToCart': 'addtocart',
39
+ 'InitiateCheckout': 'checkout',
40
+ 'ViewContent': 'pagevisit',
41
+ 'CompleteRegistration': 'signup',
42
+ 'Search': 'search',
43
+ };
44
+ const pinterestEvent = pinterestEventMap[eventName] || 'custom';
45
+
46
+ // User data com hashing
47
+ const userData = {};
48
+ if (email) userData.em = [await sha256(email)];
49
+ if (phone) userData.ph = [await sha256(phone.replace(/\D/g, ''))];
50
+ if (userId) userData.external_id = [await sha256(userId)];
51
+ if (clientIp) userData.client_ip_address = clientIp; // sem hash
52
+ if (userAgent) userData.client_user_agent = userAgent; // sem hash
53
+
54
+ // Payload da Conversions API
55
+ const payload = {
56
+ data: [{
57
+ event_name: pinterestEvent,
58
+ action_source: 'web',
59
+ event_time: Math.floor(Date.now() / 1000),
60
+ event_id: eventId, // deduplicação com browser tag
61
+ event_source_url: pageUrl || '',
62
+ user_data: userData,
63
+ custom_data: {
64
+ currency: currency || 'BRL',
65
+ value: value ? String(value) : '0',
66
+ order_id: orderId || undefined,
67
+ content_ids: productId ? [productId] : undefined,
68
+ content_name: productName || undefined,
69
+ content_type: 'product',
70
+ },
71
+ // Para lead: adicionar lead_type
72
+ ...(eventName === 'Lead' ? { custom_data: { ...{}, lead_type: 'Newsletter' } } : {}),
73
+ }],
74
+ };
75
+
76
+ try {
77
+ const resp = await fetch(
78
+ `https://api.pinterest.com/v5/ad_accounts/${env.PINTEREST_AD_ACCOUNT_ID}/events`,
79
+ {
80
+ method: 'POST',
81
+ headers: {
82
+ 'Content-Type': 'application/json',
83
+ 'Authorization': `Bearer ${env.PINTEREST_ACCESS_TOKEN}`,
84
+ },
85
+ body: JSON.stringify(payload),
86
+ }
87
+ );
88
+
89
+ const result = await resp.json();
90
+
91
+ // Resposta de sucesso: { num_events_received: 1, num_events_processed: 1 }
92
+ if (result.num_events_received === 1 && result.num_events_processed === 1) {
93
+ return { success: true, result };
94
+ } else {
95
+ return { success: false, error: 'API_ERROR', result };
96
+ }
97
+
98
+ } catch (error) {
99
+ console.error('Pinterest Conversions API Error:', error);
100
+ return { success: false, error: error.message };
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Função de Enhanced Match — re-inicializa o pixel com dados hasheados
106
+ * @param {string} email - Email do usuário (opcional)
107
+ * @param {string} phone - Telefone do usuário (opcional)
108
+ */
109
+ export async function reinitPinterestWithEnhancedMatch(email, phone) {
110
+ if (!email && !phone) return;
111
+
112
+ const hashedEmail = email ? await sha256Email(email) : '';
113
+ const hashedPhone = phone ? await sha256Phone(phone.replace(/\D/g, '')) : '';
114
+
115
+ // Re-load com dados hasheados para Enhanced Match
116
+ if (typeof pintrk !== 'undefined') {
117
+ pintrk('load', '{PINTEREST_TAG_ID}', {
118
+ em: hashedEmail,
119
+ ph: hashedPhone
120
+ });
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Funções de hash para Enhanced Match no browser
126
+ */
127
+ export async function sha256Email(str) {
128
+ if (!str) return '';
129
+ const normalized = str.toLowerCase().trim();
130
+ const buf = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(normalized));
131
+ return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
132
+ }
133
+
134
+ export async function sha256Phone(str) {
135
+ if (!str) return '';
136
+ const normalized = str.replace(/\D/g, '').toLowerCase().trim();
137
+ const buf = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(normalized));
138
+ return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
139
+ }
140
+
141
+ export default {
142
+ sendPinterestApi,
143
+ reinitPinterestWithEnhancedMatch
144
+ };
@@ -1,48 +1,48 @@
1
- {
2
- "platform": "pinterest",
3
- "version": "v5",
4
- "event_mappings": {
5
- "pixel_to_native": {
6
- "PageView": "pagevisit",
7
- "Lead": "lead",
8
- "Purchase": "checkout",
9
- "AddToCart": "addtocart",
10
- "InitiateCheckout": "checkout",
11
- "ViewContent": "pagevisit",
12
- "CompleteRegistration": "signup",
13
- "Search": "search"
14
- },
15
- "native_names": {
16
- "pagevisit": "Visualização de página",
17
- "lead": "Formulário de lead",
18
- "checkout": "Compra confirmada",
19
- "addtocart": "Adicionar ao carrinho",
20
- "signup": "Cadastro",
21
- "search": "Busca"
22
- }
23
- },
24
- "required_parameters": {
25
- "pagevisit": ["line_items"],
26
- "lead": ["lead_type"],
27
- "addtocart": ["value", "currency", "line_items"],
28
- "checkout": ["value", "currency", "order_id", "line_items"],
29
- "signup": ["lead_type"],
30
- "search": ["search_query"],
31
- "watchvideo": []
32
- },
33
- "optional_parameters": {
34
- "pagevisit": ["custom_data"],
35
- "lead": ["custom_data"],
36
- "addtocart": ["custom_data"],
37
- "checkout": ["custom_data"],
38
- "signup": ["custom_data"],
39
- "search": ["custom_data"]
40
- },
41
- "enhanced_match_fields": {
42
- "email": "em",
43
- "phone": "ph",
44
- "external_id": "external_id",
45
- "client_ip_address": "client_ip_address",
46
- "client_user_agent": "client_user_agent"
47
- }
48
- }
1
+ {
2
+ "platform": "pinterest",
3
+ "version": "v5",
4
+ "event_mappings": {
5
+ "pixel_to_native": {
6
+ "PageView": "pagevisit",
7
+ "Lead": "lead",
8
+ "Purchase": "checkout",
9
+ "AddToCart": "addtocart",
10
+ "InitiateCheckout": "checkout",
11
+ "ViewContent": "pagevisit",
12
+ "CompleteRegistration": "signup",
13
+ "Search": "search"
14
+ },
15
+ "native_names": {
16
+ "pagevisit": "Visualização de página",
17
+ "lead": "Formulário de lead",
18
+ "checkout": "Compra confirmada",
19
+ "addtocart": "Adicionar ao carrinho",
20
+ "signup": "Cadastro",
21
+ "search": "Busca"
22
+ }
23
+ },
24
+ "required_parameters": {
25
+ "pagevisit": ["line_items"],
26
+ "lead": ["lead_type"],
27
+ "addtocart": ["value", "currency", "line_items"],
28
+ "checkout": ["value", "currency", "order_id", "line_items"],
29
+ "signup": ["lead_type"],
30
+ "search": ["search_query"],
31
+ "watchvideo": []
32
+ },
33
+ "optional_parameters": {
34
+ "pagevisit": ["custom_data"],
35
+ "lead": ["custom_data"],
36
+ "addtocart": ["custom_data"],
37
+ "checkout": ["custom_data"],
38
+ "signup": ["custom_data"],
39
+ "search": ["custom_data"]
40
+ },
41
+ "enhanced_match_fields": {
42
+ "email": "em",
43
+ "phone": "ph",
44
+ "external_id": "external_id",
45
+ "client_ip_address": "client_ip_address",
46
+ "client_user_agent": "client_user_agent"
47
+ }
48
+ }
@@ -1,28 +1,28 @@
1
- /**
2
- * Pinterest Pixel Browser Template — CDP Edge Quantum Tier
3
- *
4
- * Este template contém o código de inicialização do Pinterest Pixel
5
- * Uso: Incluir no tracking.js gerado pelo Browser Tracking Agent
6
- */
7
-
8
- export const PINTEREST_TAG_TEMPLATE = `
9
- <!-- Pinterest Pixel Base Code -->
10
- <script>
11
- !function(e){if(!window.pintrk){window.pintrk=function(){
12
- window.pintrk.queue.push(Array.prototype.slice.call(arguments))};
13
- var n=window.pintrk;n.queue=[],n.version="3.0";
14
- var t=document.createElement("script");
15
- t.async=!0,t.src=e;
16
- var r=document.getElementsByTagName("script")[0];
17
- r.parentNode.insertBefore(t,r)}}("https://s.pinimg.com/ct/core.js");
18
-
19
- pintrk('load', '{PINTEREST_TAG_ID}', {
20
- em: '<user_email_if_known>' // Enhanced Match — enviar email quando disponível
21
- });
22
- pintrk('page');
23
- </script>
24
- <noscript>
25
- <img height="1" width="1" style="display:none;"
26
- src="https://ct.pinterest.com/v3/?event=init&tid={PINTEREST_TAG_ID}&pd[em]=<hashed_email>&noscript=1" />
27
- </noscript>
28
- `;
1
+ /**
2
+ * Pinterest Pixel Browser Template — CDP Edge Quantum Tier
3
+ *
4
+ * Este template contém o código de inicialização do Pinterest Pixel
5
+ * Uso: Incluir no tracking.js gerado pelo Browser Tracking Agent
6
+ */
7
+
8
+ export const PINTEREST_TAG_TEMPLATE = `
9
+ <!-- Pinterest Pixel Base Code -->
10
+ <script>
11
+ !function(e){if(!window.pintrk){window.pintrk=function(){
12
+ window.pintrk.queue.push(Array.prototype.slice.call(arguments))};
13
+ var n=window.pintrk;n.queue=[],n.version="3.0";
14
+ var t=document.createElement("script");
15
+ t.async=!0,t.src=e;
16
+ var r=document.getElementsByTagName("script")[0];
17
+ r.parentNode.insertBefore(t,r)}}("https://s.pinimg.com/ct/core.js");
18
+
19
+ pintrk('load', '{PINTEREST_TAG_ID}', {
20
+ em: '<user_email_if_known>' // Enhanced Match — enviar email quando disponível
21
+ });
22
+ pintrk('page');
23
+ </script>
24
+ <noscript>
25
+ <img height="1" width="1" style="display:none;"
26
+ src="https://ct.pinterest.com/v3/?event=init&tid={PINTEREST_TAG_ID}&pd[em]=<hashed_email>&noscript=1" />
27
+ </noscript>
28
+ `;