Documentation Carregando eventos

O payload canônico de evento

O SupremeTracking aceita eventos de diferentes fontes, incluindo tags de navegador e webhooks. Cada fonte é traduzida para o mesmo Supreme Event Payload canônico antes de o Node processá-la.

Essa regra de canônico-primeiro mantém o código compartilhado do pipeline genérico. Tradutores de fonte tratam as diferenças de entrada, e cada integração de destino mapeia o evento canônico para o formato da própria plataforma.

Um evento canônico representativo

Os campos opcionais exatos dependem da fonte e do contexto disponível, mas um evento normalizado tem este formato:

{
  "event_id": "evt_example_123",
  "event_name": "purchase",
  "event_time": 1730000000,
  "channel": "webevents",
  "action_source": "browser",
  "provider": "direct",
  "property_id": 1,
  "source": "browser",
  "source_platform": "direct",
  "context": {
    "stuid": "0123456789abcdef0123456789abcdef",
    "session_id": "1.1730000000.0123456789abcdef",
    "page_url": "https://shop.example.com/thank-you",
    "page_title": "Order complete",
    "referrer": "https://shop.example.com/checkout",
    "utm": {},
    "tracking_ids": {}
  },
  "user": {
    "email": "buyer@example.com",
    "phone": null
  },
  "params": {
    "transaction_id": "order_123",
    "value": 99.9,
    "currency": "USD",
    "items": []
  }
}

Essa é a representação normalizada do Node, não um corpo de requisição bruto para copiar no endpoint de ingestão.

Campos principais

  • event_id identifica o evento e viabiliza a deduplicação.
  • event_name é a ação canônica, como page_view, lead, add_to_cart ou purchase.
  • event_time é normalizado para segundos Unix.
  • channel identifica a família de ingestão: web events, webhooks, eventos internos ou imports.
  • action_source descreve onde a ação aconteceu, como browser ou server.
  • provider identifica o provedor normalizado associado à fonte.
  • property_id atribui o evento a uma Property ativa do Node. Não há fallback: um evento que não resolve para uma Property válida e ativa é rejeitado durante a validação. Ele é carimbado pelo servidor — do ?pid= do loader no navegador, da credencial numa escrita servidor-a-servidor — nunca lido do payload.
  • source e source_platform preservam contexto útil de origem.
  • context carrega página, sessão, identidade de primeira parte, atribuição e contexto de requisição quando disponíveis.
  • user contém campos normalizados do usuário. Capitalização de e-mail, formatação de telefone e aliases são normalizados pelo Node.
  • params contém valores específicos do evento, como ID de transação, value, currency, status e items. Num lead, os campos do próprio Lead vivem em params.lead.

Não adicione lógica de destino específica de plataforma a esse formato compartilhado. Meta, GA4, Google Ads, CRM e outros handlers são donos do próprio mapeamento canônico-para-plataforma dentro dos seus módulos.

Eventos de Lead

Um lead é o único evento canônico que carrega um bloco de pipeline de vendas. Quatro portas o produzem — uma chamada window.supremeSend escrita à mão, o template do Google Tag Manager, o snippet da skill de Web Events e a API de Leads — e todas as quatro convergem para o mesmo evento canônico. Não existe variante do formato abaixo por porta.

O bloco canônico de Lead

event_name: "lead"
user:   identidade — email, phone, first_name, last_name, full_name, company, job_title,
        street, street2, city, state, zip, country  (normalizados pelo Node)
params: {
  value?:    número ≥ 0 (unidades maiores, ex. 150.5)
  currency?: "BRL" — ISO-4217, 3 letras maiúsculas; obrigatório quando value vem
  lead?: {
    status?:         "new" | "contacted" | "qualified" | "won" | "lost"
    source?:         string  ^[a-z0-9][a-z0-9_.-]{0,63}$
    source_lead_id?: string  1–191 caracteres, sem caractere de controle
    custom_fields?:  objeto raso (regras abaixo)
  }
}

Pelo navegador — à mão, pela tag ou pela skill — o wire é este:

window.supremeSend({
  payload: {
    events: [{ name: 'lead', id: '<event id ≤ 50 bytes UTF-8, opcional>', data: { params: {
      value: 150.5, currency: 'BRL',
      lead: { status: 'qualified', source: 'landing-page', source_lead_id: 'form-2026-0001',
              custom_fields: { plan_interest: 'pro', employees: 12, newsletter: true } }
    } } }],
    context: { user: { email: 'maria@example.com', phone: '+5511987654321',
                       first_name: 'Maria', last_name: 'Silva',
                       company: 'Acme', job_title: 'Head of Growth' } }
  }
});

Identidade e campos do Lead são dois blocos diferentes

A identidade vai em context.user na entrada e aparece como user no evento canônico. Os campos do próprio Lead vão em params.lead.

É params.lead e não params.status porque params.status já significa status de pagamento de uma compra. O status de pipeline de um Lead precisa do próprio espaço, não de um segundo sentido para uma chave existente.

Para eventos lead, o Node lê de context.user o conjunto de identidade completo: email, phone, first_name, last_name, full_name, company, job_title, street, street2, city, state, zip e country. Para qualquer outro evento, continua lendo só email, phone, first_name e last_name — então um payload que não é lead produz exatamente o evento canônico que já produzia.

Um lead exige e-mail ou telefone. Sem nenhum dos dois, o evento é recusado.

A normalização é a do próprio Node: e-mail e os campos de nome ficam em minúsculas, e o telefone é reduzido a dígitos mantendo um + inicial, então +55 (11) 98765-4321 vira +5511987654321. Os demais campos de identidade são aparados, não passados para minúsculas.

source_lead_id é uma referência ao sistema de onde o lead veio. Nunca substitui o identificador que o Node tem para aquela pessoa, e nunca vira external_id em um payload de destino.

status só muda a linha do Lead quando vem no payload — uma captura que o omite nunca o reseta. O default new vale só na criação da linha.

custom_fields

params.lead.custom_fields carrega os campos que são seus, não do produto:

  • Um objeto JSON, nunca uma lista. As chaves atendem ^[a-z][a-z0-9_]{0,63}$, e são no máximo 50.
  • Os valores são só escalares: string, inteiro, float finito, boolean ou null. Nenhum array, nenhum objeto aninhado.
  • Tamanho: o objeto codificado em JSON com Unicode e barras não escapados precisa ter no máximo 16384 bytes.
  • Chaves reservadas, recusadas se usadas: email, phone, first_name, last_name, full_name, company, job_title, street, street2, city, state, zip, country, gender, birth_date, external_id, lead_source, ip_address, user_agent, lead_id, status, source, source_lead_id, value, currency, property_id, stuid, event_id, event_name, event_time, channel, action_source, provider, source_platform, metadata, context, user, params, attribution, utm_source, utm_medium, utm_campaign, utm_content, utm_term, gclid, gbraid, wbraid, gad_source, fbclid, fbc, fbp, msclkid, ttclid, rdt_cid, srsltid, ga_client_id, ga_session_id, ctwa_clid, delivery, session_id, page_url, referrer, created_at, updated_at, first_seen_at, last_seen_at, is_sandbox.

Numa captura seguinte o objeto é mesclado, não substituído: chave presente na nova captura sobrescreve, chave ausente dela é preservada, e chave enviada com null remove. O resultado mesclado também obedece aos dois limites — se não obedecer, a captura inteira é recusada, não sobrando nem evento nem linha.

Códigos de erro

Código Quando
lead_block_not_object params.lead presente e não é objeto
lead_status_invalid status fora do conjunto
lead_source_invalid source fora da regex
source_lead_id_invalid vazio, com mais de 191 caracteres, ou com caractere de controle
custom_fields_not_object custom_fields presente e não é objeto
custom_field_key_invalid chave fora da regex
custom_field_key_reserved chave reservada
custom_field_value_not_scalar valor array ou objeto (ou float não finito)
custom_fields_too_many mais de 50 chaves (na captura ou no resultado mesclado)
custom_fields_too_large mais de 16384 bytes (na captura ou no resultado mesclado)
value_invalid value não numérico, negativo ou não finito
currency_invalid currency fora de ^[A-Z]{3}$
currency_required value presente sem currency
identity_required nem e-mail nem telefone
event_id_too_long event id com mais de 50 bytes UTF-8

Severidade por canal

São duas regras diferentes, e há um motivo:

  • Um bloco params.lead inválido — qualquer código lead_*, source_* ou custom_* acima — recusa o evento inteiro, em todos os canais. Nada legado manda esse bloco, então nada quebra por ser estrito.
  • Um value ou currency inválido é recusado pela API de Leads e pelas superfícies (o template do Google Tag Manager, a skill de Web Events). No caminho de navegador do Node, o evento segue como hoje e a linha do Lead simplesmente não recebe valor nem moeda. value e currency já chegam em produção em payloads de lead, então não podem passar a derrubar eventos.

Paridade navegador/API

O mesmo conteúdo produz o mesmo evento canônico e o mesmo estado final do Lead pelas duas portas. O que difere é só a proveniência, e difere por construção: pela API de Leads o evento chega com channel: "webhooks", action_source: "server", provider: "leads_api" e source_platform: "leads_api", e o event_id dele deriva do header Idempotency-Key quando um é enviado.

Um Lead é uma linha por pessoa por Property. A identidade — nome, e-mail, telefone, empresa, cargo, endereço — não mora nessa linha: ela fica com as identidades e é lida por um join.

Carimbado pelo servidor, nunca pelo payload

Estes nunca são tirados de um payload de Lead:

  • property_id — do ?pid= do loader no navegador, da credencial na API de Leads. Um property_id no corpo da API é recusado como unknown_field.
  • channel, action_source e provider — atribuídos pela via de ingestão que recebeu o evento.
  • lead_id e stuid — cunhados e resolvidos pelo Node.

O que é ingerido vs. o que é armazenado

Tudo acima descreve o payload que o Node recebe. A forma como ele armazena e relê esse evento é outra. A partir da 3.7.0, os campos que viviam duplicados dentro de custom_payload passaram a morar em colunas da própria linha do evento e em tabelas próprias. O evento canônico de ingestão não mudou — o armazenamento mudou.

Quem só usa o Console não precisa fazer nada com essa distinção. Ela importa para quem lê o banco por fora: uma consulta própria, uma integração externa, um export.

Onde cada parte vive hoje

No payload de ingestão Onde fica armazenado desde a 3.7.0
context.page_url, context.user_agent, context.ip_address colunas na linha do evento
context.utm.* colunas utm_source, utm_medium, utm_campaign, utm_term, utm_content
channel, action_source, provider, source_platform colunas na linha do evento
campanha, adset, anúncio, placement, rede e tipo de campanha colunas campaign_id, adset_id, ad_id, placement, network, campaign_type
o conjunto channel_* e attribution_platform colunas na linha do evento
context.tracking_ids — entre eles gclid, gbraid, wbraid, gad_source, fbclid, fbp, fbc, ga_client_id, ga_session_id, ttclid e msclkid tabela attribution_identifiers
user tabela identities
params.lead (eventos lead) a linha de Lead da Property — uma por pessoa — com a identidade continuando em identities
os parâmetros de uma compra purchases.platform_data
a sessão tabela sessions, mais as colunas de sessão na linha do evento
params, page.title, page.referrer, screen, ingest_host, metadata continuam em custom_payload
context.cookies (o jar cru do navegador) não é persistido — veja abaixo

custom_payload virou resíduo, não o evento inteiro

Antes da 3.7.0, ler custom_payload era ler o evento. Hoje essa coluna guarda apenas o que não tem outro lugar para morar: params, page.title, page.referrer, screen, ingest_host e metadata. Quando não sobra nada, ela fica NULL.

Quem consulta custom_payload diretamente recebe só essa última linha da tabela acima — não o evento. Os parâmetros de uma compra nem isso: eles vivem em purchases.platform_data, que é a cópia autoritativa e acompanha mudanças de status, enquanto a cópia congelada dentro do evento não acompanhava.

Se a sua integração ou o seu relatório lê custom_payload esperando o evento inteiro, é a leitura que precisa mudar de fonte.

O Console remonta o evento na leitura

A aba Raw JSON do detalhe do evento não imprime custom_payload. Ela mostra o evento remontado a partir de todas as fontes da tabela acima — as colunas da linha, attribution_identifiers, identities, purchases e a sessão. É por isso que ela mostra mais do que custom_payload contém, e é a referência de o que o Node guardou sobre aquele evento.

O que fica do jar de cookies

O jar de cookies cru é dado de trânsito: chega no bootstrap, serve o pipeline e não é gravado em lugar nenhum. Antes de evaporar, o que carregava identidade é destilado e persistido:

  • _fbp, lido do cookie de mesmo nome.
  • _fbc, lido do cookie quando ele existe e sintetizado a partir do parâmetro ?fbclid= da URL quando não existe — ou seja, parte dos valores guardados nunca foi um cookie.
  • ga_client_id e ga_session_id, extraídos dos cookies _ga e _ga_*.
  • stuid, lido do cookie próprio do Node.

Cookies que não correspondem a nenhum identificador conhecido — os de terceiros, os do seu próprio site — não são persistidos em lugar nenhum. attribution_identifiers é um registro de identificadores que às vezes vêm de cookies, não um espelho do jar do navegador.

Guardar o identificador em vez do cookie é uma decisão de projeto: o Node fica com o que serve à atribuição e não acumula o resto do navegador do seu visitante.

Os cinco estágios do pipeline

Depois da tradução, EventPipeline::process() roda estes estágios em ordem:

  1. Filter aplica regras de rejeição antes de qualquer outro processamento.
  2. Validation verifica se o evento canônico é aceitável.
  3. Enrichment resolve e adiciona identidade e contexto disponíveis localmente.
  4. Persistence armazena o evento resultante no Node.
  5. Forwarding passa o evento canônico para cada handler de integração ativo.

Como Persistence roda antes de Forwarding, um evento pode estar visível no Console mesmo quando um destino depois o rejeita ou falha em recebê-lo. Use os logs do Node e o próprio diagnóstico do destino para investigar essa segunda metade.

Do GTM a um destino

O template atual do GTM monta um envelope de navegador e chama window.supremeSend. O Node traduz essa entrada para o formato canônico acima. Saiba como configurar a fonte em Connect Google Tag Manager (em inglês).

Os módulos de Forwarding então leem o evento canônico e montam os payloads de destino. Saiba como os módulos são separados em Visão geral de integrações.

Para inspecionar o resultado no seu próprio Node, verifique seu primeiro evento.