cdp-edge 2.0.1 → 2.0.3
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 +325 -308
- package/contracts/api-versions.json +12 -8
- package/dist/commands/install.js +1 -1
- package/dist/commands/setup.js +1 -1
- package/extracted-skill/tracking-events-generator/agents/attribution-agent.md +23 -23
- package/extracted-skill/tracking-events-generator/agents/browser-tracking.md +2 -2
- package/extracted-skill/tracking-events-generator/agents/compliance-agent.md +20 -0
- package/extracted-skill/tracking-events-generator/agents/crm-integration-agent.md +48 -16
- package/extracted-skill/tracking-events-generator/agents/dashboard-agent.md +7 -7
- package/extracted-skill/tracking-events-generator/agents/database-agent.md +8 -8
- package/extracted-skill/tracking-events-generator/agents/debug-agent.md +13 -13
- package/extracted-skill/tracking-events-generator/agents/devops-agent.md +31 -7
- package/extracted-skill/tracking-events-generator/agents/email-agent.md +27 -0
- package/extracted-skill/tracking-events-generator/agents/fingerprint-agent.md +205 -0
- package/extracted-skill/tracking-events-generator/agents/intelligence-agent.md +6 -6
- package/extracted-skill/tracking-events-generator/agents/linkedin-agent.md +108 -0
- package/extracted-skill/tracking-events-generator/agents/ltv-predictor-agent.md +1 -1
- package/extracted-skill/tracking-events-generator/agents/master-feedback-loop.md +68 -8
- package/extracted-skill/tracking-events-generator/agents/master-orchestrator.md +75 -38
- package/extracted-skill/tracking-events-generator/agents/performance-agent.md +29 -19
- package/extracted-skill/tracking-events-generator/agents/performance-optimization-agent.md +11 -1
- package/extracted-skill/tracking-events-generator/agents/premium-tracking-intelligence-agent.md +4 -4
- package/extracted-skill/tracking-events-generator/agents/security-enterprise-agent.md +137 -28
- package/extracted-skill/tracking-events-generator/agents/server-tracking.md +15 -16
- package/extracted-skill/tracking-events-generator/agents/spotify-agent.md +1 -1
- package/extracted-skill/tracking-events-generator/agents/tiktok-agent.md +63 -0
- package/extracted-skill/tracking-events-generator/agents/tracking-plan-agent.md +100 -5
- package/extracted-skill/tracking-events-generator/agents/webhook-agent.md +62 -4
- package/extracted-skill/tracking-events-generator/agents/whatsapp-agent.md +58 -5
- package/extracted-skill/tracking-events-generator/agents/whatsapp-ctwa-setup-agent.md +16 -16
- package/extracted-skill/tracking-events-generator/agents/youtube-agent.md +143 -28
- package/extracted-skill/tracking-events-generator/contracts/api-versions.json +12 -8
- package/package.json +76 -76
- package/server-edge-tracker/worker.js +53 -8
|
@@ -65,12 +65,70 @@ async function hashWebhookUserData(webhookPayload) {
|
|
|
65
65
|
|
|
66
66
|
## 📦 ENTREGÁVEIS
|
|
67
67
|
|
|
68
|
-
1. **Route Handler**: `/
|
|
68
|
+
1. **Route Handler**: `/webhook/{gateway}` no Worker.
|
|
69
69
|
2. **D1 Query**: Lógica de atualização do perfil do usuário com o status de comprador.
|
|
70
70
|
3. **CAPI Dispatch**: Envio dos dados enriquecidos para as redes de anúncio.
|
|
71
71
|
|
|
72
72
|
---
|
|
73
73
|
|
|
74
|
+
## 🔗 INTEGRAÇÃO COM OUTROS AGENTES (Fluxo Pós-Compra)
|
|
75
|
+
|
|
76
|
+
Após processar um webhook de compra com sucesso, o Webhook Agent DEVE disparar:
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
Webhook (Hotmart/Kiwify/Ticto/Stripe)
|
|
80
|
+
│
|
|
81
|
+
├─► [1] Validar HMAC → rejeitar 401 se inválido
|
|
82
|
+
├─► [2] Dedup D1 por transaction_id
|
|
83
|
+
├─► [3] Cross-check D1 por email → fbp/fbc/ttp/gclid
|
|
84
|
+
├─► [4] Hashear PII (SHA-256) — ver seção acima
|
|
85
|
+
│
|
|
86
|
+
├─► [5] CAPI Dispatch (ctx.waitUntil) — paralelo:
|
|
87
|
+
│ → Meta CAPI v22.0 (Purchase)
|
|
88
|
+
│ → GA4 MP (purchase)
|
|
89
|
+
│ → TikTok Events API v1.3 (CompletePayment)
|
|
90
|
+
│
|
|
91
|
+
├─► [6] Email Agent (ctx.waitUntil) — enviar confirmação de compra:
|
|
92
|
+
│ → Chamar sendEmail(env, 'purchase_confirmation', { email, nome, produto, valor })
|
|
93
|
+
│ → Ver email-agent.md para implementação completa
|
|
94
|
+
│
|
|
95
|
+
└─► [7] CRM Sync (ctx.waitUntil) — sincronizar comprador:
|
|
96
|
+
→ Chamar syncToCRM(env, 'purchase', { email, nome, produto, valor, order_id })
|
|
97
|
+
→ Ver crm-integration-agent.md para implementação completa
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Código de Integração (webhook handler)
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
// No handler de webhook, após validação e dedup:
|
|
104
|
+
ctx.waitUntil(Promise.allSettled([
|
|
105
|
+
// [5] CAPI dispatch
|
|
106
|
+
dispatchToCAPI(env, hashedUserData, purchaseData),
|
|
107
|
+
|
|
108
|
+
// [6] Email Agent — confirmação de compra
|
|
109
|
+
sendEmail(env, 'purchase_confirmation', {
|
|
110
|
+
to: buyerEmail,
|
|
111
|
+
name: buyerName,
|
|
112
|
+
product: productName,
|
|
113
|
+
value: purchaseValue
|
|
114
|
+
}),
|
|
115
|
+
|
|
116
|
+
// [7] CRM Sync — criar/atualizar contato como comprador
|
|
117
|
+
syncToCRM(env, 'purchase', {
|
|
118
|
+
email: buyerEmail,
|
|
119
|
+
name: buyerName,
|
|
120
|
+
product: productName,
|
|
121
|
+
value: purchaseValue,
|
|
122
|
+
order_id: transactionId
|
|
123
|
+
})
|
|
124
|
+
]));
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
> **Nota:** `sendEmail()` é implementada pelo Email Agent em `cloudflare/email-service.js`.
|
|
128
|
+
> `syncToCRM()` é implementada pelo CRM Integration Agent em `cloudflare/crm-service.js`.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
74
132
|
## INPUTS RECEBIDOS
|
|
75
133
|
|
|
76
134
|
- Payload JSON do webhook da plataforma de vendas (Hotmart, Kiwify, Ticto, Stripe)
|
|
@@ -95,10 +153,10 @@ async function hashWebhookUserData(webhookPayload) {
|
|
|
95
153
|
```json
|
|
96
154
|
{
|
|
97
155
|
"rotas_geradas": {
|
|
98
|
-
"hotmart": "/
|
|
99
|
-
"kiwify": "/
|
|
156
|
+
"hotmart": "/webhook/hotmart",
|
|
157
|
+
"kiwify": "/webhook/kiwify",
|
|
100
158
|
"ticto": "/webhook/ticto",
|
|
101
|
-
"stripe": "/
|
|
159
|
+
"stripe": "/webhook/stripe"
|
|
102
160
|
},
|
|
103
161
|
"validacao_hmac": {
|
|
104
162
|
"hotmart": "X-Hotmart-Hottok",
|
|
@@ -9,8 +9,8 @@ Você é o **Especialista em Mensageria WhatsApp (Quantum Tier)** do CDP Edge. S
|
|
|
9
9
|
1. **Meta Cloud API v22.0 (Eixo Vendas/Notificações ao dono)**:
|
|
10
10
|
- **Público**: O dono do sistema — notificações de Nova Venda e Novo Lead em tempo real.
|
|
11
11
|
- **Objetivo**: Avisar o dono quando chegar uma venda ou lead via webhook.
|
|
12
|
-
- **Padrão**: API oficial Meta v22.0 — `POST /v22.0/{
|
|
13
|
-
- **Secrets**: `
|
|
12
|
+
- **Padrão**: API oficial Meta v22.0 — `POST /v22.0/{WHATSAPP_PHONE_NUMBER_ID}/messages`.
|
|
13
|
+
- **Secrets**: `WHATSAPP_PHONE_NUMBER_ID`, `WHATSAPP_ACCESS_TOKEN`, `WA_NOTIFY_NUMBER`.
|
|
14
14
|
2. **CallMeBot (Eixo Guardião/Alertas de Sistema)**:
|
|
15
15
|
- **Público**: O dono do sistema (admin).
|
|
16
16
|
- **Objetivo**: Alertas internos do Cloudflare — Worker com erro, API falhando, token expirado, D1 com problema. **NÃO usado para mensagens a clientes.**
|
|
@@ -29,11 +29,64 @@ Sempre que o usuário desejar robustez na mensageria:
|
|
|
29
29
|
|
|
30
30
|
---
|
|
31
31
|
|
|
32
|
+
## 🔗 INTEGRAÇÃO COM WHATSAPP CTWA SETUP AGENT
|
|
33
|
+
|
|
34
|
+
O **WhatsApp CTWA Setup Agent** (`whatsapp-ctwa-setup-agent.md`) é o parceiro deste agente para rastreamento de anúncios Click-to-WhatsApp. A divisão de responsabilidades é clara:
|
|
35
|
+
|
|
36
|
+
| Este agente (whatsapp-agent) | CTWA Setup Agent |
|
|
37
|
+
|---|---|
|
|
38
|
+
| Notificações de venda/lead ao dono (Meta API) | Webhook `/webhook/whatsapp` — recebe mensagens da Meta |
|
|
39
|
+
| Alertas de sistema ao admin (CallMeBot) | Extração de `ctwa_clid` e disparo à Meta CAPI |
|
|
40
|
+
| Tracking de clique em botão WhatsApp (browser) | Setup do webhook de verificação (GET + POST) |
|
|
41
|
+
|
|
42
|
+
### Fluxo CTWA → Meta CAPI (implementado pelo CTWA Setup Agent)
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
Usuário clica anúncio CTWA no Facebook/Instagram
|
|
46
|
+
↓
|
|
47
|
+
WhatsApp abre com mensagem pré-preenchida
|
|
48
|
+
↓
|
|
49
|
+
Meta faz POST ao Worker: /webhook/whatsapp
|
|
50
|
+
↓ (CTWA Setup Agent processa)
|
|
51
|
+
Worker extrai ctwa_clid + phone do payload
|
|
52
|
+
↓
|
|
53
|
+
Worker envia evento 'Contact' à Meta CAPI:
|
|
54
|
+
{
|
|
55
|
+
event_name: 'Contact',
|
|
56
|
+
action_source: 'chat', ← obrigatório para CTWA
|
|
57
|
+
event_source_url: ad_source_url,
|
|
58
|
+
user_data: { ph: sha256(phone) },
|
|
59
|
+
custom_data: { ctwa_clid: '...' } ← identifica o anúncio
|
|
60
|
+
}
|
|
61
|
+
↓
|
|
62
|
+
Worker salva no D1: tabela whatsapp_contacts
|
|
63
|
+
↓
|
|
64
|
+
WhatsApp Agent dispara notificação ao dono via Meta Cloud API v22.0
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### O que este agente gera para o fluxo CTWA
|
|
68
|
+
|
|
69
|
+
```javascript
|
|
70
|
+
// sendWhatsApp() — notificação ao dono quando chega lead CTWA
|
|
71
|
+
async function notifyOwnerNewCtwaLead(env, contactData) {
|
|
72
|
+
const message = `📲 Novo Lead CTWA!\n\nNome: ${contactData.name || 'Desconhecido'}\nTelefone: ${contactData.phone}\nAnúncio: ${contactData.headline || '-'}\nMensagem: "${contactData.messageBody?.slice(0, 80) || '-'}"`;
|
|
73
|
+
|
|
74
|
+
await sendWhatsApp(env, 'system', {
|
|
75
|
+
to: env.WA_NOTIFY_NUMBER,
|
|
76
|
+
message
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
> **Regra de coordenação:** O CTWA Setup Agent processa o webhook e extrai ctwa_clid. Após salvar no D1 com `capi_sent = 0`, ele chama `notifyOwnerNewCtwaLead()` deste agente para notificar o dono. Manter esta divisão — nunca misturar as responsabilidades.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
32
85
|
## INPUTS RECEBIDOS
|
|
33
86
|
|
|
34
87
|
- Tipo de disparo: `Purchase` | `Lead` (Meta Cloud API) ou falha de sistema (CallMeBot)
|
|
35
88
|
- Dados do comprador/lead: `name`, `email`, `phone`, `product_name`, `value` (via webhook ou D1)
|
|
36
|
-
- Secrets Meta: `
|
|
89
|
+
- Secrets Meta: `WHATSAPP_PHONE_NUMBER_ID`, `WHATSAPP_ACCESS_TOKEN`, `WA_NOTIFY_NUMBER`
|
|
37
90
|
- Secrets CallMeBot: `CALLMEBOT_PHONE`, `CALLMEBOT_APIKEY`
|
|
38
91
|
- Contexto de erro (para alertas CallMeBot): plataforma afetada, mensagem de erro, timestamp
|
|
39
92
|
|
|
@@ -58,9 +111,9 @@ Sempre que o usuário desejar robustez na mensageria:
|
|
|
58
111
|
"eixos": {
|
|
59
112
|
"notificacoes": {
|
|
60
113
|
"api": "Meta Cloud API v22.0",
|
|
61
|
-
"endpoint": "POST /v22.0/{
|
|
114
|
+
"endpoint": "POST /v22.0/{WHATSAPP_PHONE_NUMBER_ID}/messages",
|
|
62
115
|
"tipos": ["Purchase", "Lead"],
|
|
63
|
-
"secrets": ["
|
|
116
|
+
"secrets": ["WHATSAPP_PHONE_NUMBER_ID", "WHATSAPP_ACCESS_TOKEN", "WA_NOTIFY_NUMBER"]
|
|
64
117
|
},
|
|
65
118
|
"alertas_sistema": {
|
|
66
119
|
"api": "CallMeBot",
|
|
@@ -7,7 +7,7 @@ Sua missão: configurar o rastreamento completo de anúncios Click to WhatsApp d
|
|
|
7
7
|
|
|
8
8
|
## ✅ REGRAS CRÍTICAS
|
|
9
9
|
|
|
10
|
-
0. **CONSULTA OBRIGATÓRIA À MEMÓRIA**: Extraia o ID de Número WhatsApp, Token de API e Token de Verificação (`
|
|
10
|
+
0. **CONSULTA OBRIGATÓRIA À MEMÓRIA**: Extraia o ID de Número WhatsApp, Token de API e Token de Verificação (`WHATSAPP_PHONE_NUMBER_ID`, `WHATSAPP_ACCESS_TOKEN`, `WA_WEBHOOK_VERIFY_TOKEN`) consultando ativamente o "memory-agent.json". Solicite ao Orquestrador tudo o que faltar. Execute configurações de WhatsApp exclusivamente com os dados oficiais guardados na Memória para garantir alinhamento sistêmico.
|
|
11
11
|
1. Cloudflare-Only: Sem dependências externas.
|
|
12
12
|
2. Same-Domain: Worker no domínio do site (anti-adblock).
|
|
13
13
|
|
|
@@ -172,8 +172,8 @@ message_body | Olá, vi o anúncio e tenho interesse
|
|
|
172
172
|
[ ] META_APP_ID — ID do app no Meta for Developers ← usuário fornece
|
|
173
173
|
[ ] META_APP_SECRET — App Secret (developers.facebook.com → Básico) ← usuário fornece
|
|
174
174
|
[ ] WA_WEBHOOK_VERIFY_TOKEN — gerado pelo agente (crypto.randomUUID) ← agente cria
|
|
175
|
-
[ ]
|
|
176
|
-
[ ]
|
|
175
|
+
[ ] WHATSAPP_PHONE_NUMBER_ID — descoberto automaticamente via API ← agente descobre
|
|
176
|
+
[ ] WHATSAPP_ACCESS_TOKEN — mesmo que META_ACCESS_TOKEN ← agente reutiliza
|
|
177
177
|
[ ] WHATSAPP_BUSINESS_ACCOUNT_ID — descoberto automaticamente via API ← agente descobre
|
|
178
178
|
```
|
|
179
179
|
|
|
@@ -498,16 +498,16 @@ Deve mostrar o app com `override_callback_uri` (se não-SMB) ou apenas o app sub
|
|
|
498
498
|
|
|
499
499
|
```bash
|
|
500
500
|
echo "{WABA_ID}" | wrangler secret put WHATSAPP_BUSINESS_ACCOUNT_ID
|
|
501
|
-
echo "{PHONE_ID}" | wrangler secret put
|
|
502
|
-
echo "{META_ACCESS_TOKEN}" | wrangler secret put
|
|
501
|
+
echo "{PHONE_ID}" | wrangler secret put WHATSAPP_PHONE_NUMBER_ID
|
|
502
|
+
echo "{META_ACCESS_TOKEN}" | wrangler secret put WHATSAPP_ACCESS_TOKEN
|
|
503
503
|
echo "{META_APP_SECRET}" | wrangler secret put META_APP_SECRET
|
|
504
504
|
echo "{META_ACCESS_TOKEN}" | wrangler secret put META_ACCESS_TOKEN
|
|
505
505
|
|
|
506
506
|
# ── Secrets para Auto-Resposta WhatsApp (enviar mensagens de saída) ─────────
|
|
507
507
|
# Necessários para: worker.js → auto-resposta após Lead/Purchase
|
|
508
|
-
#
|
|
508
|
+
# WHATSAPP_ACCESS_TOKEN = mesmo token Meta (Cloud API) — pode reutilizar META_ACCESS_TOKEN
|
|
509
509
|
# WHATSAPP_PHONE_NUMBER_ID = mesmo {PHONE_ID} descoberto acima
|
|
510
|
-
echo "{META_ACCESS_TOKEN}" | wrangler secret put
|
|
510
|
+
echo "{META_ACCESS_TOKEN}" | wrangler secret put WHATSAPP_ACCESS_TOKEN
|
|
511
511
|
echo "{PHONE_ID}" | wrangler secret put WHATSAPP_PHONE_NUMBER_ID
|
|
512
512
|
|
|
513
513
|
# Confirmar todos
|
|
@@ -518,15 +518,15 @@ Secrets esperados no worker ao final:
|
|
|
518
518
|
```
|
|
519
519
|
META_ACCESS_TOKEN
|
|
520
520
|
META_APP_SECRET
|
|
521
|
-
|
|
522
|
-
|
|
521
|
+
WHATSAPP_ACCESS_TOKEN
|
|
522
|
+
WHATSAPP_PHONE_NUMBER_ID
|
|
523
523
|
WA_WEBHOOK_VERIFY_TOKEN
|
|
524
524
|
WHATSAPP_BUSINESS_ACCOUNT_ID
|
|
525
|
-
|
|
526
|
-
WHATSAPP_PHONE_NUMBER_ID ← auto-resposta outbound (mesmo valor de
|
|
525
|
+
WHATSAPP_ACCESS_TOKEN ← auto-resposta outbound (mesmo valor de META_ACCESS_TOKEN)
|
|
526
|
+
WHATSAPP_PHONE_NUMBER_ID ← auto-resposta outbound (mesmo valor de WHATSAPP_PHONE_NUMBER_ID)
|
|
527
527
|
```
|
|
528
528
|
|
|
529
|
-
> **Nota:** `
|
|
529
|
+
> **Nota:** `WHATSAPP_ACCESS_TOKEN` e `WHATSAPP_PHONE_NUMBER_ID` são usados pela função de auto-resposta no worker (envio de mensagens de saída para o lead após eventos de conversão). São o mesmo token/phone_id da CTWA — apenas referenciados por nomes distintos no código.
|
|
530
530
|
|
|
531
531
|
---
|
|
532
532
|
|
|
@@ -611,13 +611,13 @@ App: {META_APP_ID} — subscriptions: messages ✅
|
|
|
611
611
|
|
|
612
612
|
SECRETS NO WORKER:
|
|
613
613
|
✅ META_ACCESS_TOKEN
|
|
614
|
-
✅
|
|
615
|
-
✅
|
|
614
|
+
✅ WHATSAPP_ACCESS_TOKEN
|
|
615
|
+
✅ WHATSAPP_PHONE_NUMBER_ID → {PHONE_ID}
|
|
616
616
|
✅ WA_WEBHOOK_VERIFY_TOKEN
|
|
617
617
|
✅ WHATSAPP_BUSINESS_ACCOUNT_ID → {WABA_ID}
|
|
618
618
|
✅ META_APP_SECRET
|
|
619
|
-
✅
|
|
620
|
-
✅ WHATSAPP_PHONE_NUMBER_ID → mesmo valor de
|
|
619
|
+
✅ WHATSAPP_ACCESS_TOKEN → mesmo valor de META_ACCESS_TOKEN (auto-resposta)
|
|
620
|
+
✅ WHATSAPP_PHONE_NUMBER_ID → mesmo valor de WHATSAPP_PHONE_NUMBER_ID (auto-resposta)
|
|
621
621
|
|
|
622
622
|
TESTE E2E:
|
|
623
623
|
✅ GET verificação → challenge retornado
|
|
@@ -53,7 +53,7 @@ if (consentModeVersion !== 'v2') {
|
|
|
53
53
|
|
|
54
54
|
### TrueView In-Stream (pulável após 5s)
|
|
55
55
|
- **Evento de billing**: view confirmada após 30s ou clique
|
|
56
|
-
- **Rastrear**: `video_start`, `
|
|
56
|
+
- **Rastrear**: `video_start`, `video_complete`, clique no CTA
|
|
57
57
|
- **Conversão Google Ads**: `engaged_view` (30s assistidos = 1 conversão de vídeo)
|
|
58
58
|
|
|
59
59
|
### Bumper Ads (6s não-puláveis)
|
|
@@ -89,39 +89,152 @@ const _gbraid = _urlParams.get('gbraid') || ''; // App campaigns (privacy)
|
|
|
89
89
|
**ATENÇÃO wbraid/gbraid**: São os click IDs para campanhas YouTube em iOS (pós ATT).
|
|
90
90
|
Nunca hashear — enviar como texto plano para Google Ads API.
|
|
91
91
|
|
|
92
|
-
### 2. Rastreamento de vídeo YouTube na página
|
|
92
|
+
### 2. Rastreamento de vídeo YouTube na página — YouTube IFrame API
|
|
93
93
|
|
|
94
|
-
O `behavior-engine.js` já implementa rastreamento de vídeos via YouTube IFrame API.
|
|
95
94
|
Para usar, o iframe deve ter `enablejsapi=1`:
|
|
96
95
|
|
|
97
96
|
```html
|
|
98
|
-
<!-- Embed YouTube com JS API habilitada -->
|
|
97
|
+
<!-- Embed YouTube com JS API habilitada (obrigatório) -->
|
|
99
98
|
<iframe
|
|
100
99
|
id="video-tour-imovel"
|
|
101
|
-
src="https://www.youtube.com/embed/VIDEO_ID?enablejsapi=1"
|
|
100
|
+
src="https://www.youtube.com/embed/VIDEO_ID?enablejsapi=1&origin=https://seudominio.com.br"
|
|
102
101
|
allow="autoplay"
|
|
103
102
|
></iframe>
|
|
104
103
|
```
|
|
105
104
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
105
|
+
#### Implementação real do YouTube IFrame API listener
|
|
106
|
+
|
|
107
|
+
```javascript
|
|
108
|
+
/**
|
|
109
|
+
* YouTube IFrame API Listener — injeta no behavior-engine.js ou tracking.js
|
|
110
|
+
* Rastreia: video_start, video_25, video_50, video_75, video_complete
|
|
111
|
+
* Dispara via cdpTrack.track() para o Worker → GA4 MP + demais plataformas
|
|
112
|
+
*/
|
|
113
|
+
|
|
114
|
+
// Carregar YouTube IFrame API (uma vez por página)
|
|
115
|
+
(function initYouTubeTracking() {
|
|
116
|
+
if (window._ytTrackingInitialized) return;
|
|
117
|
+
window._ytTrackingInitialized = true;
|
|
118
|
+
|
|
119
|
+
// Mapa de iframes já trackeados
|
|
120
|
+
const trackedPlayers = new Map();
|
|
121
|
+
|
|
122
|
+
// Injetar API script do YouTube (não carrega 2x se já existe)
|
|
123
|
+
if (!document.getElementById('youtube-iframe-api')) {
|
|
124
|
+
const tag = document.createElement('script');
|
|
125
|
+
tag.id = 'youtube-iframe-api';
|
|
126
|
+
tag.src = 'https://www.youtube.com/iframe_api';
|
|
127
|
+
document.head.appendChild(tag);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Callback global chamado pelo YouTube quando API estiver pronta
|
|
131
|
+
window.onYouTubeIframeAPIReady = function() {
|
|
132
|
+
// Auto-detectar todos os iframes com enablejsapi=1
|
|
133
|
+
document.querySelectorAll('iframe[src*="youtube.com/embed"]').forEach(iframe => {
|
|
134
|
+
if (trackedPlayers.has(iframe.id)) return;
|
|
135
|
+
|
|
136
|
+
const videoTitle = iframe.title || iframe.id || 'YouTube Video';
|
|
137
|
+
|
|
138
|
+
const player = new YT.Player(iframe.id, {
|
|
139
|
+
events: {
|
|
140
|
+
onStateChange: (event) => handlePlayerStateChange(event, player, videoTitle),
|
|
141
|
+
onReady: (event) => handlePlayerReady(event, player, videoTitle)
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
trackedPlayers.set(iframe.id, { player, milestone: new Set() });
|
|
146
|
+
});
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// Se API já carregada (SPA reload), inicializar diretamente
|
|
150
|
+
if (typeof YT !== 'undefined' && YT.Player) {
|
|
151
|
+
window.onYouTubeIframeAPIReady();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function handlePlayerReady(event, player, videoTitle) {
|
|
155
|
+
// Iniciar polling de progresso
|
|
156
|
+
const iframeId = player.getIframe().id;
|
|
157
|
+
const state = trackedPlayers.get(iframeId);
|
|
158
|
+
|
|
159
|
+
const interval = setInterval(() => {
|
|
160
|
+
if (!player.getDuration) return;
|
|
161
|
+
const duration = player.getDuration();
|
|
162
|
+
const current = player.getCurrentTime();
|
|
163
|
+
if (duration <= 0) return;
|
|
164
|
+
|
|
165
|
+
const percent = Math.floor((current / duration) * 100);
|
|
166
|
+
|
|
167
|
+
// Disparar milestones: 25, 50, 75 (100% é coberto pelo estado ENDED)
|
|
168
|
+
const milestoneEvents = { 25: 'video_25', 50: 'video_50', 75: 'video_75' };
|
|
169
|
+
[25, 50, 75].forEach(milestone => {
|
|
170
|
+
if (percent >= milestone && !state.milestone.has(milestone)) {
|
|
171
|
+
state.milestone.add(milestone);
|
|
172
|
+
|
|
173
|
+
window.cdpTrack?.track(milestoneEvents[milestone], {
|
|
174
|
+
content_name: videoTitle,
|
|
175
|
+
video_percent: milestone,
|
|
176
|
+
video_duration: Math.round(duration),
|
|
177
|
+
video_provider: 'youtube',
|
|
178
|
+
value: 0,
|
|
179
|
+
currency: 'BRL'
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}, 1000); // checar a cada 1s
|
|
184
|
+
|
|
185
|
+
state.progressInterval = interval;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function handlePlayerStateChange(event, player, videoTitle) {
|
|
189
|
+
const iframeId = player.getIframe().id;
|
|
190
|
+
const state = trackedPlayers.get(iframeId);
|
|
191
|
+
|
|
192
|
+
// YT.PlayerState: PLAYING=1, PAUSED=2, ENDED=0, BUFFERING=3
|
|
193
|
+
switch (event.data) {
|
|
194
|
+
case YT.PlayerState.PLAYING:
|
|
195
|
+
if (!state.started) {
|
|
196
|
+
state.started = true;
|
|
197
|
+
window.cdpTrack?.track('video_start', {
|
|
198
|
+
content_name: videoTitle,
|
|
199
|
+
video_duration: Math.round(player.getDuration() || 0),
|
|
200
|
+
video_provider: 'youtube'
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
break;
|
|
204
|
+
|
|
205
|
+
case YT.PlayerState.ENDED:
|
|
206
|
+
clearInterval(state.progressInterval);
|
|
207
|
+
window.cdpTrack?.track('video_complete', {
|
|
208
|
+
content_name: videoTitle,
|
|
209
|
+
video_duration: Math.round(player.getDuration() || 0),
|
|
210
|
+
video_provider: 'youtube'
|
|
211
|
+
});
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
})();
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
O listener dispara via `cdpTrack.track()`:
|
|
219
|
+
- `video_start` — primeiros 2s de play
|
|
220
|
+
- `video_25`, `video_50`, `video_75` — marcos de progresso (25/50/75%)
|
|
221
|
+
- `video_complete` — 100% assistido
|
|
109
222
|
|
|
110
223
|
### 3. Evento de Lead após assistir vídeo (imóveis)
|
|
111
224
|
|
|
112
225
|
```javascript
|
|
113
226
|
// Disparar Lead qualificado quando usuário assiste 75%+ do tour
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
}
|
|
227
|
+
// Adicionar dentro do callback de milestoneEvents no IFrame API listener:
|
|
228
|
+
// milestoneEvents[75] → 'video_75' — adicionar lógica abaixo no bloco forEach
|
|
229
|
+
if (milestone === 75) {
|
|
230
|
+
window.cdpTrack?.track('InitiateCheckout', {
|
|
231
|
+
content_name: 'Tour_Virtual_Empreendimento',
|
|
232
|
+
value: 0,
|
|
233
|
+
currency: 'BRL',
|
|
234
|
+
// Sinaliza alta intenção para Meta + Google
|
|
235
|
+
meta_intensity: 'high',
|
|
236
|
+
});
|
|
237
|
+
}
|
|
125
238
|
```
|
|
126
239
|
|
|
127
240
|
### 4. Consent Mode v2 — OBRIGATÓRIO para YouTube/Google Ads
|
|
@@ -161,9 +274,11 @@ Para verificar persistência correta:
|
|
|
161
274
|
```javascript
|
|
162
275
|
// No sendGA4Mp() — adicionar mapeamento de eventos YouTube
|
|
163
276
|
const VIDEO_GA4_MAP = {
|
|
164
|
-
video_start:
|
|
165
|
-
|
|
166
|
-
|
|
277
|
+
video_start: 'video_start',
|
|
278
|
+
video_25: 'video_progress', // GA4 usa video_progress com percent
|
|
279
|
+
video_50: 'video_progress',
|
|
280
|
+
video_75: 'video_progress',
|
|
281
|
+
video_complete: 'video_complete',
|
|
167
282
|
};
|
|
168
283
|
|
|
169
284
|
// Params obrigatórios para video_progress (GA4)
|
|
@@ -228,9 +343,9 @@ if (!payload.gclid && payload.utmSource === 'youtube') {
|
|
|
228
343
|
| Evento cdpTrack | Mapeamento GA4 | Mapeamento Google Ads | Quando Disparar |
|
|
229
344
|
|---|---|---|---|
|
|
230
345
|
| `video_start` | `video_start` | — | Primeiros 2s de reprodução |
|
|
231
|
-
| `
|
|
232
|
-
| `
|
|
233
|
-
| `
|
|
346
|
+
| `video_25` | `video_progress` | — | 25% assistido |
|
|
347
|
+
| `video_50` | `video_progress` | `engaged_view` candidate | 50% assistido |
|
|
348
|
+
| `video_75` | `video_progress` | `engaged_view` | 75% assistido — alta intenção |
|
|
234
349
|
| `video_complete` | `video_complete` | `video_view_complete` | 100% assistido |
|
|
235
350
|
| `Lead` (após vídeo) | `generate_lead` | Conversão primária | Formulário submetido |
|
|
236
351
|
| `InitiateCheckout` | `begin_checkout` | Conversão micro | Clique em "Quero saber mais" |
|
|
@@ -244,7 +359,7 @@ if (!payload.gclid && payload.utmSource === 'youtube') {
|
|
|
244
359
|
```
|
|
245
360
|
FASE 1: AWARENESS (YouTube TrueView 30s)
|
|
246
361
|
↓ Tour aéreo do empreendimento / lifestyle do bairro
|
|
247
|
-
↓ Rastrear:
|
|
362
|
+
↓ Rastrear: video_50 + video_75 → score alto no LTV
|
|
248
363
|
↓ Remarketing: quem assistiu 50%+ vira audiência no Google Ads
|
|
249
364
|
|
|
250
365
|
FASE 2: CONSIDERAÇÃO (YouTube Non-skip 15s + Display)
|
|
@@ -390,7 +505,7 @@ if (youtubeMobileLeads.count === 0) {
|
|
|
390
505
|
|
|
391
506
|
## RESPONSABILIDADE
|
|
392
507
|
|
|
393
|
-
- Gerar eventos de progresso de vídeo (`video_start`, `
|
|
508
|
+
- Gerar eventos de progresso de vídeo (`video_start`, `video_25`, `video_50`, `video_75`, `video_complete`) via GA4
|
|
394
509
|
- Implementar YouTube IFrame API listener para rastreamento de VSL no browser
|
|
395
510
|
- Garantir `gclid`, `wbraid`, `gbraid` chegando ao Worker (nunca hashear estes campos)
|
|
396
511
|
- Persistir `ga_client_id` no D1 para cruzamento com conversões YouTube Ads
|
|
@@ -407,7 +522,7 @@ if (youtubeMobileLeads.count === 0) {
|
|
|
407
522
|
},
|
|
408
523
|
"eventos_implementados": [
|
|
409
524
|
"video_start",
|
|
410
|
-
"
|
|
525
|
+
"video_25", "video_50", "video_75",
|
|
411
526
|
"video_complete",
|
|
412
527
|
"generate_lead",
|
|
413
528
|
"purchase"
|
|
@@ -212,10 +212,14 @@
|
|
|
212
212
|
]
|
|
213
213
|
},
|
|
214
214
|
"conversions_api": {
|
|
215
|
-
"current": "
|
|
216
|
-
"minimum_supported": "
|
|
217
|
-
"recommended": "
|
|
218
|
-
"endpoint_pattern": "https://api.linkedin.com/rest/
|
|
215
|
+
"current": "202401",
|
|
216
|
+
"minimum_supported": "202401",
|
|
217
|
+
"recommended": "202401",
|
|
218
|
+
"endpoint_pattern": "https://api.linkedin.com/rest/conversionEvents",
|
|
219
|
+
"required_headers": {
|
|
220
|
+
"LinkedIn-Version": "202401",
|
|
221
|
+
"X-Restli-Protocol-Version": "2.0.0"
|
|
222
|
+
},
|
|
219
223
|
"authentication": "Bearer token (LINKEDIN_ACCESS_TOKEN)",
|
|
220
224
|
"rate_limits": {
|
|
221
225
|
"requests_per_second": 10,
|
|
@@ -359,10 +363,10 @@
|
|
|
359
363
|
},
|
|
360
364
|
|
|
361
365
|
"last_updated_by": {
|
|
362
|
-
"agent": "
|
|
363
|
-
"session_id": "CDP_2026-
|
|
364
|
-
"timestamp": "2026-
|
|
366
|
+
"agent": "Audit — CDP Edge v2.0",
|
|
367
|
+
"session_id": "CDP_2026-04-10_audit",
|
|
368
|
+
"timestamp": "2026-04-10T00:00:00.000Z"
|
|
365
369
|
},
|
|
366
370
|
|
|
367
|
-
"next_review_date": "2026-
|
|
371
|
+
"next_review_date": "2026-05-10T00:00:00.000Z"
|
|
368
372
|
}
|