# Modelo: Tráfego Direto (Cloudflare Native)
> Infoproduto / Lançamento sem formulário de lead na página — usuário clica direto no botão e vai para checkout externo.
> Plataformas: GA4 · Google Ads · Meta (Facebook) Ads · TikTok Ads
> Infraestrutura: **Cloudflare Workers + D1** (100% Native)

**Quando usar este modelo:**
- Página de vendas / VSL sem formulário (sem captura de email/telefone na própria página)
- Compra realizada em plataforma externa (Hotmart, Kiwify, Kirvano, Eduzz, etc.) que envia webhook
- Fluxo: Visitante chega → `track_user_id` gerado → dados de sessão salvos no D1 → clica no botão → vai para checkout com `user_id` na URL → webhook de compra chega com o `user_id` → D1 recupera fbp/fbc/ttp/geo

**Diferença dos outros modelos:**
- **Nenhum dado de usuário (email/telefone) é capturado na página** — usa `track_user_id` anônimo
- Prefixo `cdp_uid` para o identificador gerado no primeiro acesso
- Tags de PageView e InitiateCheckout são enviadas via Fetch para o Worker (não pixels diretos)
- URL modificada ao clicar no botão de compra: passa `track_user_id` como parâmetro
- **D1** faz lookup pelo `user_id` (não por email)
- **Sem Google Ads Offline Conversion** (requer email capturado antes da compra)

---

## 🏗️ ARQUITETURA TÉCNICA (Quantum Tier)

```
┌─────────────────────────────────────────────────────────────┐
│ Browser: Página de Vendas / VSL                    │
│
│  1. cdpTrack.generateId() → gera cdp_uid                │
│  2. cdpTrack.track('PageView', {...}) → Worker          │
│  3. Botão Clique → inject cdp_uid na URL                │
│  4. cdpTrack.track('InitiateCheckout', {...}) → Worker  │
└─────────────────────────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────────┐
│ Cloudflare Worker (Quantum Tier)                            │
│
│  Endpoint: /track (Same-Domain)                 │
│  - Recebe eventos do browser                            │
│  - Salva sessão no D1 (fbp, fbc, ttp, UTMs)       │
│  - Despacha para APIs: Meta, GA4, TikTok              │
│
│  Endpoint: /api/wh/{plataforma}                       │
│  - Recebe webhooks de compra (Hotmart, Kiwify...)       │
│  - D1 lookup pelo user_id                              │
│  - Envia Purchase CAPI/MP v1.3                        │
└─────────────────────────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────────┐
│ Cloudflare D1 (SQLite)                               │
│
│  Tabela: identity_graph                                │
│  - cdp_uid (primary key)                                │
│  - fbp, fbc, ttp (cookies)                            │
│  - utm_source, utm_medium, utm_campaign               │
│  - ip, user_agent                                      │
│  - first_seen, last_seen                                │
└─────────────────────────────────────────────────────────────┘
```

---

## 📘 EVENTOS PRINCIPAIS

| Evento | Gatilho | Dados Enviados |
|---|---|---|
| **PageView** | Carregamento da página | IP, User-Agent, URL, Cookies (fbp, fbc, ttp) |
| **InitiateCheckout** | Clique no botão de compra (link externo) | `cdp_uid`, `checkout_url`, `utm` (se disponível) |
| **Purchase** | Webhook da plataforma (via Worker) | `transaction_id`, `value`, `currency`, `cdp_uid`, D1 lookup data |

---

## 🛠️ PASSO 1: CONFIGURAÇÃO DO SITE

### 1.1 SDK de Rastreamento (Header)
```html
<!-- Inserir no <head> -->
<script src="/js/cdpTrack.js" async></script>
<script>
  window.cdpConfig = {
    workerUrl: '/track',  // Same-Domain (furtivo)
    metaId: 'SEU_PIXEL_ID',
    ga4Id: 'G-XXXXXXXX',
    tiktokId: 'C4XXXXXXXXXXXXXXX',
  };
</script>
```

### 1.2 Geração de cdp_uid (First Access)
```javascript
// Inserir no início do <body> (primeiro script)
<script>
(function() {
  // Gera cdp_uid se não existir (1 ano de expiração)
  const existing = document.cookie.match(/cdp_uid=([^;]+)/);
  if (!existing) {
    const uid = 'usr_' + Math.random().toString(36).substring(2, 10) + Date.now();
    const expiry = new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toUTCString();
    document.cookie = `cdp_uid=${encodeURIComponent(uid)}; expires=${expiry}; path=/; SameSite=Lax`;
  }
})();
</script>
```

### 1.3 PageView (Carregamento da Página)
```javascript
// Após carregamento inicial
<script>
window.addEventListener('DOMContentLoaded', async () => {
  const cdp_uid = document.cookie.match(/cdp_uid=([^;]+)/)?.[1] || '';

  await cdpTrack.track('PageView', {
    cdp_uid,
    page_url: window.location.href,
    page_title: document.title
  });
});
</script>
```

### 1.4 InitiateCheckout + Injeção de cdp_uid
```javascript
// Captura cliques em botões de checkout externo
<script>
document.addEventListener('click', async (e) => {
  const link = e.target.closest('a[href]');
  if (!link) return;

  const href = link.href.toLowerCase();
  const platforms = ['hotmart.com', 'kiwify.com', 'kirvano.com', 'eduzz.com'];
  const isCheckout = platforms.some(p => href.includes(p));

  if (isCheckout) {
    e.preventDefault(); // Previne redirecionamento imediato

    const cdp_uid = document.cookie.match(/cdp_uid=([^;]+)/)?.[1] || '';

    // Dispara InitiateCheckout
    await cdpTrack.track('InitiateCheckout', {
      cdp_uid,
      checkout_platform: platforms.find(p => href.includes(p)),
      original_url: href
    });

    // Injeta cdp_uid na URL (xcod/sck/src conforme plataforma)
    const url = new URL(href);

    if (href.includes('hotmart.com')) {
      url.searchParams.set('xcod', cdp_uid);
    } else if (href.includes('kiwify.com')) {
      url.searchParams.set('sck', cdp_uid);
    } else if (href.includes('kirvano.com')) {
      url.searchParams.set('src', cdp_uid);
    }

    // Redireciona para o checkout
    window.location.href = url.toString();
  }
}, { capture: true });
</script>
```

---

## ⚡ PASSO 2: SERVIDOR (CLOUDFLARE WORKER)

### 2.1 Handler Principal (Same-Domain)
```javascript
// worker.js

export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);
    const cors = {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'POST, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type',
    };

    if (request.method === 'OPTIONS') {
      return new Response(null, { headers: cors });
    }

    // Endpoint principal de tracking
    if (url.pathname === '/track') {
      return handleTracking(request, env, ctx);
    }

    // Endpoint de webhooks de plataformas
    if (url.pathname.startsWith('/api/wh/')) {
      return handleWebhook(request, env, url);
    }

    return new Response('Not Found', { status: 404 });
  }
};

async function handleTracking(request, env, ctx) {
  const body = await request.json();
  const cf = request.cf || {};
  const ip = cf.colo === 'XX' ? '8.8.8.8' : request.headers.get('CF-Connecting-IP');

  // Captura cookies do request
  const fbp = request.headers.get('Cookie')?.match(/_fbp=([^;]+)/)?.[1];
  const fbc = request.headers.get('Cookie')?.match(/_fbc=([^;]+)/)?.[1];
  const ttp = request.headers.get('Cookie')?.match(/_ttp=([^;]+)/)?.[1];

  const sessionData = {
    cdp_uid: body.cdp_uid,
    fbp,
    fbc,
    ttp,
    ip,
    user_agent: request.headers.get('User-Agent'),
    utm_source: getUtm('utm_source'),
    utm_medium: getUtm('utm_medium'),
    utm_campaign: getUtm('utm_campaign'),
    utm_content: getUtm('utm_content'),
    utm_term: getUtm('utm_term'),
  };

  // Salva/Atualiza no D1
  await saveSession(env.DB, sessionData);

  // Dispatch não-bloqueante para APIs
  ctx.waitUntil(dispatchEvents(body, sessionData, env));

  return new Response(JSON.stringify({ success: true }), {
    headers: { ...cors, 'Content-Type': 'application/json' }
  });
}

async function saveSession(DB, data) {
  await DB.prepare(`
    INSERT INTO identity_graph (cdp_uid, fbp, fbc, ttp, ip, user_agent,
      utm_source, utm_medium, utm_campaign, utm_content, utm_term, last_seen)
    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    ON CONFLICT(cdp_uid) DO UPDATE SET
      fbp = excluded.fbp,
      fbc = excluded.fbc,
      ttp = excluded.ttp,
      ip = excluded.ip,
      last_seen = datetime('now')
  `).bind(
    data.cdp_uid, data.fbp, data.fbc, data.ttp, data.ip, data.user_agent,
    data.utm_source, data.utm_medium, data.utm_campaign, data.utm_content, data.utm_term
  ).run();
}

async function dispatchEvents(eventBody, sessionData, env) {
  const { event_name } = eventBody;

  if (event_name === 'PageView') {
    await Promise.all([
      dispatchMeta(event_name, sessionData, env, {}),
      dispatchGA4('page_view', sessionData, env, {}),
      dispatchTikTok('PageView', sessionData, env, {}),
    ]);
  } else if (event_name === 'InitiateCheckout') {
    await Promise.all([
      dispatchMeta('InitiateCheckout', sessionData, env, {
        content_name: eventBody.checkout_platform
      }),
      dispatchGA4('begin_checkout', sessionData, env, {}),
      dispatchTikTok('InitiateCheckout', sessionData, env, {}),
    ]);
  }
}
```

### 2.2 Webhook Handler (Hotmart, Kiwify, Kirvano)
```javascript
async function handleWebhook(request, env, url) {
  const body = await request.json();

  // Extrai cdp_uid conforme plataforma
  let cdp_uid;

  if (url.pathname.includes('hotmart')) {
    cdp_uid = body.data?.purchase?.order_bump_parent_purchase || body.xcod;
  } else if (url.pathname.includes('kiwify')) {
    cdp_uid = body.order_ref || body.sck;
  } else if (url.pathname.includes('kirvano')) {
    cdp_uid = body.src;
  }

  if (!cdp_uid) {
    console.error('cdp_uid not found in webhook');
    return new Response('Missing cdp_uid', { status: 400 });
  }

  // D1 Lookup: recupera dados da sessão original
  const session = await env.DB.prepare(
    'SELECT * FROM identity_graph WHERE cdp_uid = ?'
  ).bind(cdp_uid).first();

  if (!session) {
    console.error('Session not found in D1');
    return new Response('Session not found', { status: 404 });
  }

  const purchaseData = {
    transaction_id: body.data?.purchase?.transaction || body.transaction_id,
    value: body.data?.purchase?.price?.value || body.value,
    currency: body.data?.purchase?.price?.currency_value || body.currency,
    email: body.data?.buyer?.email || body.email,
    phone: body.data?.buyer?.checkout_phone || body.phone,
    ...session // D1 data (fbp, fbc, ttp, UTMs, IP)
  };

  // Dispatch Purchase para APIs
  await Promise.all([
    dispatchMeta('Purchase', purchaseData, env, {}),
    dispatchGA4('purchase', purchaseData, env, {
      transaction_id: purchaseData.transaction_id,
      value: purchaseData.value,
      currency: purchaseData.currency
    }),
    dispatchTikTok('CompletePayment', purchaseData, env, {}),
  ]);

  return new Response('OK', { status: 200 });
}
```

### 2.3 Meta CAPI v25.0
```javascript
async function dispatchMeta(eventName, data, env, customData = {}) {
  if (!env.META_ACCESS_TOKEN || !env.META_PIXEL_ID) return;

  const emailHash = data.email ? await sha256(data.email.toLowerCase().trim()) : null;
  const phoneHash = data.phone ? await sha256('55' + data.phone.replace(/\D/g, '')) : null;

  const payload = {
    data: [{
      event_name: eventName,
      event_time: Math.floor(Date.now() / 1000),
      event_id: data.transaction_id || crypto.randomUUID(),
      event_source_url: data.page_url || '',
      action_source: 'website',
      user_data: {
        em: emailHash ? [emailHash] : undefined,
        ph: phoneHash ? [phoneHash] : undefined,
        fbp: data.fbp,
        fbc: data.fbc,
        client_ip_address: data.ip,
        client_user_agent: data.user_agent,
        external_id: data.cdp_uid ? [await sha256(data.cdp_uid)] : undefined,
      },
      custom_data: {
        value: data.value || 0,
        currency: data.currency || 'BRL',
        ...customData
      }
    }]
  };

  const res = await fetch(
    `https://graph.facebook.com/v25.0/${env.META_PIXEL_ID}/events?access_token=${env.META_ACCESS_TOKEN}`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload)
    }
  );

  if (!res.ok) {
    console.error('Meta CAPI Error:', await res.text());
  }
}
```

### 2.4 GA4 Measurement Protocol
```javascript
async function dispatchGA4(eventName, data, env, eventData = {}) {
  if (!env.GA4_ID || !env.GA4_API_SECRET) return;

  const payload = {
    client_id: data.cdp_uid, // Usa cdp_uid como client_id
    user_id: data.cdp_uid,
    events: [{
      name: eventName,
      params: {
        page_location: data.page_url || '',
        page_title: data.page_title || '',
        ...eventData
      }
    }]
  };

  const res = await fetch(
    `https://www.google-analytics.com/mp/collect?measurement_id=${env.GA4_ID}&api_secret=${env.GA4_API_SECRET}`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload)
    }
  );

  if (!res.ok) {
    console.error('GA4 Error:', await res.text());
  }
}
```

### 2.5 TikTok Events API v1.3
```javascript
async function dispatchTikTok(eventName, data, env, eventData = {}) {
  if (!env.TIKTOK_PIXEL_ID || !env.TIKTOK_ACCESS_TOKEN) return;

  const payload = {
    pixel_code: env.TIKTOK_PIXEL_ID,
    event: eventName,
    event_id: data.transaction_id || crypto.randomUUID(),
    timestamp: new Date().toISOString(),
    context: {
      ad: {
        callback: data.ttp
      },
      page: {
        url: data.page_url || '',
        referrer: document.referrer || ''
      },
      user: {
        ip_address: data.ip,
        user_agent: data.user_agent,
        external_id: data.cdp_uid
      }
    },
    properties: {
      value: data.value || 0,
      currency: data.currency || 'BRL',
      ...eventData
    }
  };

  const res = await fetch(
    'https://business-api.tiktok.com/open_api/v1.3/event/track/',
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Access-Token': env.TIKTOK_ACCESS_TOKEN
      },
      body: JSON.stringify(payload)
    }
  );

  if (!res.ok) {
    console.error('TikTok Error:', await res.text());
  }
}
```

### 2.6 Helpers
```javascript
async function sha256(value) {
  if (!value) return null;
  const encoder = new TextEncoder();
  const data = encoder.encode(value);
  const hash = await crypto.subtle.digest('SHA-256', data);
  return Array.from(new Uint8Array(hash)).map(b => b.toString(16).padStart(2, '0')).join('');
}

function getUtm(param) {
  const urlParams = new URLSearchParams(window.location.search);
  return urlParams.get(param) || '';
}
```

---

## 📊 PASSO 3: SCHEMA D1

```sql
-- Tabela principal (já definida no Server Agent)
CREATE TABLE IF NOT EXISTS identity_graph (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  cdp_uid TEXT UNIQUE NOT NULL,
  fbp TEXT,
  fbc TEXT,
  ttp TEXT,
  ga_client_id TEXT,
  external_id TEXT,
  first_utm TEXT,
  heat_score_avg INTEGER DEFAULT 0,
  visit_count INTEGER DEFAULT 1,
  last_seen TEXT DEFAULT (datetime('now')),
  created_at TEXT DEFAULT (datetime('now'))
);

CREATE INDEX IF NOT EXISTS idx_cdp_uid ON identity_graph(cdp_uid);
CREATE INDEX IF NOT EXISTS idx_fbp ON identity_graph(fbp);
CREATE INDEX IF NOT EXISTS idx_fbc ON identity_graph(fbc);
```

---

## ✅ CHECKLIST DE VERIFICAÇÃO

### Browser
- [ ] cdp_uid gerado no primeiro acesso e persistido por 1 ano
- [ ] PageView disparado via Fetch para `/track`
- [ ] InitiateCheckout disparado antes do redirecionamento
- [ ] cdp_uid injetado na URL do checkout (xcod/sck/src)
- [ ] UTMs capturados e salvos no D1

### Cloudflare Worker
- [ ] Endpoint `/track` configurado como Route no Cloudflare
- [ ] Endpoint `/api/wh/{plataforma}` para webhooks
- [ ] Salvar sessão no D1 com todos os cookies
- [ ] D1 lookup pelo cdp_uid ao receber webhook
- [ ] Meta CAPI v25.0 endpoint correto
- [ ] TikTok Events API v1.3 endpoint correto
- [ ] GA4 Measurement Protocol configurado

### Plataformas Externas
- [ ] Hotmart webhook configurado para `/api/wh/hotmart`
- [ ] Kiwify webhook configurado para `/api/wh/kiwify`
- [ ] Kirvano webhook configurado para `/api/wh/kirvano`
- [ ] cdp_uid sendo recebido no webhook (xcod/sck/src)

---

## 🔄 FLUXO COMPLETO

```
1. Visitante acessa a página de vendas
   └── JS: gera cdp_uid → salva cookie (1 ano)
   └── JS: dispara PageView → /track
   └── Worker: salva sessão no D1 (fbp, fbc, ttp, UTMs, IP)
   └── Worker: dispatch → Meta, GA4, TikTok (PageView)

2. Visitante clica no botão de compra
   └── JS: intercepta clique → previne redirecionamento
   └── JS: dispara InitiateCheckout → /track
   └── Worker: atualiza sessão no D1
   └── Worker: dispatch → Meta, GA4, TikTok (InitiateCheckout)
   └── JS: injeta cdp_uid na URL (xcod/sck/src)
   └── JS: redireciona para checkout externo

3. Usuário compra na plataforma externa (Hotmart/Kiwify/Kirvano)
   └── Plataforma envia webhook → /api/wh/{plataforma}
   └── Worker: extrai cdp_uid do webhook (xcod/sck/src)
   └── Worker: D1 lookup → recupera fbp, fbc, ttp, UTMs, IP
   └── Worker: dispatch → Meta Purchase CAPI (v25.0)
   └── Worker: dispatch → GA4 Purchase (MP)
   └── Worker: dispatch → TikTok CompletePayment (v1.3)
```

---

## ⚠️ NOTAS IMPORTANTES

- **Mesmo-Domain Protocol**: O Worker DEVE roda no mesmo domínio do site para evitar CORS e bloqueios
- **Deduplicação**: O `event_id` deve ser único para cada evento (usar `crypto.randomUUID()` ou similar)
- **Hashing**: SHA256 deve ser feito no Worker usando `crypto.subtle.digest` (WebCrypto API)
- **Privacidade**: Nunca logar email/telefone em texto claro no console do Worker
- **Webhook Seguro**: Validar `cdp_uid` existe no D1 antes de processar purchase

---

## 🚀 DEPLOY DO ZERO (Resumo)

1. **Criar Worker**: `npx wrangler init cdp-edge-td`
2. **Configurar D1**: `npx wrangler d1 create cdp-edge-td-db`
3. **Aplicar Schema**: `npx wrangler d1 execute cdp-edge-td-db --file=schema.sql`
4. **Configurar Secrets**:
   ```bash
   wrangler secret put META_ACCESS_TOKEN
   wrangler secret put META_PIXEL_ID
   wrangler secret put GA4_ID
   wrangler secret put GA4_API_SECRET
   wrangler secret put TIKTOK_PIXEL_ID
   wrangler secret put TIKTOK_ACCESS_TOKEN
   ```
5. **Deploy**: `npx wrangler deploy`
6. **Configurar Route**: Cloudflare Dashboard → Workers & Pages → Routes → `seusite.com/api/*` → Worker

---

*Este modelo é 100% Cloudflare Native e elimina qualquer dependência de GTM, Stape ou servidores externos.*
