Origem de Campanha no CRM Próprio: UTM e GCLID no Lead

Como gravar UTM, GCLID e first/last touch no seu CRM: schema, cookie first-party, API de lead e ponte para Offline Conversion — sem depender de HubSpot ou Salesforce.

Contexto

Você tem CRM próprio. O lead entra com nome e e-mail. Campo de origem: Direct — ou NULL.

Mídia pergunta qual campanha gerou o SQL. O Ads otimiza form fill. Vendas fecha o deal. Ninguém consegue ligar o clique ao Closed-Won porque a atribuição nunca entrou no seu modelo de dados.

Em CRM de prateleira o jogo é mapear hidden field no vendor. No CRM próprio você controla o schema e a API: o padrão-ouro não é “hack de formulário” — é contrato de attribution no INSERT do lead (com persistência first-party na jornada).

Medir o submit no GTM é outro post (formulários e iFrames). Contagem GA4 vs CRM é outro (por que nunca batem). Aqui: origem de campanha dentro do seu CRM.

Problema

Sintoma Causa típica
Lead sem UTM/gclid API grava só PII; attribution não está no payload
Origem some após multi-página Query string só na landing; nada no cookie/session
Volta em 10 dias = Direct Cookie só via JS; Safari ITP matou em 7d/24h
First e last misturados Uma coluna só, sobrescrita a cada visita
OCT nunca sobe no Ads gclid não chega no status Closed-Won / export
Fonte fragmentada Facebook vs facebook; espaços → %20

Hidden HTML ajuda como fallback. No produto seu, a fonte da verdade é o backend.

O que gravar no schema

Trate attribution como entidade (ou colunas tipadas no leads), não como texto livre “Observações”:

Campo Tipo Notas
utm_source, utm_medium, utm_campaign, utm_term, utm_content string normalizada minúsculas; sem espaço
gclid, fbclid, msclkid, ttclid string nullable passaporte OCT
wbraid, gbraid string nullable quando GCLID some (Apple / ATT)
landing_url / referrer text fallback orgânico
first_touch_at / last_touch_at timestamp política explícita
first_utm_* / last_utm_* (opcional) string se precisar dos dois modelos

Padronize no write: lowercase, trim, rejeite valores absurdamente longos. Facebook e facebook não podem virar duas fontes no relatório de mídia.

UTMs tendem a sobreviver à Link Tracking Protection; click IDs (gclid) são os primeiros a sumir em Private Browsing / Mail. Grave os dois quando existirem.

Persistência na jornada (antes do form)

Cenário real: anúncio → home → blog → pricing → /contato. Sem storage, a query some na 2ª pageview.

Mecanismo Uso no CRM próprio
sessionStorage Mesma visita, multi-página; some ao fechar a aba
Cookie via JS Fácil; Safari limita a ~7 dias (às vezes 24h)
Cookie via servidor (Set-Cookie) Padrão-ouro no seu domínio — HttpOnly, Secure, SameSite

No primeiro hit com query params, o app (middleware/edge) deve:

  1. Ler utm_* + click IDs da URL.
  2. Setar cookie first-party (ex.: dqb_attr, JSON ou cookies separados) com TTL alinhado ao ciclo de venda (30–90d), ciente do ITP se for só JS.
  3. Não apagar first-touch: se já existe first, atualize só last.

No CRM próprio isso é trivial comparado a HubSpot: você é o dono do domínio e do Set-Cookie.

API de lead: o contrato certo

Evite depender só de:

<input type="hidden" name="utm_source" value="">

Prefira o submit (Fetch/XHR ou form POST) mandar um bloco explícito — e o servidor também ler o cookie de attribution se o client falhar:

{
  "email": "lead@empresa.com",
  "name": "Ana",
  "attribution": {
    "utm_source": "google",
    "utm_medium": "cpc",
    "utm_campaign": "brand_2026",
    "gclid": "Cj0KCQj...",
    "landing_url": "https://seusite.com/precos",
    "touch": "last"
  }
}

No handler:

  1. Merge: body attribution ∪ cookie server-side ∪ (opcional) query da request.
  2. Normalize e valide.
  3. INSERT lead com as colunas de origem na mesma transação.
  4. Se a política for first-touch e o e-mail já existe, não sobrescreva first; atualize last / atividade.

Hidden fields continuam úteis para forms clássicos sem JS — mas o caminho feliz é API + cookie first-party.

Form em SPA: MutationObserver para hidden é gambiarra de builder. No app seu, o estado de attribution vive no client store ou no cookie; o submit só serializa o que o backend já conhece.

iFrame: só se você criou o problema

Form same-origin no mesmo app → sem Same-Origin hell.

Se o form está em subdomínio/embed de outro host seu, aí sim: postMessage com allowlist de origin, ou query no src do iframe. Detalhe de eventos: formulários e iFrames no GTM. Na maioria dos CRMs próprios, não use iFrame de terceiro e a dor some.

Do lead ao Closed-Won (e ao Ads)

  1. Política: first-touch (aquisição) vs last-touch (conversão) — duas colunas ou regra documentada no código.
  2. Pipeline: ao mudar status para SQL / Closed-Won, o gclid (ou wbraid/gbraid) precisa ainda estar no registro (ou na tabela attribution_events).
  3. Offline Conversion: job (fila/cron) exporta gclid + conversion name + value + timestamp (timezone certo) para o Google Ads. Janela típica ~90 dias após o clique.
  4. Sem click ID: UTM ainda serve dashboard interno; Enhanced Conversions for Leads (hash de e-mail) é plano B — próximo degrau, não escopo deste post.

Checklist de QA

# Verificação Feito quando...
1 Hit com UTM+gclid Cookie/Set-Cookie grava first + last
2 Multi-página até o form Payload da API traz attribution
3 INSERT no banco Colunas preenchidas na mesma row do lead
4 Safari / voltar dias depois Sabemos o que o cookie server ainda tem
5 Normalização Só minúsculas; sem %20
6 Lead repetido First preservado; last atualizado
7 Closed-Won gclid ainda no registro
8 OCT teste 1 conversão sobe no Ads com o mesmo gclid

Próximo passo

Defina o schema, o cookie first-party e o contrato da API. Valide com um clique de anúncio de teste até a row no banco — não só no Tag Assistant.

Baixe o guia PDF gratuito (schema, script de cookie dqb_attr pronto para colar, checklist de QA): Guia origem no CRM próprio.

Se metade do pipeline é Direct, o submit não manda attribution e o GCLID some antes do Closed-Won, cabe uma auditoria do caminho app → CRM próprio (middleware, schema, export OCT).

Para alinhar volumes entre ferramentas: GA4 vs CRM. Para o evento de conversão no front: formulários e iFrames no GTM.