@overlens/events-sdk
v0.1.0
Published
Browser SDK for Overlens Events: lead capture with eventId, UTMs and Meta fbp/fbc.
Maintainers
Readme
@overlens/events-sdk
SDK de navegador do Overlens Events para formulários de lead. Substitui o snippet copiado em cada landing page: gera e controla o eventId, captura UTMs, lê fbp/fbc da Meta e envia o lead para POST /webhooks/leads/:triggerSlug com keepalive, para que o envio sobreviva ao redirecionamento para o checkout.
- Zero dependências de runtime.
- ESM, CJS, tipos TypeScript e um build IIFE para páginas sem bundler.
submitLeadnunca lança: qualquer problema vira{ status: "failed" }, e a UX da LP não quebra por causa da SDK.- Seguro em SSR: importar o pacote e criar o cliente sem
windownão quebra (no servidor ele só não captura nada).
Instalação
Com bundler (npm)
pnpm add @overlens/events-sdkimport { createEventsClient } from "@overlens/events-sdk";
const events = createEventsClient({
endpoint: "https://events.overlens.com.br",
triggerSlug: "atlas-cam-0826",
});
form.addEventListener("submit", async (e) => {
e.preventDefault();
const result = await events.submitLead({
email: form.email.value,
name: form.name.value, // opcional
phone: form.phone.value, // opcional
honeypot: form.website.value, // opcional: o campo oculto anti-bot
});
// result.status: "accepted" | "unmatched" | "already_processed" | "failed"
});Crie o cliente uma vez, no carregamento da página, e não dentro do submit. A captura de UTMs e do fbclid acontece na criação, enquanto a URL de entrada ainda tem a query string.
Sem bundler (<script> via jsDelivr)
<script src="https://cdn.jsdelivr.net/npm/@overlens/events-sdk@0/dist/index.global.js"></script>
<script>
const events = OverlensEvents.createEventsClient({
endpoint: "https://events.overlens.com.br",
triggerSlug: "atlas-cam-0826",
});
</script>O build IIFE é minificado e expõe window.OverlensEvents. O @0 acompanha a versão 0.x mais recente. Para travar uma versão exata, use @0.1.0.
Opções de createEventsClient
| Opção | Tipo | Descrição |
|---|---|---|
| endpoint | string | Origem da API do Events. Barra final é tolerada. |
| triggerSlug | string | Slug do gatilho lead_form. O POST vai para {endpoint}/webhooks/leads/{triggerSlug}. |
| adapters | LeadAdapter[] | Destinos no navegador (Pixel, dataLayer) disparados com o mesmo eventId. Veja Adaptadores. |
| fetch | typeof fetch | Opcional. O padrão é o fetch global. Serve para testes. |
submitLead(input)
| Campo | Tipo | Descrição |
|---|---|---|
| email | string | Obrigatório. |
| name | string? | Vazio é omitido do envio (fluxo "veterano"). |
| phone | string? | Vazio é omitido do envio. |
| honeypot | string? | Valor do campo oculto website. Se vier preenchido, o lead é enviado normalmente (o servidor descarta em silêncio) e os adaptadores não são chamados. |
| utms | Utms? | Substitui por inteiro as UTMs capturadas automaticamente ({ utmSource, utmMedium, utmCampaign, utmTerm, utmContent }). Útil na migração, para manter o readUtms() atual da LP. |
Resultado
Promise<{ status, eventId, error? }>, que sempre resolve:
| status | Significado | eventId seguinte |
|---|---|---|
| accepted | Lead recebido e encaminhado ao workflow. | novo |
| unmatched | Lead recebido, mas ainda não existe workflow para o slug. Ele fica guardado e é reprocessado quando o gatilho for criado. | novo |
| already_processed | Esse eventId já tinha sido recebido (duplo clique ou retry). | novo |
| failed | Não foi possível confirmar o envio. error traz o motivo: o código da API (invalid_payload, invalid_origin, rate_limited…), http_<status>, network_error ou fetch_unavailable. | o mesmo: um novo submit deduplica no servidor |
O que a SDK faz sozinha
eventId: um UUID por sessão de formulário. Um duplo clique com o envio ainda em voo devolve a mesma Promise, então sai um único request. O id só muda depois de uma resposta confirmada (accepted,unmatchedoualready_processed).- UTMs: lidas de
location.searchna criação do cliente, com last-touch não vazio. Uma URL com UTMs substitui as anteriores por inteiro, e uma URL sem UTMs mantém as que já estavam salvas. Ficam nolocalStoragesob a chaveoverlens-events:utms. url:location.origin + location.pathname, sem a query string.fbp: cookie_fbp. A SDK não cria esse cookie, porque ele pertence ao Pixel.fbc: cookie_fbc. Sem ele, se a URL de entrada tiverfbclid, a SDK montafb.1.{timestamp}.{fbclid}e guarda nosessionStorage(chaveoverlens-events:fbc). O cookie sempre tem precedência. A SDK não grava cookie.- Envio:
POSTcomContent-Type: text/plain(dispensa o preflight de CORS) ekeepalive: true. Falha de rede ou 5xx ganham até 2 novas tentativas com backoff curto (300 ms e 1 s) e o mesmo corpo. 4xx não tem retry. - Versão: todo corpo leva
sdkVersioncom a versão do pacote, embutida no build. Vai no corpo, e não num header, porque um header customizado exigiria o preflight de CORS. O dashboard usa esse campo para mostrar quais versões ainda recebem leads em cada origem (Configurações → "Versões da SDK nos últimos 30 dias"). - Storage bloqueado (aba privada, cookies de terceiros): a SDK continua funcionando, só perde a persistência.
A origem da LP precisa estar na allowlist em /settings → Formulário de Lead do Events. Sem isso, a API responde 403 invalid_origin.
Adaptadores
Um adaptador dispara o lead num destino do navegador (o Pixel da Meta, o dataLayer do GTM) com o mesmo eventId do POST. É isso que permite à Meta contar o lead do Pixel e o da Conversions API como um só.
O cliente chama cada adaptador:
- uma vez por
eventId: duplo clique e retry depois de falha não disparam de novo; - de forma síncrona e antes do POST, porque a LP pode redirecionar para o checkout na linha seguinte;
- isolado em
try/catch: um adaptador que lança não afeta o lead nem os outros adaptadores; - nunca com o honeypot preenchido: bot não vira evento de mídia.
import { createEventsClient, dataLayer, metaPixel } from "@overlens/events-sdk";
const events = createEventsClient({
endpoint: "https://events.overlens.com.br",
triggerSlug: "atlas-cam-0826",
adapters: [metaPixel()], // ou dataLayer({ ... }), veja abaixo
});No build IIFE, eles ficam em OverlensEvents.metaPixel e OverlensEvents.dataLayer.
Use um caminho até o Pixel: metaPixel() quando o Pixel está no código da página, ou dataLayer() quando ele dispara por uma tag do GTM. Os dois juntos disparam o Lead duas vezes no navegador (a Meta desduplica pelo eventID, mas não há motivo para isso).
metaPixel(options?)
Chama o fbq que a página já tem, passando o eventId como eventID:
metaPixel(); // fbq("track", "Lead", {}, { eventID })
metaPixel({ eventName: "CompleteRegistration" }); // outro evento padrão → "track"
metaPixel({ eventName: "AtlasLead" }); // nome próprio → fbq("trackCustom", …)
metaPixel({ customData: () => ({ content_name: "Atlas" }) });| Opção | Padrão | Descrição |
|---|---|---|
| eventName | "Lead" | Nome do evento no Pixel. Um evento padrão da Meta usa track, e qualquer outro nome usa trackCustom. Diferencia maiúsculas: lead é personalizado. Precisa ser o mesmo nome do nó Meta do workflow. |
| customData | {} | (ctx) => objeto com o custom data do Pixel. Por padrão, nenhum dado pessoal é enviado. |
| when | sempre | Consentimento, veja abaixo. |
Sem window.fbq na página, não faz nada. A SDK nunca injeta o Pixel: o Pixel, o id dele e o consentimento pertencem à LP.
dataLayer(options)
Faz o push no window.dataLayer (e o cria, se o GTM ainda não carregou) com o eventId em event_id:
dataLayer({
event: "atlas_para_negocios_lead", // obrigatório: o mesmo nome que a LP já usa
payload: (ctx) => ({ utm_source: ctx.page.utms.utmSource }),
});
// → dataLayer.push({ utm_source: "meta", event: "atlas_para_negocios_lead", event_id: "…" })| Opção | Descrição |
|---|---|
| event | Obrigatório. A chave event que dispara o gatilho no GTM. |
| payload | Opcional. (ctx) => objeto com as chaves extras do push. ctx traz eventId, lead (email, name?, phone?) e page (url, utms). |
| when | Consentimento, veja abaixo. |
- Nenhum dado pessoal vai por padrão. Só entra no push o que você colocar em
payload. eventeevent_idsão aplicados depois dopayload: uma chave dopayloadcom o mesmo nome não consegue renomear o evento nem trocar o id.
Configuração no GTM (manual)
- Variáveis → Nova → Variável da camada de dados, com nome da variável
event_id. - Na tag do Pixel
Lead(a que dispara no evento personalizadoatlas_para_negocios_lead), preencha o campo Event ID com{{event_id}}. Em tags HTML personalizadas, passe{ eventID: {{event_id}} }como quarto argumento dofbq("track", …). - Publique o container depois do deploy da LP. Antes disso,
event_idchega vazio: isso só perde o dedup, não quebra nada.
Migração de uma LP que já faz dataLayer.push
O adaptador passa a ser dono do push:
- Use o mesmo nome de evento de hoje em
event. Não renomeie: há tags de mídia no container que dependem dele. - Reproduza em
payloadtodos os campos que o push atual manda. Se alguma tag do container (por exemplo, a tag HTML que envia para o Apps Script) lê e-mail, nome ou telefone do push, esses campos precisam estar nopayload, porque o adaptador não envia nada pessoal por conta própria. - Remova o
dataLayer.pushda LP. Com os dois ligados, o evento chega duas vezes ao GTM, e as tags disparam em dobro.
// antes, em pushLeadEvent:
window.dataLayer.push({ event: "atlas_para_negocios_lead", email, name, phone, ...utms });
// depois:
dataLayer({
event: "atlas_para_negocios_lead",
payload: ({ lead, page }) => ({ ...lead, ...page.utms }), // os mesmos campos de antes
});Consentimento (when)
Todo adaptador aceita when?: () => boolean, ligado ao CMP da página. A função é consultada a cada lead, e não na criação do cliente, então um aceite dado depois do carregamento vale. Retornando false, o adaptador não dispara (e nem chama customData/payload). O lead continua sendo enviado para o Events. O padrão é sempre disparar.
metaPixel({ when: () => window.myCmp?.hasConsent("marketing") === true });Adaptador próprio
import type { LeadAdapter } from "@overlens/events-sdk";
const logger: LeadAdapter = {
name: "console",
onLeadSubmitted({ eventId, lead, page }) {
console.log("lead", eventId, lead.email, page.url, page.utms);
},
};onLeadSubmitted precisa ser síncrono. Trabalho assíncrono iniciado ali pode ser interrompido pela navegação da página.
FAQ: desduplicação com a Meta
Quando a Meta junta o evento do Pixel e o da Conversions API?
Quando os dois chegam com o mesmo event_name e o mesmo event_id, com até 48 h de diferença. O event_id a SDK resolve: o adaptador e o POST usam o mesmo eventId, e o Events o repassa à CAPI. O nome é configurado em dois lugares, e só você consegue mantê-los iguais.
Onde fica o nome do lado do servidor?
No nó Meta Ads do workflow do gatilho lead_form. Ele precisa usar exatamente o nome que o Pixel dispara: Lead (o padrão do metaPixel()) ou o eventName que você passou. O painel do nó mostra um aviso quando o gatilho é um Formulário de Lead.
E quando o Pixel dispara pelo GTM?
Vale o nome da tag do Pixel no container (normalmente Lead), e não o event do dataLayer. O atlas_para_negocios_lead só aciona o gatilho do GTM. O event_id chega à tag pela variável de camada de dados event_id.
Como confirmo que funcionou?
No Events Manager, em Test Events, um submit real mostra o evento do navegador e o do servidor com o mesmo event_id. Na visão geral do evento, os dois aparecem como desduplicados, sem dobrar a contagem.
O Pixel não está na página. Quebra algo?
Não. Sem fbq, o metaPixel() não faz nada e o lead segue normalmente para a CAPI, só que sem par para desduplicar.
Releases
A SDK segue semver, com uma ressalva: enquanto estiver em 0.x, uma quebra de compatibilidade sobe o minor. Por isso, em produção, fixe a versão exata no jsDelivr (@0.1.0) ou use ^ com Renovate/Dependabot no npm. As mudanças de cada versão ficam no CHANGELOG.md, que vai junto no pacote publicado (a partir da primeira versão gerada pelo Changesets).
Para quem mantém a SDK (changesets, verificação no CI, publicação e fim de suporte de versões antigas): docs/ways-of-work/sdk-release.md.
