cdp-edge 1.2.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 +367 -0
- package/bin/cdp-edge.js +61 -0
- package/contracts/api-versions.json +368 -0
- package/dist/commands/analyze.js +52 -0
- package/dist/commands/infra.js +54 -0
- package/dist/commands/install.js +168 -0
- package/dist/commands/server.js +174 -0
- package/dist/commands/setup.js +123 -0
- package/dist/commands/validate.js +84 -0
- package/dist/index.js +12 -0
- package/docs/CI-CD-SETUP.md +217 -0
- package/docs/PixelBuilder-Documentacao-Completa (2).docx +0 -0
- package/docs/events-reference.md +359 -0
- package/docs/installation.md +155 -0
- package/docs/quick-start.md +185 -0
- package/docs/sdk-reference.md +371 -0
- package/docs/whatsapp-ctwa.md +209 -0
- package/extracted-skill/tracking-events-generator/INDEX.md +94 -0
- package/extracted-skill/tracking-events-generator/INSTALACAO-CDPEDGE.md +58 -0
- package/extracted-skill/tracking-events-generator/INTEGRACAO-COMPLETA.md +594 -0
- package/extracted-skill/tracking-events-generator/MELHORIAS-IMPLEMENTADAS.md +412 -0
- package/extracted-skill/tracking-events-generator/Premium-Tracking-Intelligence-Resumo.md +333 -0
- package/extracted-skill/tracking-events-generator/SKILL.md +257 -0
- package/extracted-skill/tracking-events-generator/advanced-matching.js +364 -0
- package/extracted-skill/tracking-events-generator/agents/ab-testing-agent.md +54 -0
- package/extracted-skill/tracking-events-generator/agents/attribution-agent.md +1304 -0
- package/extracted-skill/tracking-events-generator/agents/bing-agent.md +76 -0
- package/extracted-skill/tracking-events-generator/agents/browser-tracking.md +264 -0
- package/extracted-skill/tracking-events-generator/agents/code-guardian-agent.md +149 -0
- package/extracted-skill/tracking-events-generator/agents/compliance-agent.md +2077 -0
- package/extracted-skill/tracking-events-generator/agents/crm-integration-agent.md +1419 -0
- package/extracted-skill/tracking-events-generator/agents/dashboard-agent.md +456 -0
- package/extracted-skill/tracking-events-generator/agents/database-agent.md +667 -0
- package/extracted-skill/tracking-events-generator/agents/debug-agent.md +1455 -0
- package/extracted-skill/tracking-events-generator/agents/domain-setup-agent.md +224 -0
- package/extracted-skill/tracking-events-generator/agents/email-agent.md +61 -0
- package/extracted-skill/tracking-events-generator/agents/fingerprint-agent.md +52 -0
- package/extracted-skill/tracking-events-generator/agents/google-agent.md +109 -0
- package/extracted-skill/tracking-events-generator/agents/intelligence-agent.md +365 -0
- package/extracted-skill/tracking-events-generator/agents/intelligence-scheduling.md +643 -0
- package/extracted-skill/tracking-events-generator/agents/linkedin-agent.md +62 -0
- package/extracted-skill/tracking-events-generator/agents/localization-agent.md +55 -0
- package/extracted-skill/tracking-events-generator/agents/ltv-predictor-agent.md +59 -0
- package/extracted-skill/tracking-events-generator/agents/master-feedback-loop.md +900 -0
- package/extracted-skill/tracking-events-generator/agents/master-orchestrator.md +1922 -0
- package/extracted-skill/tracking-events-generator/agents/memory-agent.json +109 -0
- package/extracted-skill/tracking-events-generator/agents/memory-agent.md +703 -0
- package/extracted-skill/tracking-events-generator/agents/meta-agent.md +110 -0
- package/extracted-skill/tracking-events-generator/agents/page-analyzer.md +255 -0
- package/extracted-skill/tracking-events-generator/agents/performance-agent.md +1157 -0
- package/extracted-skill/tracking-events-generator/agents/performance-optimization-agent.md +1432 -0
- package/extracted-skill/tracking-events-generator/agents/pinterest-agent.md +310 -0
- package/extracted-skill/tracking-events-generator/agents/premium-tracking-intelligence-agent.md +849 -0
- package/extracted-skill/tracking-events-generator/agents/r2-setup-agent.md +250 -0
- package/extracted-skill/tracking-events-generator/agents/reddit-agent.md +313 -0
- package/extracted-skill/tracking-events-generator/agents/security-enterprise-agent.md +1752 -0
- package/extracted-skill/tracking-events-generator/agents/server-tracking.md +1188 -0
- package/extracted-skill/tracking-events-generator/agents/spotify-agent.md +383 -0
- package/extracted-skill/tracking-events-generator/agents/tiktok-agent.md +111 -0
- package/extracted-skill/tracking-events-generator/agents/tracking-plan-agent.md +364 -0
- package/extracted-skill/tracking-events-generator/agents/validator-agent.md +267 -0
- package/extracted-skill/tracking-events-generator/agents/webhook-agent.md +69 -0
- package/extracted-skill/tracking-events-generator/agents/whatsapp-agent.md +76 -0
- package/extracted-skill/tracking-events-generator/agents/whatsapp-ctwa-setup-agent.md +699 -0
- package/extracted-skill/tracking-events-generator/agents/youtube-agent.md +422 -0
- package/extracted-skill/tracking-events-generator/anti-blocking.js +285 -0
- package/extracted-skill/tracking-events-generator/cdpTrack.js +641 -0
- package/extracted-skill/tracking-events-generator/contracts/api-versions.json +368 -0
- package/extracted-skill/tracking-events-generator/docs/guia-cloudflare-iniciante.md +107 -0
- package/extracted-skill/tracking-events-generator/engagement-scoring.js +226 -0
- package/extracted-skill/tracking-events-generator/evals/evals.json +235 -0
- package/extracted-skill/tracking-events-generator/integration-test.js +497 -0
- package/extracted-skill/tracking-events-generator/knowledge-base.md +2894 -0
- package/extracted-skill/tracking-events-generator/micro-events.js +992 -0
- package/extracted-skill/tracking-events-generator/models/captura-de-lead.md +78 -0
- package/extracted-skill/tracking-events-generator/models/captura-lead-evento-externo.md +99 -0
- package/extracted-skill/tracking-events-generator/models/checkout-proprio.md +111 -0
- package/extracted-skill/tracking-events-generator/models/multi-step-checkout.md +672 -0
- package/extracted-skill/tracking-events-generator/models/pagina-obrigado.md +55 -0
- package/extracted-skill/tracking-events-generator/models/pinterest/conversions-api-template.js +144 -0
- package/extracted-skill/tracking-events-generator/models/pinterest/event-mappings.json +48 -0
- package/extracted-skill/tracking-events-generator/models/pinterest/tag-template.js +28 -0
- package/extracted-skill/tracking-events-generator/models/quiz-funnel.md +68 -0
- package/extracted-skill/tracking-events-generator/models/reddit/conversions-api-template.js +205 -0
- package/extracted-skill/tracking-events-generator/models/reddit/event-mappings.json +56 -0
- package/extracted-skill/tracking-events-generator/models/reddit/pixel-template.js +19 -0
- package/extracted-skill/tracking-events-generator/models/scenarios/behavior-engine.js +425 -0
- package/extracted-skill/tracking-events-generator/models/scenarios/real-estate-logic.md +50 -0
- package/extracted-skill/tracking-events-generator/models/scenarios/sales-page-logic.md +50 -0
- package/extracted-skill/tracking-events-generator/models/trafego-direto.md +582 -0
- package/extracted-skill/tracking-events-generator/models/webinar-registration.md +63 -0
- package/extracted-skill/tracking-events-generator/tracking.config.js +46 -0
- package/extracted-skill/tracking-events-generator/walkthrough.md +26 -0
- package/package.json +75 -0
- package/server-edge-tracker/INSTALAR.md +328 -0
- package/server-edge-tracker/migrate-new-db.sql +137 -0
- package/server-edge-tracker/migrate-v2.sql +16 -0
- package/server-edge-tracker/migrate-v3.sql +6 -0
- package/server-edge-tracker/migrate-v4.sql +18 -0
- package/server-edge-tracker/migrate-v5.sql +17 -0
- package/server-edge-tracker/migrate-v6.sql +24 -0
- package/server-edge-tracker/migrate.sql +111 -0
- package/server-edge-tracker/schema.sql +265 -0
- package/server-edge-tracker/worker.js +2574 -0
- package/server-edge-tracker/wrangler.toml +85 -0
- package/templates/afiliado-sem-landing.md +312 -0
- package/templates/captura-de-lead.md +78 -0
- package/templates/captura-lead-evento-externo.md +99 -0
- package/templates/checkout-proprio.md +111 -0
- package/templates/install/.claude/commands/cdp.md +1 -0
- package/templates/install/CLAUDE.md +65 -0
- package/templates/linkedin/tag-template.js +46 -0
- package/templates/multi-step-checkout.md +673 -0
- package/templates/pagina-obrigado.md +55 -0
- package/templates/pinterest/conversions-api-template.js +144 -0
- package/templates/pinterest/event-mappings.json +48 -0
- package/templates/pinterest/tag-template.js +28 -0
- package/templates/quiz-funnel.md +68 -0
- package/templates/reddit/conversions-api-template.js +205 -0
- package/templates/reddit/event-mappings.json +56 -0
- package/templates/reddit/pixel-template.js +46 -0
- package/templates/scenarios/behavior-engine.js +402 -0
- package/templates/scenarios/real-estate-logic.md +50 -0
- package/templates/scenarios/sales-page-logic.md +50 -0
- package/templates/spotify/pixel-template.js +46 -0
- package/templates/trafego-direto.md +582 -0
- package/templates/vsl-page.md +292 -0
- package/templates/webinar-registration.md +63 -0
|
@@ -0,0 +1,1188 @@
|
|
|
1
|
+
# Agente: Server Tracking (Cloudflare Architect) — CDP Edge
|
|
2
|
+
|
|
3
|
+
Você é o Arquiteto Cloudflare do CDP Edge. Sua única responsabilidade é projetar e gerar toda a infraestrutura server-side nativa da Cloudflare.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🌩️ ENTREGÁVEIS OBRIGATÓRIOS
|
|
8
|
+
|
|
9
|
+
Ao ser ativado, você sempre gera os seguintes arquivos:
|
|
10
|
+
|
|
11
|
+
| Arquivo | Descrição |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `wrangler.toml` | Configuração completa do Worker com todos os bindings |
|
|
14
|
+
| `schema.sql` | Schema D1 completo: eventos, identity_graph, leads, behavioral_events |
|
|
15
|
+
| `worker.js` | O Worker principal com lógica de processamento e engagement scoring server-side |
|
|
16
|
+
| `DEPLOY.md` | Guia passo a passo de deploy do zero ao funcionando |
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 🏗️ ARQUITETURA CLOUDFLARE (STACK COMPLETA)
|
|
21
|
+
|
|
22
|
+
### Camadas da Infraestrutura
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
Browser (Visitante)
|
|
26
|
+
│
|
|
27
|
+
▼
|
|
28
|
+
Cloudflare Edge (Worker)
|
|
29
|
+
├── Route Principal: /api/* ← Same-Domain Protocol
|
|
30
|
+
├── Route Webhook: /api/wh/* ← Rota para Gateways de Pagamento
|
|
31
|
+
├── Edge Routing (A/B) ← Interceptação via A/B Testing Agent
|
|
32
|
+
├── Edge Localization ← Manipulação de Checkout/Moeda
|
|
33
|
+
├── ML LTV Prediction ← Predição de Valor via Workers AI
|
|
34
|
+
├── Fingerprinting Engine ← Resgate de Atribuição UTM
|
|
35
|
+
├── Messaging Engine ← Disparos WhatsApp e Email (Resend)
|
|
36
|
+
├── D1 Database (SQLite) ← Persistência de eventos e Identity Graph
|
|
37
|
+
├── R2 Bucket ← Logs auditáveis
|
|
38
|
+
├── Queues ← Fila de retry
|
|
39
|
+
├── KV Namespace ← Cache de geo/ip e sessão
|
|
40
|
+
└── Cron Triggers ← Limpeza de dados + Reporte Financeiro
|
|
41
|
+
│
|
|
42
|
+
├──▶ Meta CAPI v22.0 (sendMetaCapi)
|
|
43
|
+
├──▶ Google GA4 MP (sendGA4Mp)
|
|
44
|
+
├──▶ TikTok Events API v1.3 (sendTikTokApi)
|
|
45
|
+
├──▶ Pinterest CAPI v5 (sendPinterestCapi — template, ativar via secret)
|
|
46
|
+
├──▶ Reddit CAPI v2.0 (sendRedditCapi — template, ativar via secret)
|
|
47
|
+
├──▶ LinkedIn CAPI v2 (sendLinkedInCapi — template, ativar via secret)
|
|
48
|
+
├──▶ Spotify CAPI v1 (sendSpotifyCapi — template, ativar via secret)
|
|
49
|
+
├──▶ WhatsApp CTWA (processWhatsAppWebhook — POST /webhook/whatsapp)
|
|
50
|
+
└──▶ WhatsApp Notificações (sendWhatsApp — notifica dono em Lead/Purchase)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 📄 WRANGLER.TOML (TEMPLATE)
|
|
56
|
+
|
|
57
|
+
```toml
|
|
58
|
+
name = "cdp-edge-worker"
|
|
59
|
+
main = "worker.js"
|
|
60
|
+
compatibility_date = "2025-01-01"
|
|
61
|
+
compatibility_flags = ["nodejs_compat"]
|
|
62
|
+
|
|
63
|
+
[[d1_databases]]
|
|
64
|
+
binding = "DB"
|
|
65
|
+
database_name = "cdp-edge-db"
|
|
66
|
+
database_id = "SUBSTITUIR_PELO_ID_GERADO"
|
|
67
|
+
|
|
68
|
+
[[kv_namespaces]]
|
|
69
|
+
binding = "GEO_CACHE"
|
|
70
|
+
id = "SUBSTITUIR_PELO_ID_GERADO"
|
|
71
|
+
|
|
72
|
+
[[r2_buckets]]
|
|
73
|
+
binding = "LOGS"
|
|
74
|
+
bucket_name = "cdp-edge-logs"
|
|
75
|
+
|
|
76
|
+
[[queues.producers]]
|
|
77
|
+
binding = "RETRY_QUEUE"
|
|
78
|
+
queue = "cdp-edge-retry"
|
|
79
|
+
|
|
80
|
+
[[queues.consumers]]
|
|
81
|
+
queue = "cdp-edge-retry"
|
|
82
|
+
max_batch_size = 10
|
|
83
|
+
max_batch_timeout = 30
|
|
84
|
+
|
|
85
|
+
[vars]
|
|
86
|
+
UMBRELLA_DOMAIN = "dominio.com"
|
|
87
|
+
|
|
88
|
+
# SECRETS (via wrangler secret put):
|
|
89
|
+
# META_ACCESS_TOKEN ← obrigatório
|
|
90
|
+
# GA4_API_SECRET ← obrigatório
|
|
91
|
+
# TIKTOK_ACCESS_TOKEN ← opcional
|
|
92
|
+
# WA_ACCESS_TOKEN ← WhatsApp notificações ao dono
|
|
93
|
+
# WA_PHONE_ID ← WhatsApp notificações ao dono
|
|
94
|
+
# WHATSAPP_TOKEN ← WhatsApp Cloud API (CTWA webhook)
|
|
95
|
+
# WHATSAPP_PHONE_NUMBER_ID ← WhatsApp Cloud API (CTWA webhook)
|
|
96
|
+
# WA_WEBHOOK_VERIFY_TOKEN ← gerado pelo agente (crypto.randomUUID)
|
|
97
|
+
# PINTEREST_ACCESS_TOKEN ← ativar Pinterest CAPI v5
|
|
98
|
+
# PINTEREST_AD_ACCOUNT_ID ← ativar Pinterest CAPI v5
|
|
99
|
+
# REDDIT_ACCESS_TOKEN ← ativar Reddit CAPI v2.0
|
|
100
|
+
# REDDIT_AD_ACCOUNT_ID ← ativar Reddit CAPI v2.0
|
|
101
|
+
# LINKEDIN_ACCESS_TOKEN ← ativar LinkedIn CAPI v2
|
|
102
|
+
# LINKEDIN_CONVERSION_ID ← ativar LinkedIn CAPI v2
|
|
103
|
+
# LINKEDIN_AD_ACCOUNT_ID ← ativar LinkedIn CAPI v2
|
|
104
|
+
# SPOTIFY_ACCESS_TOKEN ← ativar Spotify CAPI v1
|
|
105
|
+
# SPOTIFY_AD_ACCOUNT_ID ← ativar Spotify CAPI v1
|
|
106
|
+
# RESEND_API_KEY ← email transacional
|
|
107
|
+
# META_AD_ACCOUNT_ID ← Customer Match
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 📄 SCHEMA.SQL (D1)
|
|
113
|
+
|
|
114
|
+
```sql
|
|
115
|
+
-- TABELA DE EVENTOS (Auditoria Completa)
|
|
116
|
+
CREATE TABLE IF NOT EXISTS events_log (
|
|
117
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
118
|
+
event_id TEXT UNIQUE NOT NULL,
|
|
119
|
+
event_name TEXT NOT NULL,
|
|
120
|
+
platform TEXT,
|
|
121
|
+
session_id TEXT,
|
|
122
|
+
heat_score INTEGER DEFAULT 0,
|
|
123
|
+
user_data TEXT, -- JSON hasheado (SHA-256)
|
|
124
|
+
page_url TEXT,
|
|
125
|
+
utm_source TEXT,
|
|
126
|
+
utm_medium TEXT,
|
|
127
|
+
utm_campaign TEXT,
|
|
128
|
+
status TEXT DEFAULT 'pending',
|
|
129
|
+
error_msg TEXT,
|
|
130
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
-- IDENTITY GRAPH (Cross-Device Recognition)
|
|
134
|
+
CREATE TABLE IF NOT EXISTS identity_graph (
|
|
135
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
136
|
+
fingerprint TEXT UNIQUE,
|
|
137
|
+
fbp TEXT,
|
|
138
|
+
fbc TEXT,
|
|
139
|
+
ga_client_id TEXT,
|
|
140
|
+
external_id TEXT,
|
|
141
|
+
ttclid TEXT,
|
|
142
|
+
first_utm TEXT,
|
|
143
|
+
heat_score_avg INTEGER DEFAULT 0,
|
|
144
|
+
visit_count INTEGER DEFAULT 1,
|
|
145
|
+
last_seen TEXT DEFAULT (datetime('now')),
|
|
146
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
-- LEADS CAPTURADOS
|
|
150
|
+
CREATE TABLE IF NOT EXISTS leads (
|
|
151
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
152
|
+
graph_id INTEGER REFERENCES identity_graph(id),
|
|
153
|
+
name TEXT,
|
|
154
|
+
email_hash TEXT,
|
|
155
|
+
phone_hash TEXT,
|
|
156
|
+
source TEXT,
|
|
157
|
+
campaign TEXT,
|
|
158
|
+
heat_score INTEGER DEFAULT 0,
|
|
159
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
-- ÍNDICES DE PERFORMANCE
|
|
163
|
+
CREATE INDEX IF NOT EXISTS idx_identity_fp ON identity_graph(fingerprint);
|
|
164
|
+
CREATE INDEX IF NOT EXISTS idx_identity_ext ON identity_graph(external_id);
|
|
165
|
+
CREATE INDEX IF NOT EXISTS idx_events_id ON events_log(event_id);
|
|
166
|
+
CREATE INDEX IF NOT EXISTS idx_events_created ON events_log(created_at);
|
|
167
|
+
|
|
168
|
+
-- BEHAVIORAL DATA (Engagement Scoring)
|
|
169
|
+
CREATE TABLE IF NOT EXISTS behavioral_events (
|
|
170
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
171
|
+
event_id TEXT NOT NULL UNIQUE,
|
|
172
|
+
user_id TEXT,
|
|
173
|
+
session_id TEXT,
|
|
174
|
+
|
|
175
|
+
-- Browser-side preliminary score (0-5.0)
|
|
176
|
+
engagement_score REAL DEFAULT 0.0,
|
|
177
|
+
time_level TEXT,
|
|
178
|
+
scroll_score REAL DEFAULT 0.0,
|
|
179
|
+
click_score REAL DEFAULT 0.0,
|
|
180
|
+
video_score REAL DEFAULT 0.0,
|
|
181
|
+
hover_score REAL DEFAULT 0.0,
|
|
182
|
+
intention_level TEXT, -- curioso, interessado, comprador
|
|
183
|
+
|
|
184
|
+
-- Server-side final score (mais preciso)
|
|
185
|
+
server_engagement_score REAL DEFAULT 0.0,
|
|
186
|
+
|
|
187
|
+
-- Advanced matching data
|
|
188
|
+
email_hash TEXT,
|
|
189
|
+
phone_hash TEXT,
|
|
190
|
+
first_name_hash TEXT,
|
|
191
|
+
last_name_hash TEXT,
|
|
192
|
+
city TEXT,
|
|
193
|
+
state TEXT,
|
|
194
|
+
zip TEXT,
|
|
195
|
+
country TEXT,
|
|
196
|
+
|
|
197
|
+
-- Context
|
|
198
|
+
page_url TEXT,
|
|
199
|
+
utm_source TEXT,
|
|
200
|
+
utm_medium TEXT,
|
|
201
|
+
utm_campaign TEXT,
|
|
202
|
+
click_ids TEXT, -- JSON
|
|
203
|
+
|
|
204
|
+
-- Timestamps
|
|
205
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
CREATE INDEX IF NOT EXISTS idx_behavioral_events_session ON behavioral_events(session_id);
|
|
209
|
+
CREATE INDEX IF NOT EXISTS idx_behavioral_events_user ON behavioral_events(user_id);
|
|
210
|
+
CREATE INDEX IF NOT EXISTS idx_behavioral_events_engagement ON behavioral_events(server_engagement_score);
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## 📄 WORKER.JS (TEMPLATE COMPLETO)
|
|
216
|
+
|
|
217
|
+
```javascript
|
|
218
|
+
/**
|
|
219
|
+
* CDPEDGE CLOUDFLARE WORKER - Quantum Tier
|
|
220
|
+
*/
|
|
221
|
+
|
|
222
|
+
const ENCODER = new TextEncoder();
|
|
223
|
+
|
|
224
|
+
export default {
|
|
225
|
+
async fetch(request, env, ctx) {
|
|
226
|
+
const url = new URL(request.url);
|
|
227
|
+
|
|
228
|
+
if (url.pathname === '/api/health') {
|
|
229
|
+
return new Response(JSON.stringify({ status: 'Online' }), {
|
|
230
|
+
headers: { 'Content-Type': 'application/json' }
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (url.pathname.startsWith('/api/crm/')) {
|
|
235
|
+
return handleCrmApi(request, env, url);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const corsHeaders = buildCorsHeaders(env);
|
|
239
|
+
if (request.method === 'OPTIONS') return new Response(null, { headers: corsHeaders });
|
|
240
|
+
if (request.method !== 'POST') return new Response('Method Not Allowed', { status: 405 });
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
const body = await request.json();
|
|
244
|
+
const cf = request.cf || {};
|
|
245
|
+
const clientIP = request.headers.get('CF-Connecting-IP') || '';
|
|
246
|
+
const userAgent = request.headers.get('User-Agent') || '';
|
|
247
|
+
|
|
248
|
+
// 1. Sincronizar identidade (Identity Graph)
|
|
249
|
+
const visitor = await syncIdentity(env.DB, body);
|
|
250
|
+
|
|
251
|
+
// 2. Calcular Engagement Score no servidor (mais preciso)
|
|
252
|
+
const behavioralData = body.behavioral_data || {};
|
|
253
|
+
const engagementScore = await calculateServerEngagementScore(behavioralData, visitor);
|
|
254
|
+
|
|
255
|
+
// 3. Logar dados comportamentais no D1
|
|
256
|
+
await logBehavioralEvent(env.DB, body, visitor, engagementScore);
|
|
257
|
+
|
|
258
|
+
// 4. Dispatch para plataformas (usando engagement score calculado)
|
|
259
|
+
ctx.waitUntil(Promise.allSettled([
|
|
260
|
+
dispatchMetaCapi(body, env, visitor, engagementScore, clientIP, userAgent, cf),
|
|
261
|
+
dispatchGA4(body, env, visitor, engagementScore),
|
|
262
|
+
dispatchTikTok(body, env, visitor, engagementScore),
|
|
263
|
+
logToD1(env.DB, body, visitor, engagementScore)
|
|
264
|
+
]));
|
|
265
|
+
|
|
266
|
+
const cookieHeader = buildCookieHeader(visitor, env.UMBRELLA_DOMAIN);
|
|
267
|
+
|
|
268
|
+
return new Response(JSON.stringify({
|
|
269
|
+
success: true,
|
|
270
|
+
engagement_score: engagementScore.server_engagement_score,
|
|
271
|
+
intention_level: engagementScore.final_intention_level
|
|
272
|
+
}), {
|
|
273
|
+
headers: { ...corsHeaders, 'Content-Type': 'application/json', 'Set-Cookie': cookieHeader }
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
} catch (err) {
|
|
277
|
+
return new Response('Internal Error', { status: 500 });
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// HASHING (SHA-256 WebCrypto) - Server-Side
|
|
283
|
+
async function sha256(value) {
|
|
284
|
+
if (!value) return null;
|
|
285
|
+
const normalized = String(value).toLowerCase().trim();
|
|
286
|
+
const buffer = await crypto.subtle.digest('SHA-256', ENCODER.encode(normalized));
|
|
287
|
+
return Array.from(new Uint8Array(buffer)).map(byte => byte.toString(16).padStart(2, '0')).join('');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// NORMALIZAÇÃO DE PII (Server-Side)
|
|
291
|
+
/**
|
|
292
|
+
* Normaliza e-mail para máximo match (server-side)
|
|
293
|
+
*/
|
|
294
|
+
function normalizeEmail(email) {
|
|
295
|
+
if (!email || typeof email !== 'string') return '';
|
|
296
|
+
let normalized = email.trim();
|
|
297
|
+
|
|
298
|
+
// Gmail: remover plus addressing
|
|
299
|
+
if (normalized.includes('@gmail.com')) {
|
|
300
|
+
normalized = normalized.split('+')[0] + '@gmail.com';
|
|
301
|
+
}
|
|
302
|
+
if (normalized.includes('@googlemail.com')) {
|
|
303
|
+
normalized = normalized.split('+')[0] + '@googlemail.com';
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return normalized.toLowerCase();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Normaliza telefone para máximo match (server-side)
|
|
311
|
+
*/
|
|
312
|
+
function normalizePhone(phone) {
|
|
313
|
+
if (!phone || typeof phone !== 'string') return '';
|
|
314
|
+
let normalized = phone.replace(/\D/g, '');
|
|
315
|
+
|
|
316
|
+
// Adiciona DDI 55 se não tiver
|
|
317
|
+
if (normalized.length === 11 || normalized.length === 10) {
|
|
318
|
+
normalized = '55' + normalized;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return normalized.substring(0, 15);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Normaliza nome para máximo match (server-side)
|
|
326
|
+
*/
|
|
327
|
+
function normalizeName(name) {
|
|
328
|
+
if (!name || typeof name !== 'string') return '';
|
|
329
|
+
let normalized = name.trim();
|
|
330
|
+
|
|
331
|
+
// Remove acentos
|
|
332
|
+
normalized = normalized.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
|
333
|
+
|
|
334
|
+
// Converte para minúsculas
|
|
335
|
+
normalized = normalized.toLowerCase();
|
|
336
|
+
|
|
337
|
+
// Remove espaços extras
|
|
338
|
+
normalized = normalized.replace(/\s+/g, ' ');
|
|
339
|
+
|
|
340
|
+
return normalized.substring(0, 100);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Normaliza cidade para Meta AM (server-side)
|
|
345
|
+
*/
|
|
346
|
+
function normalizeCity(city) {
|
|
347
|
+
if (!city || typeof city !== 'string') return '';
|
|
348
|
+
let normalized = city.trim();
|
|
349
|
+
|
|
350
|
+
// Remove acentos
|
|
351
|
+
normalized = normalized.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
|
352
|
+
|
|
353
|
+
// Converte para minúsculas
|
|
354
|
+
normalized = normalized.toLowerCase();
|
|
355
|
+
|
|
356
|
+
return normalized.substring(0, 50);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Normaliza estado para Meta AM (server-side)
|
|
361
|
+
*/
|
|
362
|
+
function normalizeState(state) {
|
|
363
|
+
if (!state || typeof state !== 'string') return '';
|
|
364
|
+
let normalized = state.trim();
|
|
365
|
+
|
|
366
|
+
// Remove acentos
|
|
367
|
+
normalized = normalized.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
|
368
|
+
|
|
369
|
+
// Converte para minúsculas
|
|
370
|
+
normalized = normalized.toLowerCase();
|
|
371
|
+
|
|
372
|
+
return normalized.substring(0, 50);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Normaliza CEP para Meta AM (server-side)
|
|
377
|
+
*/
|
|
378
|
+
function normalizeZip(zip) {
|
|
379
|
+
if (!zip || typeof zip !== 'string') return '';
|
|
380
|
+
return zip.replace(/\D/g, '').substring(0, 10);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Normaliza data de nascimento para Meta AM (server-side)
|
|
385
|
+
* Formato esperado: YYYYMMDD
|
|
386
|
+
*/
|
|
387
|
+
function normalizeDOB(dob) {
|
|
388
|
+
if (!dob || typeof dob !== 'string') return '';
|
|
389
|
+
|
|
390
|
+
const formats = [
|
|
391
|
+
/(\d{4})-(\d{2})-(\d{2})/, // YYYY-MM-DD
|
|
392
|
+
/(\d{2})\/(\d{2})\/(\d{4})/, // DD/MM/YYYY
|
|
393
|
+
/(\d{2})-(\d{2})-(\d{4})/ // DD-MM-YYYY
|
|
394
|
+
];
|
|
395
|
+
|
|
396
|
+
for (const format of formats) {
|
|
397
|
+
const match = dob.match(format);
|
|
398
|
+
if (match) {
|
|
399
|
+
let year, month, day;
|
|
400
|
+
if (match[1].length === 4) {
|
|
401
|
+
[year, month, day] = [match[1], match[2], match[3]];
|
|
402
|
+
} else {
|
|
403
|
+
[day, month, year] = [match[1], match[2], match[3]];
|
|
404
|
+
}
|
|
405
|
+
return `${year}${month}${day}`;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return '';
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// SERVER-SIDE ENGAGEMENT SCORING (Quantum Tier)
|
|
413
|
+
/**
|
|
414
|
+
* Calcula o score de engajamento final no servidor.
|
|
415
|
+
* Mais preciso que o browser-side porque tem acesso a:
|
|
416
|
+
* - Histórico de sessões anteriores (D1)
|
|
417
|
+
* - Comportamento multi-sessão
|
|
418
|
+
* - Padrões temporais
|
|
419
|
+
*
|
|
420
|
+
* @param {object} behavioralData - Dados comportamentais recebidos do browser
|
|
421
|
+
* @param {object} visitorContext - Contexto do visitante do Identity Graph
|
|
422
|
+
* @returns {object} Score final (0-5.0) e componentes
|
|
423
|
+
*/
|
|
424
|
+
async function calculateServerEngagementScore(behavioralData, visitorContext) {
|
|
425
|
+
const browserScore = behavioralData.engagement_score || 0.0;
|
|
426
|
+
const intentionLevel = behavioralData.intention_level || 'curioso';
|
|
427
|
+
|
|
428
|
+
// 1. Histórico de visitas (Weight: 25%)
|
|
429
|
+
const visitScore = calculateVisitScore(visitorContext);
|
|
430
|
+
|
|
431
|
+
// 2. Consistência de intenção (Weight: 20%)
|
|
432
|
+
const intentionScore = calculateIntentionScore(behavioralData, visitorContext);
|
|
433
|
+
|
|
434
|
+
// 3. Recência (Weight: 15%)
|
|
435
|
+
const recencyScore = calculateRecencyScore(visitorContext);
|
|
436
|
+
|
|
437
|
+
// 4. Multi-sessão (Weight: 20%)
|
|
438
|
+
const multiSessionScore = calculateMultiSessionScore(behavioralData, visitorContext);
|
|
439
|
+
|
|
440
|
+
// 5. Browser-side score (Weight: 20%)
|
|
441
|
+
const browserSideScore = browserScore;
|
|
442
|
+
|
|
443
|
+
// Cálculo final ponderado
|
|
444
|
+
const finalScore = (
|
|
445
|
+
(visitScore * 0.25) +
|
|
446
|
+
(intentionScore * 0.20) +
|
|
447
|
+
(recencyScore * 0.15) +
|
|
448
|
+
(multiSessionScore * 0.20) +
|
|
449
|
+
(browserSideScore * 0.20)
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
// Determinar nível de intenção final
|
|
453
|
+
const finalIntentionLevel = determineFinalIntentionLevel(finalScore, intentionLevel);
|
|
454
|
+
|
|
455
|
+
return {
|
|
456
|
+
server_engagement_score: Math.min(finalScore, 5.0),
|
|
457
|
+
final_intention_level: finalIntentionLevel,
|
|
458
|
+
components: {
|
|
459
|
+
visit_score: visitScore,
|
|
460
|
+
intention_score: intentionScore,
|
|
461
|
+
recency_score: recencyScore,
|
|
462
|
+
multi_session_score: multiSessionScore,
|
|
463
|
+
browser_side_score: browserSideScore
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function calculateVisitScore(visitorContext) {
|
|
469
|
+
// Score baseado no número de visitas
|
|
470
|
+
const visitCount = visitorContext.visit_count || 1;
|
|
471
|
+
|
|
472
|
+
if (visitCount === 1) return 1.0;
|
|
473
|
+
if (visitCount <= 3) return 2.5;
|
|
474
|
+
if (visitCount <= 7) return 3.5;
|
|
475
|
+
if (visitCount <= 14) return 4.0;
|
|
476
|
+
return 5.0;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function calculateIntentionScore(behavioralData, visitorContext) {
|
|
480
|
+
const intentionLevel = behavioralData.intention_level || 'curioso';
|
|
481
|
+
|
|
482
|
+
// Peso por nível de intenção
|
|
483
|
+
const intentionWeights = {
|
|
484
|
+
'curioso': 1.0,
|
|
485
|
+
'interessado': 3.0,
|
|
486
|
+
'comprador': 5.0
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
return intentionWeights[intentionLevel] || 1.0;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function calculateRecencyScore(visitorContext) {
|
|
493
|
+
if (!visitorContext.last_seen) return 1.0;
|
|
494
|
+
|
|
495
|
+
const lastSeen = new Date(visitorContext.last_seen);
|
|
496
|
+
const now = new Date();
|
|
497
|
+
const hoursSinceLastVisit = (now - lastSeen) / (1000 * 60 * 60);
|
|
498
|
+
|
|
499
|
+
// Quanto mais recente, maior o score
|
|
500
|
+
if (hoursSinceLastVisit < 1) return 5.0;
|
|
501
|
+
if (hoursSinceLastVisit < 24) return 4.0;
|
|
502
|
+
if (hoursSinceLastVisit < 168) return 3.0; // 1 semana
|
|
503
|
+
if (hoursSinceLastVisit < 720) return 2.0; // 1 mês
|
|
504
|
+
return 1.0;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function calculateMultiSessionScore(behavioralData, visitorContext) {
|
|
508
|
+
const visitCount = visitorContext.visit_count || 1;
|
|
509
|
+
|
|
510
|
+
// Score aumenta com número de visitas
|
|
511
|
+
if (visitCount === 1) return 1.0;
|
|
512
|
+
if (visitCount <= 3) return 2.0;
|
|
513
|
+
if (visitCount <= 7) return 3.5;
|
|
514
|
+
if (visitCount <= 14) return 4.5;
|
|
515
|
+
return 5.0;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function determineFinalIntentionLevel(serverScore, browserIntention) {
|
|
519
|
+
// Nível final é determinado principalmente pelo score do servidor
|
|
520
|
+
if (serverScore < 1.5) return 'curioso';
|
|
521
|
+
if (serverScore < 3.0) return 'interessado';
|
|
522
|
+
return 'comprador';
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// IDENTITY GRAPH SYNC
|
|
526
|
+
async function syncIdentity(DB, body) {
|
|
527
|
+
const fp = body.fingerprint || null;
|
|
528
|
+
if (!fp || !DB) return body;
|
|
529
|
+
|
|
530
|
+
const existing = await DB.prepare(
|
|
531
|
+
'SELECT * FROM identity_graph WHERE fingerprint = ?'
|
|
532
|
+
).bind(fp).first();
|
|
533
|
+
|
|
534
|
+
if (existing) {
|
|
535
|
+
return {
|
|
536
|
+
fbp: body.fbp || existing.fbp,
|
|
537
|
+
fbc: body.fbc || existing.fbc,
|
|
538
|
+
ga_client_id: body.ga_client_id || existing.ga_client_id,
|
|
539
|
+
external_id: body.external_id || existing.external_id,
|
|
540
|
+
ttclid: body.ttclid || existing.ttclid,
|
|
541
|
+
fingerprint: fp,
|
|
542
|
+
visit_count: (existing.visit_count || 1) + 1
|
|
543
|
+
};
|
|
544
|
+
} else {
|
|
545
|
+
await DB.prepare(`
|
|
546
|
+
INSERT OR IGNORE INTO identity_graph (fingerprint, fbp, fbc, ga_client_id, external_id, ttclid, first_utm)
|
|
547
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
548
|
+
`).bind(fp, body.fbp, body.fbc, body.ga_client_id, body.external_id, body.ttclid, JSON.stringify(body.utm || {})).run();
|
|
549
|
+
return body;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// META CAPI v22.0 (com Engagement Scoring + Advanced Matching Maximum)
|
|
554
|
+
async function dispatchMetaCapi(body, env, visitor, engagementScore, clientIP, userAgent, cf) {
|
|
555
|
+
if (!env.META_ACCESS_TOKEN || !body.pixel_id) return;
|
|
556
|
+
|
|
557
|
+
// Advanced Matching Maximum: Hash de todos os campos PII disponíveis
|
|
558
|
+
const em = body.email ? await sha256(normalizeEmail(body.email)) : null;
|
|
559
|
+
const ph = body.phone ? await sha256(normalizePhone(body.phone)) : null;
|
|
560
|
+
const fn = body.first_name ? await sha256(normalizeName(body.first_name)) : null;
|
|
561
|
+
const ln = body.last_name ? await sha256(normalizeName(body.last_name)) : null;
|
|
562
|
+
const ct = body.city ? normalizeCity(body.city) : null; // Cidade NÃO é hashada (Meta AM)
|
|
563
|
+
const st = body.state ? normalizeState(body.state) : null; // Estado NÃO é hashado (Meta AM)
|
|
564
|
+
const zp = body.zip ? normalizeZip(body.zip) : null; // CEP NÃO é hashado (Meta AM)
|
|
565
|
+
const db = body.dob ? normalizeDOB(body.dob) : null; // DOB NÃO é hashado (Meta AM)
|
|
566
|
+
|
|
567
|
+
const payload = {
|
|
568
|
+
data: [{
|
|
569
|
+
event_name: body.event_name,
|
|
570
|
+
event_time: Math.floor(Date.now() / 1000),
|
|
571
|
+
event_id: body.event_id || crypto.randomUUID(),
|
|
572
|
+
event_source_url: body.page_url,
|
|
573
|
+
action_source: 'website',
|
|
574
|
+
user_data: {
|
|
575
|
+
// Advanced Matching Maximum
|
|
576
|
+
em: em ? [em] : undefined,
|
|
577
|
+
ph: ph ? [ph] : undefined,
|
|
578
|
+
fn: fn ? [fn] : undefined,
|
|
579
|
+
ln: ln ? [ln] : undefined,
|
|
580
|
+
ct: ct ? [ct] : undefined,
|
|
581
|
+
st: st ? [st] : undefined,
|
|
582
|
+
zp: zp ? [zp] : undefined,
|
|
583
|
+
db: db ? [db] : undefined,
|
|
584
|
+
|
|
585
|
+
// Identity Graph
|
|
586
|
+
client_ip_address: clientIP,
|
|
587
|
+
client_user_agent: userAgent,
|
|
588
|
+
fbp: visitor.fbp,
|
|
589
|
+
fbc: visitor.fbc,
|
|
590
|
+
external_id: visitor.external_id ? [visitor.external_id] : undefined
|
|
591
|
+
},
|
|
592
|
+
custom_data: {
|
|
593
|
+
// Engagement score enviado para Meta (otimiza ad delivery)
|
|
594
|
+
engagement_score: engagementScore.server_engagement_score,
|
|
595
|
+
intention_level: engagementScore.final_intention_level,
|
|
596
|
+
|
|
597
|
+
// Componentes do score (para análise)
|
|
598
|
+
visit_score: engagementScore.components.visit_score,
|
|
599
|
+
intention_score: engagementScore.components.intention_score,
|
|
600
|
+
recency_score: engagementScore.components.recency_score,
|
|
601
|
+
multi_session_score: engagementScore.components.multi_session_score,
|
|
602
|
+
browser_side_score: engagementScore.components.browser_side_score,
|
|
603
|
+
|
|
604
|
+
// Dados originais do evento
|
|
605
|
+
value: body.value || 0,
|
|
606
|
+
currency: body.currency || 'BRL',
|
|
607
|
+
...body.custom_data
|
|
608
|
+
}
|
|
609
|
+
}]
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
await fetch(`https://graph.facebook.com/v22.0/${body.pixel_id}/events?access_token=${env.META_ACCESS_TOKEN}`, {
|
|
613
|
+
method: 'POST',
|
|
614
|
+
headers: { 'Content-Type': 'application/json' },
|
|
615
|
+
body: JSON.stringify(payload)
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function buildCorsHeaders(env) {
|
|
620
|
+
return {
|
|
621
|
+
'Access-Control-Allow-Origin': '*',
|
|
622
|
+
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
|
623
|
+
'Access-Control-Allow-Headers': 'Content-Type',
|
|
624
|
+
'Access-Control-Allow-Credentials': 'true'
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function buildCookieHeader(visitor, umbrellaDomain) {
|
|
629
|
+
const ttl = 60 * 60 * 24 * 365;
|
|
630
|
+
const base = `Path=/; Max-Age=${ttl}; Domain=.${umbrellaDomain}; SameSite=Lax; Secure`;
|
|
631
|
+
if (visitor.fbp) return `_fbp=${visitor.fbp}; ${base}`;
|
|
632
|
+
return '';
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// LOGAR DADOS COMPORTAMENTAIS (Engagement Scoring)
|
|
636
|
+
async function logBehavioralEvent(DB, body, visitor, engagementScore) {
|
|
637
|
+
if (!DB) return;
|
|
638
|
+
|
|
639
|
+
await DB.prepare(`
|
|
640
|
+
INSERT OR REPLACE INTO behavioral_events (
|
|
641
|
+
event_id, user_id, session_id,
|
|
642
|
+
engagement_score, time_level, scroll_score, click_score, video_score, hover_score, intention_level,
|
|
643
|
+
server_engagement_score, final_intention_level,
|
|
644
|
+
page_url, utm_source, utm_medium, utm_campaign, click_ids
|
|
645
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
646
|
+
`).bind(
|
|
647
|
+
body.event_id,
|
|
648
|
+
body.user_id,
|
|
649
|
+
body.session_id,
|
|
650
|
+
engagementScore.server_engagement_score,
|
|
651
|
+
body.behavioral_data?.time_level || null,
|
|
652
|
+
body.behavioral_data?.scroll_score || 0.0,
|
|
653
|
+
body.behavioral_data?.click_score || 0.0,
|
|
654
|
+
body.behavioral_data?.video_score || 0.0,
|
|
655
|
+
body.behavioral_data?.hover_score || 0.0,
|
|
656
|
+
body.behavioral_data?.intention_level || null,
|
|
657
|
+
engagementScore.final_intention_level,
|
|
658
|
+
body.page_url,
|
|
659
|
+
body.utms?.utm_source || null,
|
|
660
|
+
body.utms?.utm_medium || null,
|
|
661
|
+
body.utms?.utm_campaign || null,
|
|
662
|
+
JSON.stringify(body.click_ids || {})
|
|
663
|
+
).run();
|
|
664
|
+
}
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
---
|
|
668
|
+
|
|
669
|
+
## 📊 ENGAGEMENT SCORING SERVER-SIDE (Quantum Tier)
|
|
670
|
+
|
|
671
|
+
### Formula de Sucesso
|
|
672
|
+
|
|
673
|
+
**Score = 1 / (Event Match Quality × Signal Strength × Behavioral Intelligence)**
|
|
674
|
+
|
|
675
|
+
O engagement scoring no servidor é mais preciso que no browser porque tem acesso a:
|
|
676
|
+
|
|
677
|
+
1. **Histórico de sessões anteriores** (D1 Identity Graph)
|
|
678
|
+
2. **Comportamento multi-sessão** (padrões entre visitas)
|
|
679
|
+
3. **Dados temporais** (recência, frequência)
|
|
680
|
+
4. **Atribuição completa** (todas as interações do usuário)
|
|
681
|
+
|
|
682
|
+
### Ponderação do Score Final (0-5.0)
|
|
683
|
+
|
|
684
|
+
| Componente | Peso | Descrição |
|
|
685
|
+
|-----------|-------|-----------|
|
|
686
|
+
| Visit Score | 25% | Número de visitas (1x = 1.0, 15+ = 5.0) |
|
|
687
|
+
| Intention Score | 20% | Nível de intenção (curioso=1.0, interessado=3.0, comprador=5.0) |
|
|
688
|
+
| Recency Score | 15% | Tempo desde última visita (<1h=5.0, >1mês=1.0) |
|
|
689
|
+
| Multi-Session Score | 20% | Comportamento consistente entre sessões |
|
|
690
|
+
| Browser-Side Score | 20% | Score preliminar do browser (0-5.0) |
|
|
691
|
+
|
|
692
|
+
### Cálculo por Componente
|
|
693
|
+
|
|
694
|
+
```javascript
|
|
695
|
+
// 1. Visit Score (25%)
|
|
696
|
+
function calculateVisitScore(visitorContext) {
|
|
697
|
+
const visitCount = visitorContext.visit_count || 1;
|
|
698
|
+
if (visitCount === 1) return 1.0;
|
|
699
|
+
if (visitCount <= 3) return 2.5;
|
|
700
|
+
if (visitCount <= 7) return 3.5;
|
|
701
|
+
if (visitCount <= 14) return 4.0;
|
|
702
|
+
return 5.0;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// 2. Intention Score (20%)
|
|
706
|
+
function calculateIntentionScore(behavioralData, visitorContext) {
|
|
707
|
+
const intentionLevel = behavioralData.intention_level || 'curioso';
|
|
708
|
+
const intentionWeights = {
|
|
709
|
+
'curioso': 1.0,
|
|
710
|
+
'interessado': 3.0,
|
|
711
|
+
'comprador': 5.0
|
|
712
|
+
};
|
|
713
|
+
return intentionWeights[intentionLevel] || 1.0;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// 3. Recency Score (15%)
|
|
717
|
+
function calculateRecencyScore(visitorContext) {
|
|
718
|
+
if (!visitorContext.last_seen) return 1.0;
|
|
719
|
+
const lastSeen = new Date(visitorContext.last_seen);
|
|
720
|
+
const now = new Date();
|
|
721
|
+
const hoursSinceLastVisit = (now - lastSeen) / (1000 * 60 * 60);
|
|
722
|
+
|
|
723
|
+
if (hoursSinceLastVisit < 1) return 5.0;
|
|
724
|
+
if (hoursSinceLastVisit < 24) return 4.0;
|
|
725
|
+
if (hoursSinceLastVisit < 168) return 3.0;
|
|
726
|
+
if (hoursSinceLastVisit < 720) return 2.0;
|
|
727
|
+
return 1.0;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// 4. Multi-Session Score (20%)
|
|
731
|
+
function calculateMultiSessionScore(behavioralData, visitorContext) {
|
|
732
|
+
const visitCount = visitorContext.visit_count || 1;
|
|
733
|
+
if (visitCount === 1) return 1.0;
|
|
734
|
+
if (visitCount <= 3) return 2.0;
|
|
735
|
+
if (visitCount <= 7) return 3.5;
|
|
736
|
+
if (visitCount <= 14) return 4.5;
|
|
737
|
+
return 5.0;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// 5. Final Score Calculation
|
|
741
|
+
const finalScore = (
|
|
742
|
+
(visitScore * 0.25) +
|
|
743
|
+
(intentionScore * 0.20) +
|
|
744
|
+
(recencyScore * 0.15) +
|
|
745
|
+
(multiSessionScore * 0.20) +
|
|
746
|
+
(browserSideScore * 0.20)
|
|
747
|
+
);
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
### Níveis de Intenção Finais
|
|
751
|
+
|
|
752
|
+
| Score Final | Nível de Intenção | Comportamento Esperado |
|
|
753
|
+
|------------|------------------|----------------------|
|
|
754
|
+
| < 1.5 | Curioso | Primeira visita, baixo engajamento |
|
|
755
|
+
| 1.5 - 3.0 | Interessado | 2-7 visitas, engajamento moderado |
|
|
756
|
+
| > 3.0 | Comprador | 7+ visitas, alta intenção de compra |
|
|
757
|
+
|
|
758
|
+
### Integração com Plataformas
|
|
759
|
+
|
|
760
|
+
**Meta CAPI v22.0:**
|
|
761
|
+
```javascript
|
|
762
|
+
custom_data: {
|
|
763
|
+
engagement_score: engagementScore.server_engagement_score,
|
|
764
|
+
intention_level: engagementScore.final_intention_level,
|
|
765
|
+
visit_score: engagementScore.components.visit_score,
|
|
766
|
+
intention_score: engagementScore.components.intention_score,
|
|
767
|
+
recency_score: engagementScore.components.recency_score,
|
|
768
|
+
multi_session_score: engagementScore.components.multi_session_score,
|
|
769
|
+
browser_side_score: engagementScore.components.browser_side_score,
|
|
770
|
+
value: body.value || 0,
|
|
771
|
+
currency: body.currency || 'BRL'
|
|
772
|
+
}
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
**Google GA4 Measurement Protocol:**
|
|
776
|
+
```javascript
|
|
777
|
+
custom_params: {
|
|
778
|
+
engagement_score: engagementScore.server_engagement_score,
|
|
779
|
+
intention_level: engagementScore.final_intention_level,
|
|
780
|
+
visit_count: visitorContext.visit_count,
|
|
781
|
+
days_since_last_visit: Math.floor(hoursSinceLastVisit / 24)
|
|
782
|
+
}
|
|
783
|
+
```
|
|
784
|
+
|
|
785
|
+
**TikTok Events API v1.3:**
|
|
786
|
+
```javascript
|
|
787
|
+
context: {
|
|
788
|
+
user: {
|
|
789
|
+
engagement_score: engagementScore.server_engagement_score,
|
|
790
|
+
intention_level: engagementScore.final_intention_level
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
---
|
|
796
|
+
|
|
797
|
+
## ✅ REGRAS CRÍTICAS
|
|
798
|
+
|
|
799
|
+
1. **Cloudflare-Only**: Sem dependências externas.
|
|
800
|
+
2. **Same-Domain**: Worker no domínio do site (anti-adblock).
|
|
801
|
+
3. **Umbrella Protocol**: Cookies com abrangência de domínio.
|
|
802
|
+
4. **SHA-256 Nativo**: WebCrypto API sempre.
|
|
803
|
+
5. **Meta CAPI**: Sempre versão `v22.0`.
|
|
804
|
+
6. **TikTok Events API**: Sempre versão `v1.3`.
|
|
805
|
+
7. **Background Execution**: `ctx.waitUntil()` para não bloquear o usuário.
|
|
806
|
+
8. **Anti-Blocking Server-Side**: Worker deve aceitar requests de qualquer user-agent, evitar headers que ativam ad-blockers, responder rapidamente.
|
|
807
|
+
|
|
808
|
+
---
|
|
809
|
+
|
|
810
|
+
## 🛡️ ANTI-BLOCKING SERVER-SIDE (Quantum Tier)
|
|
811
|
+
|
|
812
|
+
### Estratégias para Maximizar Resiliência
|
|
813
|
+
|
|
814
|
+
**1. Same-Domain Endpoint:**
|
|
815
|
+
- Worker deve estar no mesmo domínio do site: `site.com/api/tracking`
|
|
816
|
+
- Evita bloqueios de CORS e ad-blockers que bloqueiam requests cross-origin
|
|
817
|
+
|
|
818
|
+
**2. First-Party Cookies:**
|
|
819
|
+
- Definir cookies no umbrella domain: `.example.com`
|
|
820
|
+
- Duração de 365 dias (max permitido)
|
|
821
|
+
- SameSite=Lax para balance entre segurança e funcionalidade
|
|
822
|
+
|
|
823
|
+
**3. Response Headers Anti-Blocking:**
|
|
824
|
+
```javascript
|
|
825
|
+
const corsHeaders = {
|
|
826
|
+
'Access-Control-Allow-Origin': '*',
|
|
827
|
+
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
|
828
|
+
'Access-Control-Allow-Headers': 'Content-Type',
|
|
829
|
+
'Access-Control-Allow-Credentials': 'true',
|
|
830
|
+
// Headers adicionais anti-blocking
|
|
831
|
+
'X-Content-Type-Options': 'nosniff',
|
|
832
|
+
'X-Frame-Options': 'SAMEORIGIN'
|
|
833
|
+
};
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
**4. Fast Response (<100ms):**
|
|
837
|
+
- Processar e responder rapidamente
|
|
838
|
+
- Usar `ctx.waitUntil()` para processamento assíncrono
|
|
839
|
+
- Primeira resposta é imediata, processamento em background
|
|
840
|
+
|
|
841
|
+
**5. Accept Any User-Agent:**
|
|
842
|
+
- Não bloquear requests baseados em user-agent
|
|
843
|
+
- Ad-blockers podem falsificar user-agent
|
|
844
|
+
- Validação deve ser baseada em token/secret, não UA
|
|
845
|
+
|
|
846
|
+
**6. No Sensitive Keywords in Paths:**
|
|
847
|
+
- Evitar paths com palavras que ativam ad-blockers: `/track`, `/pixel`, `/analytics`
|
|
848
|
+
- Usar `/api/tracking` ou `/api/events` em vez disso
|
|
849
|
+
|
|
850
|
+
---
|
|
851
|
+
|
|
852
|
+
## 🔄 ESCALONAMENTO AUTOMÁTICO DE ERROS (Quantum Tier)
|
|
853
|
+
|
|
854
|
+
1. **Cloudflare-Only**: Sem dependências externas.
|
|
855
|
+
2. **Same-Domain**: Worker no domínio do site.
|
|
856
|
+
3. **Umbrella Protocol**: Cookies com abrangência de domínio.
|
|
857
|
+
4. **SHA-256 Nativo**: WebCrypto API sempre.
|
|
858
|
+
5. **Meta CAPI**: Sempre versão `v22.0`.
|
|
859
|
+
6. **TikTok Events API**: Sempre versão `v1.3`.
|
|
860
|
+
7. **Background Execution**: `ctx.waitUntil()` para não bloquear o usuário.
|
|
861
|
+
|
|
862
|
+
---
|
|
863
|
+
|
|
864
|
+
## 🔄 ESCALONAMENTO AUTOMÁTICO DE ERROS (Quantum Tier)
|
|
865
|
+
|
|
866
|
+
### Estratégia de Resiliência: 3-Tier Retry System
|
|
867
|
+
|
|
868
|
+
Quando o Worker falhar ao enviar evento para qualquer plataforma (Meta, Google, TikTok, etc.), seguir este fluxo:
|
|
869
|
+
|
|
870
|
+
```
|
|
871
|
+
Tentativa 1 (Imediata)
|
|
872
|
+
↓ Falha
|
|
873
|
+
Tentativa 2 (Cloudflare Queue - 5 minutos)
|
|
874
|
+
↓ Falha
|
|
875
|
+
Tentativa 3 (Cloudflare Queue - 15 minutos)
|
|
876
|
+
↓ Falha (3ª consecutiva)
|
|
877
|
+
🚨 ALERTA VIA WHATSAPP AGENT → ADMIN
|
|
878
|
+
```
|
|
879
|
+
|
|
880
|
+
---
|
|
881
|
+
|
|
882
|
+
### PASSO 1 — Captura de Falha com Gravação no D1
|
|
883
|
+
|
|
884
|
+
Toda função de dispatch (Meta, Google, TikTok) DEVE ter try/catch com gravação:
|
|
885
|
+
|
|
886
|
+
```javascript
|
|
887
|
+
// Exemplo para dispatchMetaCapi com escalonamento
|
|
888
|
+
async function dispatchMetaCapi(body, env, visitor, heatScore, clientIP, userAgent, cf, retryCount = 0) {
|
|
889
|
+
if (!env.META_ACCESS_TOKEN || !body.pixel_id) {
|
|
890
|
+
await logEventFailure(env.DB, 'meta', body.event_id, 'Missing credentials/pixel_id');
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
try {
|
|
895
|
+
const em = await sha256(body.email);
|
|
896
|
+
const ph = await sha256((body.phone || '').replace(/\D/g, ''));
|
|
897
|
+
|
|
898
|
+
const payload = { /* payload Meta CAPI v22.0 */ };
|
|
899
|
+
|
|
900
|
+
const response = await fetch(
|
|
901
|
+
`https://graph.facebook.com/v22.0/${body.pixel_id}/events?access_token=${env.META_ACCESS_TOKEN}`,
|
|
902
|
+
{
|
|
903
|
+
method: 'POST',
|
|
904
|
+
headers: { 'Content-Type': 'application/json' },
|
|
905
|
+
body: JSON.stringify(payload)
|
|
906
|
+
}
|
|
907
|
+
);
|
|
908
|
+
|
|
909
|
+
if (!response.ok) {
|
|
910
|
+
const errorText = await response.text();
|
|
911
|
+
throw new Error(`Meta API Error ${response.status}: ${errorText}`);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// SUCESSO: Atualizar status no D1
|
|
915
|
+
await logEventSuccess(env.DB, 'meta', body.event_id);
|
|
916
|
+
|
|
917
|
+
} catch (error) {
|
|
918
|
+
// FALHA: Gravar no D1 e enfileirar para retry
|
|
919
|
+
await logEventFailure(env.DB, 'meta', body.event_id, error.message, retryCount);
|
|
920
|
+
|
|
921
|
+
// Enfileirar para Cloudflare Queue
|
|
922
|
+
await enqueueRetry(env.RETRY_QUEUE, {
|
|
923
|
+
platform: 'meta',
|
|
924
|
+
event_id: body.event_id,
|
|
925
|
+
body: body,
|
|
926
|
+
visitor,
|
|
927
|
+
heatScore,
|
|
928
|
+
clientIP,
|
|
929
|
+
userAgent,
|
|
930
|
+
cf,
|
|
931
|
+
retry_count: retryCount + 1
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
```
|
|
936
|
+
|
|
937
|
+
---
|
|
938
|
+
|
|
939
|
+
### PASSO 2 — Atualização do Schema D1 (adicionar tabelas de retry)
|
|
940
|
+
|
|
941
|
+
Adicionar ao `schema.sql`:
|
|
942
|
+
|
|
943
|
+
```sql
|
|
944
|
+
-- TABELA DE EVENTOS COM STATUS APERFEIÇOADO
|
|
945
|
+
ALTER TABLE events_log ADD COLUMN IF NOT EXISTS retry_count INTEGER DEFAULT 0;
|
|
946
|
+
ALTER TABLE events_log ADD COLUMN IF NOT EXISTS last_retry_at TEXT;
|
|
947
|
+
ALTER TABLE events_log ADD COLUMN IF NOT EXISTS max_retries INTEGER DEFAULT 3;
|
|
948
|
+
|
|
949
|
+
-- TABELA DE FILA DE RETRY (para Queue)
|
|
950
|
+
CREATE TABLE IF NOT EXISTS retry_queue (
|
|
951
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
952
|
+
platform TEXT NOT NULL,
|
|
953
|
+
event_id TEXT NOT NULL,
|
|
954
|
+
event_payload TEXT NOT NULL,
|
|
955
|
+
visitor_data TEXT,
|
|
956
|
+
retry_count INTEGER DEFAULT 0,
|
|
957
|
+
scheduled_at TEXT DEFAULT (datetime('now')),
|
|
958
|
+
status TEXT DEFAULT 'pending', -- pending | processing | failed | success
|
|
959
|
+
error_message TEXT,
|
|
960
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
961
|
+
);
|
|
962
|
+
|
|
963
|
+
CREATE INDEX IF NOT EXISTS idx_retry_scheduled ON retry_queue(scheduled_at, status);
|
|
964
|
+
```
|
|
965
|
+
|
|
966
|
+
---
|
|
967
|
+
|
|
968
|
+
### PASSO 3 — Funções de Log de Sucesso/Falha
|
|
969
|
+
|
|
970
|
+
```javascript
|
|
971
|
+
// Log de sucesso
|
|
972
|
+
async function logEventSuccess(DB, platform, eventId) {
|
|
973
|
+
if (!DB) return;
|
|
974
|
+
|
|
975
|
+
await DB.prepare(`
|
|
976
|
+
UPDATE events_log
|
|
977
|
+
SET status = 'success',
|
|
978
|
+
retry_count = 0,
|
|
979
|
+
last_retry_at = NULL
|
|
980
|
+
WHERE event_id = ? AND platform = ?
|
|
981
|
+
`).bind(eventId, platform).run();
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// Log de falha com escalonamento
|
|
985
|
+
async function logEventFailure(DB, platform, eventId, errorMessage, retryCount) {
|
|
986
|
+
if (!DB) return;
|
|
987
|
+
|
|
988
|
+
const maxRetries = 3;
|
|
989
|
+
const isFinalFailure = retryCount >= maxRetries;
|
|
990
|
+
|
|
991
|
+
await DB.prepare(`
|
|
992
|
+
UPDATE events_log
|
|
993
|
+
SET status = ?,
|
|
994
|
+
retry_count = ?,
|
|
995
|
+
error_msg = ?,
|
|
996
|
+
last_retry_at = ?
|
|
997
|
+
WHERE event_id = ? AND platform = ?
|
|
998
|
+
`).bind(
|
|
999
|
+
isFinalFailure ? 'failed' : 'retrying',
|
|
1000
|
+
retryCount,
|
|
1001
|
+
errorMessage,
|
|
1002
|
+
new Date().toISOString(),
|
|
1003
|
+
eventId,
|
|
1004
|
+
platform
|
|
1005
|
+
).run();
|
|
1006
|
+
|
|
1007
|
+
// Se falha definitiva (3ª tentativa), disparar alerta
|
|
1008
|
+
if (isFinalFailure) {
|
|
1009
|
+
await dispatchAlert(platform, eventId, errorMessage);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// Enfileirar para retry via Cloudflare Queue
|
|
1014
|
+
async function enqueueRetry(queue, retryData) {
|
|
1015
|
+
const { platform, event_id, retry_count } = retryData;
|
|
1016
|
+
|
|
1017
|
+
// Calcular delay exponencial: 5min, 15min, 45min
|
|
1018
|
+
const delays = [5, 15, 45];
|
|
1019
|
+
const delayMinutes = delays[Math.min(retry_count - 1, 2)];
|
|
1020
|
+
const scheduledAt = new Date(Date.now() + delayMinutes * 60 * 1000).toISOString();
|
|
1021
|
+
|
|
1022
|
+
await queue.send(JSON.stringify({
|
|
1023
|
+
...retryData,
|
|
1024
|
+
scheduled_at: scheduledAt,
|
|
1025
|
+
status: 'pending'
|
|
1026
|
+
}));
|
|
1027
|
+
}
|
|
1028
|
+
```
|
|
1029
|
+
|
|
1030
|
+
---
|
|
1031
|
+
|
|
1032
|
+
### PASSO 4 — Alerta Automático via WhatsApp Agent (CallMeBot)
|
|
1033
|
+
|
|
1034
|
+
Após 3 falhas consecutivas, disparar alerta para o administrador:
|
|
1035
|
+
|
|
1036
|
+
```javascript
|
|
1037
|
+
// Função de alerta integrada com WhatsApp Agent
|
|
1038
|
+
async function dispatchAlert(platform, eventId, errorMessage) {
|
|
1039
|
+
const alertMessage = `
|
|
1040
|
+
🚨 CDPEDGE ALERTA CRÍTICA
|
|
1041
|
+
|
|
1042
|
+
Platform: ${platform.toUpperCase()}
|
|
1043
|
+
Event ID: ${eventId}
|
|
1044
|
+
Error: ${errorMessage}
|
|
1045
|
+
Failed Attempts: 3 (máximo alcançado)
|
|
1046
|
+
|
|
1047
|
+
Ação necessária: Verificar configuração da API ${platform} no wrangler secrets.
|
|
1048
|
+
Timestamp: ${new Date().toISOString()}
|
|
1049
|
+
`.trim();
|
|
1050
|
+
|
|
1051
|
+
// Verificar se há token do WhatsApp configurado
|
|
1052
|
+
const waPhoneId = env.WA_PHONE_ID;
|
|
1053
|
+
const adminNumber = env.ADMIN_PHONE_NUMBER;
|
|
1054
|
+
|
|
1055
|
+
if (waPhoneId && adminNumber) {
|
|
1056
|
+
await fetch(`https://graph.facebook.com/v22.0/${waPhoneId}/messages`, {
|
|
1057
|
+
method: 'POST',
|
|
1058
|
+
headers: {
|
|
1059
|
+
'Content-Type': 'application/json',
|
|
1060
|
+
'Authorization': `Bearer ${env.WA_ACCESS_TOKEN}`
|
|
1061
|
+
},
|
|
1062
|
+
body: JSON.stringify({
|
|
1063
|
+
messaging_product: 'whatsapp',
|
|
1064
|
+
to: adminNumber,
|
|
1065
|
+
type: 'text',
|
|
1066
|
+
text: alertMessage
|
|
1067
|
+
})
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
// Fallback para CallMeBot se WhatsApp Cloud API não estiver disponível
|
|
1072
|
+
else if (env.ADMIN_PHONE_NUMBER) {
|
|
1073
|
+
await fetch(`https://api.callmebot.com/send.php`, {
|
|
1074
|
+
method: 'POST',
|
|
1075
|
+
body: new URLSearchParams({
|
|
1076
|
+
phone: env.ADMIN_PHONE_NUMBER,
|
|
1077
|
+
text: alertMessage
|
|
1078
|
+
})
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
```
|
|
1083
|
+
|
|
1084
|
+
---
|
|
1085
|
+
|
|
1086
|
+
### PASSO 5 — Consumer da Cloudflare Queue (para o wrangler.toml)
|
|
1087
|
+
|
|
1088
|
+
Adicionar configuração de consumer ao `wrangler.toml`:
|
|
1089
|
+
|
|
1090
|
+
```toml
|
|
1091
|
+
[[queues.consumers]]
|
|
1092
|
+
queue = "cdp-edge-retry"
|
|
1093
|
+
max_batch_size = 5
|
|
1094
|
+
max_batch_timeout = 60
|
|
1095
|
+
|
|
1096
|
+
# Cron Trigger para processar fila a cada minuto
|
|
1097
|
+
[[triggers.crons]]
|
|
1098
|
+
cron = "* * * * *" # A cada minuto
|
|
1099
|
+
```
|
|
1100
|
+
|
|
1101
|
+
E no `worker.js`, adicionar handler de queue:
|
|
1102
|
+
|
|
1103
|
+
```javascript
|
|
1104
|
+
// Handler de Queue (retries)
|
|
1105
|
+
export async function queue(batch, env) {
|
|
1106
|
+
for (const message of batch.messages) {
|
|
1107
|
+
const { body } = JSON.parse(message.body);
|
|
1108
|
+
const { platform, event_id, event_payload, visitor, heatScore, retry_count } = body;
|
|
1109
|
+
|
|
1110
|
+
// Redespachar para plataforma apropriada
|
|
1111
|
+
switch (platform) {
|
|
1112
|
+
case 'meta':
|
|
1113
|
+
await dispatchMetaCapi(event_payload, env, visitor, heatScore, body.clientIP, body.userAgent, body.cf, retry_count);
|
|
1114
|
+
break;
|
|
1115
|
+
case 'google':
|
|
1116
|
+
await dispatchGA4(event_payload, env, visitor, heatScore, retry_count);
|
|
1117
|
+
break;
|
|
1118
|
+
case 'tiktok':
|
|
1119
|
+
await dispatchTikTok(event_payload, env, visitor, heatScore, retry_count);
|
|
1120
|
+
break;
|
|
1121
|
+
// Adicionar outras plataformas...
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
```
|
|
1126
|
+
|
|
1127
|
+
---
|
|
1128
|
+
|
|
1129
|
+
### REGRAS DO ESCALONAMENTO
|
|
1130
|
+
|
|
1131
|
+
1. **Exponential Backoff**: Usar delays de 5min → 15min → 45min (não tentar imediatamente)
|
|
1132
|
+
2. **Max Retry = 3**: Após 3 falhas, marcar como 'failed' definitivo e disparar alerta
|
|
1133
|
+
3. **Queue-First**: Nunca retry direto na função principal → sempre enfileirar via Cloudflare Queue
|
|
1134
|
+
4. **Alert-only on Final Failure**: Não disparar WhatsApp nas 2 primeiras falhas (só na 3ª)
|
|
1135
|
+
5. **Log Everything**: Toda falha/sucesso deve ser registrada no D1 com timestamp
|
|
1136
|
+
6. **Platform-Agnostic**: Mesmo sistema de retry para todas as plataformas (Meta, Google, TikTok, Pinterest, Reddit)
|
|
1137
|
+
|
|
1138
|
+
---
|
|
1139
|
+
|
|
1140
|
+
## INPUTS RECEBIDOS
|
|
1141
|
+
|
|
1142
|
+
- JSON do Page Analyzer Agent (tecnologia detectada, páginas, tipo de funil)
|
|
1143
|
+
- JSON do Premium Tracking Intelligence Agent (eventos prioritários, engagement scoring config)
|
|
1144
|
+
- Plataformas selecionadas na FASE 0-B (Meta, Google, TikTok, etc.)
|
|
1145
|
+
- `UMBRELLA_DOMAIN` — domínio principal do funil (detectado automaticamente ou fornecido pelo usuário)
|
|
1146
|
+
- Secrets de plataformas: `META_ACCESS_TOKEN`, `GA4_API_SECRET`, `TIKTOK_ACCESS_TOKEN`
|
|
1147
|
+
- Secrets opcionais: `RESEND_API_KEY`, `WA_ACCESS_TOKEN`, `WA_PHONE_ID`
|
|
1148
|
+
|
|
1149
|
+
## RESPONSABILIDADE
|
|
1150
|
+
|
|
1151
|
+
- Gerar `wrangler.toml` completo com bindings D1, KV, R2, Queues e Cron Triggers
|
|
1152
|
+
- Gerar `schema.sql` com todas as tabelas: `events_log`, `identity_graph`, `leads`, `behavioral_events`, `webhook_events`, `user_profiles`
|
|
1153
|
+
- Gerar `worker.js` principal com endpoint `/api/tracking` (recebe eventos do browser)
|
|
1154
|
+
- Implementar Identity Graph sync, Engagement Scoring server-side e First-Party Cookie (`_cdp_uid`)
|
|
1155
|
+
- Implementar Anti-Blocking: CORS same-domain, headers limpos, sem keywords bloqueáveis
|
|
1156
|
+
- Implementar sistema de retry com Cloudflare Queues (3-Tier: imediato → 5min → 15min → 45min)
|
|
1157
|
+
- Gerar `DEPLOY.md` com guia passo a passo do zero ao funcionando
|
|
1158
|
+
|
|
1159
|
+
## SAÍDA
|
|
1160
|
+
|
|
1161
|
+
```json
|
|
1162
|
+
{
|
|
1163
|
+
"arquivos_gerados": [
|
|
1164
|
+
"wrangler.toml",
|
|
1165
|
+
"schema.sql",
|
|
1166
|
+
"worker.js",
|
|
1167
|
+
"DEPLOY.md"
|
|
1168
|
+
],
|
|
1169
|
+
"endpoints": {
|
|
1170
|
+
"tracking": "POST /api/tracking",
|
|
1171
|
+
"health": "GET /api/health",
|
|
1172
|
+
"webhooks": "POST /api/wh/{gateway}",
|
|
1173
|
+
"ticto": "POST /webhook/ticto"
|
|
1174
|
+
},
|
|
1175
|
+
"bindings_cloudflare": {
|
|
1176
|
+
"d1": "cdp-edge-db",
|
|
1177
|
+
"kv": "GEO_CACHE",
|
|
1178
|
+
"r2": "cdp-edge-logs",
|
|
1179
|
+
"queue": "cdp-edge-retry",
|
|
1180
|
+
"ai": "AI (Workers AI — LTV Prediction)"
|
|
1181
|
+
},
|
|
1182
|
+
"tabelas_d1": ["events_log", "identity_graph", "leads", "behavioral_events", "webhook_events", "user_profiles"],
|
|
1183
|
+
"retry_sistema": "Cloudflare Queues — max 3 tentativas, backoff exponencial",
|
|
1184
|
+
"anti_blocking": true,
|
|
1185
|
+
"first_party_cookie": "_cdp_uid (365 dias, HttpOnly, Secure, SameSite=Lax)",
|
|
1186
|
+
"secrets_necessarios": ["META_ACCESS_TOKEN", "GA4_API_SECRET", "TIKTOK_ACCESS_TOKEN"]
|
|
1187
|
+
}
|
|
1188
|
+
```
|