# Integração — guia para o desenvolvedor Este documento é tudo o que você precisa para integrar uma plataforma de vendas ao **UTM Intelligence**. Não é preciso conhecer o sistema por dentro. **Painel:** https://utmfy.hnvag0.easypanel.host Ao final da integração, cada venda que entrar vai: 1. aparecer no painel em segundos, já atribuída à campanha/anúncio que a gerou; 2. disparar notificação no celular do dono (push), Telegram, Discord ou e-mail. --- ## Visão geral São duas peças independentes. Você pode fazer só a primeira e o sistema já funciona — a segunda é o que permite saber **de qual anúncio** veio cada venda. | Peça | O que faz | Onde vive | |---|---|---| | **1. Webhook de vendas** | Manda a venda para o sistema | Servidor da plataforma de checkout | | **2. Script de rastreamento** | Identifica o visitante e guarda a origem | Página de vendas do cliente | --- ## Onde pegar as credenciais No painel, em **Configurações → Instalação**: | Credencial | Formato | Para que serve | |---|---|---| | Chave pública | 32 caracteres | Vai no script de rastreamento (pode ficar exposta) | | Chave secreta | 48 caracteres | Vai na URL do webhook e na API. **Nunca exponha no navegador.** | --- # Parte 1 — Webhook de vendas ## Endpoint ``` POST https://utmfy.hnvag0.easypanel.host/webhook/generic/{CHAVE_SECRETA} Content-Type: application/json ``` Responde `200 {"ok":true}` imediatamente e processa em segundo plano — a venda aparece no painel em poucos segundos. Se receber qualquer coisa diferente de `200`, **reenvie**. ## Corpo da requisição ```json { "order_id": "PED-12345", "status": "approved", "payment_method": "pix", "currency": "BRL", "total": 497.00, "fee": 44.68, "cost": 40.00, "product": { "id": "curso-completo", "name": "Curso Completo" }, "customer": { "email": "cliente@email.com", "name": "Maria Silva", "country": "BR" }, "tracking": { "vid": "8f14e45f-ceea-467a-9d1a-2f3b4c5d6e7f", "sid": "3c59dc04-8e88-4c04-9f31-b2f0e1a2c3d4", "utm_source": "facebook", "utm_medium": "cpc", "utm_campaign": "Black Friday", "utm_content": "criativo-video-01", "click_id": "IwAR3xK9..." }, "items": [ { "id": "curso-completo", "name": "Curso Completo", "quantity": 1, "price": 497.00 }, { "id": "bump-planilhas", "name": "Planilhas", "quantity": 1, "price": 47.00, "is_bump": true } ], "created_at": "2026-08-01T14:32:10-03:00", "approved_at": "2026-08-01T14:32:45-03:00" } ``` ### Campos | Campo | Obrigatório | Observação | |---|:---:|---| | `order_id` | **sim** | ID do pedido na sua plataforma. É a chave de idempotência. | | `status` | **sim** | Ver tabela de status abaixo. | | `total` | **sim** | **Na unidade da moeda**: `497.00` = R$ 497,00. Ver aviso abaixo. | | `payment_method` | não | `pix`, `credit_card`, `boleto`, `debit_card`, `paypal`… | | `currency` | não | Padrão `BRL`. | | `fee` | não | Taxa do gateway. Entra no cálculo de lucro. | | `cost` | não | Custo do produto. Entra no cálculo de lucro. | | `product.id` / `product.name` | não | Alimenta o ranking de produtos. | | `customer.email` | não | Usado para reconhecer o comprador entre dispositivos. | | `tracking.*` | não | Ver Parte 2. É o que liga a venda ao anúncio. | | `items[]` | não | Detalhe por item; use `is_bump` para order bumps. | | `created_at` / `approved_at` | não | ISO 8601 ou timestamp Unix. Padrão: agora. | > ### ⚠️ O erro mais comum: valor em centavos > > `total` é lido **na unidade da moeda**. `497.00` e `497` são ambos > **R$ 497,00** — não R$ 4,97. > > Se a sua plataforma trabalha em centavos, **divida por 100 antes de enviar**: > `"total": 49700` seria interpretado como R$ 49.700,00. ### Status aceitos | Envie | Significado | Sinônimos aceitos | |---|---|---| | `approved` | Pagamento confirmado | `paid`, `completed`, `succeeded`, `aprovado`, `pago` | | `pending` | Aguardando pagamento | (padrão quando não reconhecido) | | `refused` | Recusado | `declined`, `failed`, `recusado` | | `refunded` | Reembolsado | `refund`, `estornado`, `reembolsado` | | `chargeback` | Contestação | `dispute` | | `canceled` | Cancelado | `cancelled`, `cancelado` | | `expired` | Expirado | `expirado` | Só `approved` conta como faturamento. ## Idempotência e ciclo de vida **Reenvie o mesmo `order_id` a cada mudança de status.** O sistema atualiza o pedido existente em vez de duplicar. Fluxo típico de um Pix: ``` 1. Pix gerado → {"order_id":"PED-1","status":"pending", "total":497.00} 2. Pix pago → {"order_id":"PED-1","status":"approved", "total":497.00} 3. Reembolso → {"order_id":"PED-1","status":"refunded", "total":497.00} ``` Duas proteções que você não precisa implementar do seu lado: - **Reenvio duplicado** não gera venda dupla nem alerta repetido. - **Webhook fora de ordem** não regride o status: se o "aprovado" chegar depois do "reembolsado", o pedido continua reembolsado. ## Assinatura (recomendado) Para o sistema recusar requisições forjadas, configure um segredo compartilhado e envie o HMAC do corpo cru: ``` X-Signature: hmac_sha256(corpo_cru_da_requisicao, SEGREDO) ``` ```php $body = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); $signature = hash_hmac('sha256', $body, $segredo); // envie $body como corpo e $signature no header X-Signature ``` Sem segredo configurado no painel, a verificação é ignorada — útil em testes. ## Testando ```bash curl -X POST "https://utmfy.hnvag0.easypanel.host/webhook/generic/SUA_CHAVE_SECRETA" \ -H "Content-Type: application/json" \ -d '{ "order_id": "TESTE-001", "status": "approved", "payment_method": "pix", "total": 497.00, "product": { "id": "teste", "name": "Produto de Teste" }, "customer": { "email": "teste@exemplo.com" } }' ``` Esperado: `{"ok":true}`, e em poucos segundos a venda aparece no painel com o alerta/push correspondente. --- # Parte 2 — Rastreamento e atribuição Sem esta parte, todas as vendas aparecem como **tráfego direto**. Com ela, você sabe qual anúncio pagou cada venda. ## Passo 1 — Instalar o script na página de vendas ```html ``` Sozinho, ele já: - registra as visitas e as sessões; - captura `utm_source`, `utm_medium`, `utm_campaign`, `utm_content`, `utm_term`; - captura os IDs de clique: `fbclid`, `gclid`, `gbraid`, `wbraid`, `ttclid`, `msclkid`, `twclid`, `epik`, `sccid`; - guarda essa origem por até 13 meses (cookie de 1ª parte + localStorage); - deduz a origem de tráfego orgânico e social quando não há UTM; - **acrescenta `utmi_vid` e `utmi_sid` aos links que apontam para o checkout.** ## Passo 2 — Levar os identificadores até o webhook É o passo que fecha o ciclo. Existem três cenários — use o que se aplica: ### Cenário A — O checkout é uma plataforma externa (Hotmart, Kiwify…) O script já anexa `utmi_vid` e `utmi_sid` na URL do checkout. Configure a plataforma para repassar esses parâmetros no webhook, no campo de rastreamento que ela oferecer (`sck`, `src`, `tracking`, `custom_fields`…): ```json "tracking": { "vid": "", "sid": "" } ``` ### Cenário B — Checkout próprio Leia os identificadores no navegador e envie junto com o pedido: ```js const ids = utmi('ids'); // { vid: "...", sid: "..." } const utms = utmi('params'); // { utm_source: "...", fbclid: "...", ... } fetch('/api/criar-pedido', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...dadosDoPedido, tracking: { ...ids, ...utms } }) }); ``` Guarde `tracking` junto do pedido no seu banco e repasse no webhook. ### Cenário C — Não dá para passar nada Envie ao menos o e-mail do comprador em `customer.email`. O sistema tenta casar com um visitante conhecido (funciona inclusive entre celular e desktop). > **Se a venda chegar antes do rastreamento**, o sistema guarda os > identificadores e corrige a atribuição sozinho minutos depois. Você não > precisa controlar essa ordem. ## Eventos opcionais Melhoram o funil e a detecção de queda de conversão: ```js utmi('track', 'initiate_checkout'); // entrou no checkout utmi('track', 'lead', { origem: 'formulario' }); // lead capturado utmi('track', 'add_to_cart', {}, 197.00); // com valor utmi('identify', 'cliente@email.com'); // liga o visitante ao e-mail ``` Ou sem JavaScript, por atributo: ```html ``` `initiate_checkout` também é detectado automaticamente em links cuja URL contenha `checkout`, `carrinho`, `pagamento` ou `pay.`. --- # Parte 3 — API (opcional) Base `https://utmfy.hnvag0.easypanel.host/api/v1` · Header `Authorization: Bearer {CHAVE_SECRETA}` | Endpoint | Uso | |---|---| | `POST /orders` | Criar/atualizar venda sem webhook (mesmo corpo da Parte 1) | | `POST /ad-spend` | Importar investimento do Meta/Google Ads (lote de até 5.000 linhas) | | `GET /metrics` | Métricas consolidadas do período | | `GET /ranking` | Ranking por anúncio, campanha, produto, origem… | | `GET /orders` | Listar vendas | | `GET /health` | Monitoramento (sem autenticação) | Importar gasto de mídia é o que habilita ROAS, ROI, CAC e a lista de anúncios que estão queimando verba: ```bash curl -X POST "https://utmfy.hnvag0.easypanel.host/api/v1/ad-spend" \ -H "Authorization: Bearer SUA_CHAVE_SECRETA" \ -H "Content-Type: application/json" \ -d '{ "rows": [{ "date": "2026-08-01", "source": "facebook", "campaign_id": "1202100001", "campaign_name": "Black Friday", "ad_id": "1202100001-ad1", "ad_name": "Criativo A", "spend": 450.00, "impressions": 38000, "clicks": 620 }] }' ``` Idempotente por dia + anúncio: reenviar o mesmo dia atualiza, não duplica. Referência completa em [`API.md`](API.md). --- # Códigos de resposta | Código | O que significa | O que fazer | |---|---|---| | `200` | Recebido | Nada | | `401` | Chave inválida | Conferir a chave secreta na URL/header | | `404` | Gateway não registrado | Usar `/webhook/generic/…` | | `422` | Corpo inválido | Ler o campo `error` da resposta | | `429` | Excesso de requisições | Aguardar e reenviar | | `5xx` | Falha do servidor | **Reenviar** — o sistema é idempotente | --- # Checklist de entrega - [ ] Webhook disparando em **todas** as mudanças de status, não só na aprovação - [ ] `total` na unidade da moeda (`497.00`), não em centavos - [ ] Mesmo `order_id` reenviado a cada atualização - [ ] Reenvio automático quando a resposta não for `200` - [ ] Script de rastreamento na página de vendas - [ ] `utmi_vid` / `utmi_sid` chegando no `tracking` do webhook - [ ] `customer.email` enviado (atribuição entre dispositivos) - [ ] `fee` e `cost` preenchidos, se houver (necessários para lucro real) - [ ] Venda de teste aparecendo no painel --- # Perguntas frequentes **A venda apareceu como "direct". Por quê?** Nenhum identificador chegou no `tracking`. Confira o Cenário A/B da Parte 2. Aguarde alguns minutos: se o rastreamento chegar depois, o sistema corrige sozinho. **Posso mandar o mesmo pedido várias vezes?** Sim — é o comportamento esperado. A chave é o `order_id`. **O valor apareceu errado (100× maior ou menor).** Unidade do `total`. Envie `497.00`, não `49700`. **Preciso do script se o checkout é externo?** Sim. É ele que identifica o visitante antes de a venda existir. Sem ele, não há como saber de qual anúncio veio. **Reembolso desconta do faturamento?** Sim. Reenvie o mesmo `order_id` com `status: "refunded"`.