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:
- Ler
utm_*+ click IDs da URL. - 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. - 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:
- Merge: body
attribution∪ cookie server-side ∪ (opcional) query da request. - Normalize e valide.
INSERTlead com as colunas de origem na mesma transação.- 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)
- Política: first-touch (aquisição) vs last-touch (conversão) — duas colunas ou regra documentada no código.
- Pipeline: ao mudar status para SQL / Closed-Won, o
gclid(ouwbraid/gbraid) precisa ainda estar no registro (ou na tabelaattribution_events). - 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. - 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.