Regras de tag
Uma regra de tag faz uma interação no site virar um evento. Ela vive sob /track, pertence a um servidor de rastreamento identificado pelo Host da requisição e é gerenciada com um token de conta que tenha a feature WebTracking; envelope e códigos seguem convenções e erros.
Uma regra tem um trigger, que diz quando ela dispara, e extractors, que dizem quais parâmetros o evento leva. O trigger é uma árvore de nós.
Nota
Regra e destino são metades diferentes da medição. A regra captura no navegador; o destino envia ao Meta, GA4, TikTok. Medir uma conversão de ponta a ponta precisa das duas.
Endpoints
| Método | Caminho | O que faz | Acesso |
|---|---|---|---|
| POST | /track/rules | Cria uma regra. 409 se o nome já existe naquele servidor. | Standard + WebTracking |
| GET | /track/rules | Lista as regras do servidor, em ordem de criação. Inclui as pausadas. | ReadOnly + WebTracking |
| PUT | /track/rules/{rule_name} | Cria ou substitui a regra daquele nome. | Standard + WebTracking |
| DELETE | /track/rules/{rule_name} | Exclui uma regra pelo nome. | Standard + WebTracking |
| POST | /track/versions | Publica o rascunho. | Standard + WebTracking |
| GET | /track/versions | Lista as publicações, da mais nova para a mais antiga. | ReadOnly + WebTracking |
| GET | /track/versions/{publish_id} | Devolve uma publicação com as regras dela. | ReadOnly + WebTracking |
| POST | /track/versions/{publish_id}/republish | Publica de novo uma publicação anterior (volta de versão). | Standard + WebTracking |
| GET | /track/tag | Entrega ao tag do navegador as regras habilitadas da publicação em vigor. | Público |
Nota
GET /track/tag e POST /track/collect formam o par público do navegador, ambos resolvidos pelo Host e sem token. Quem os chama é o tag do navegador: /track/tag recebe as regras, /track/collect recebe os eventos que o tag dispara. Você gerencia as regras pelas quatro rotas de /track/rules.
A feature WebTracking não vem por padrão (uma conta nova recebe só Chat). Sem ela, mesmo com token válido, as quatro rotas de /track/rules respondem 403 em texto puro com o corpo Feature disabled: WebTracking; níveis e features em autenticação.
PUT é a forma de alterar uma regra. Não existe PATCH: o corpo do PUT é a definição completa e substitui a anterior.
As quatro rotas de /track/rules editam um rascunho. O site só recebe as regras depois de um POST /track/versions; ver Rascunho e publicação.
Identificação do servidor
O servidor alvo sai do Host/TLS da requisição; não existe parâmetro de servidor.
- A chamada vai ao domínio do próprio servidor de rastreamento, resolvido do Host da requisição e do nome do servidor TLS (SNI). A URL base destas rotas é
https://<server_name>, por exemplohttps://track.exemplo.com.br/track/rules, e não a base do resto da API. Chamar/track/rulesnum domínio que não é servidor da conta dá 404. - Se Host e nome do servidor TLS vierem e divergirem, a resposta é 400 em texto puro, corpo literal
Host header does not match TLS server name. - Nas rotas de
/track/rules, o servidor ainda precisa pertencer à conta do token; sem servidor para aquele Host e conta, 404 (Error 404: server not found). O públicoGET /track/tagresolve o servidor só pelo Host, sem cláusula de conta, com o mesmo 404 quando não há servidor.
Corpo de uma regra
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
rule_name | string | Sim, no POST | Nome da regra, texto livre de 1 a 64 caracteres (contados como caracteres, não bytes). Não pode ter espaço no início nem no fim, caractere de controle, /, %, ? ou #, nem ser só pontos (., ..). Espaços, acentos e maiúsculas valem, como em Clique no botão de compra. Único por servidor e chave do PUT e do DELETE. Também é a chave de deduplicação de um Once com per Session ou User: renomear a regra zera a dedup dela. |
event_name | string | Sim | Nome do evento que a regra envia, ^[a-z][a-z0-9_]{0,39}$ (snake_case, dentro do limite de 40 caracteres do GA4). |
trigger | objeto | Sim | Quando a regra dispara. É uma árvore; ver abaixo. |
extractors | objeto | Não | Parâmetros do evento, mapeados por nome do parâmetro (^[a-z][a-z0-9_]{0,39}$) para uma expressão. Omitido, nenhum. |
enabled | boolean | Não | Padrão true. Uma regra pausada continua em GET /track/rules e não vai para GET /track/tag. |
conversion | boolean | Não | Padrão false. Marca os eventos da regra como conversões nos relatórios de rastreamento; parameters.value, quando numérico, é o valor da conversão. Devolvido em GET /track/rules, não entregue em GET /track/tag. |
No PUT, o corpo não tem rule_name: o nome vem do caminho {rule_name} e passa pela mesma validação. No PUT e no DELETE, um nome com espaço ou acento vai percent-encoded nesse segmento da URL. A substituição é completa: enabled e conversion omitidos voltam aos padrões, extractors omitido vira vazio, e nada do corpo anterior é preservado.
O tag do navegador acrescenta a todo evento três parâmetros base: href, page_title e page_referrer (este só quando document.referrer não é vazio). Uma chave de mesmo nome em extractors sobrescreve o valor base naquele evento.
trigger
Cada nó do trigger tem um campo type; campos desconhecidos são recusados. Sete tipos são fontes: disparam sozinhos e ficam nas folhas da árvore.
type | Quando dispara | Entrega |
|---|---|---|
ElementEvent | Um evento DOM em todo elemento que casa com o seletor, inclusive elementos inseridos depois da instrumentação. Campos: selector, event. | elemento e evento |
PageReady | Uma vez, quando o DOM está pronto; retroativamente se já estiver. Sem campos. | nada |
WindowEvent | Um evento DOM em window, como scroll ou resize. Campo: event. | evento, sem elemento |
Timer | A cada interval_ms milissegundos (1000 a 3600000). Navegadores estrangulam timers em abas de fundo. | nada |
ScrollDepth | Uma vez por limiar por carregamento. Campo: thresholds (1 a 10 valores de 1 a 100, estritamente crescentes). | evento tgsr:scroll_depth com detail.threshold_pct |
TimeEngaged | Uma vez por limiar por carregamento, contando só os segundos visíveis. Campo: thresholds_s (1 a 10 valores de 1 a 3600, estritamente crescentes). | evento tgsr:time_engaged com detail.threshold_s e detail.engaged_ms |
ElementVisible | Uma vez por elemento por carregamento, quando um elemento que casa com selector cruza a viewport em pelo menos ratio (IntersectionObserver). ratio acima de 0 e até 1, padrão 0.5. | elemento, sem evento |
Três tipos são envelopes: cada um carrega um inner, que é outro trigger.
type | O que faz | Campos |
|---|---|---|
OnPage | Só deixa o inner disparar quando location.href casa com regex. Reavaliado na navegação de SPA. | regex, inner |
Once | Deduplica o inner ao primeiro disparo por escopo per. | per, inner |
SideEffect | Roda JavaScript, o corpo de (element, event) => pass, a cada disparo do inner, antes de capturar e enviar; retornar false engole aquele disparo. | source, inner |
O per de Once é um de:
Page: por carregamento de página, em memória.Element: por elemento casado. Exige uminnerque entregue elemento (que termine emElementEventouElementVisible); caso contrário, o servidor recusa com 400.Session: por sessão de aba, emsessionStorage, com chave no nome da regra.User: por perfil do navegador, emlocalStorage, com chave no nome da regra.
Aviso
A ordem dos envelopes muda o resultado. Once por fora, OnPage no meio, SideEffect junto da folha. Um OnPage por fora de um Once gasta o disparo único antes do filtro de URL: com per Session ou User, a regra pode nunca mais enviar naquele navegador. A API aceita as duas ordens.
Exemplo: o clique no botão de compra, só no checkout, uma vez por sessão.
{
"type": "Once",
"per": "Session",
"inner": {
"type": "OnPage",
"regex": "/checkout(/|$)",
"inner": {
"type": "ElementEvent",
"selector": "button.buy",
"event": "click"
}
}
}
extractors
extractors é um objeto: a chave é o nome do parâmetro do evento, o valor é uma expressão. Uma expressão é uma de duas coisas:
- um nó, um objeto com
type; - uma cadeia, um array de expressões avaliadas da esquerda para a direita, cada passo consumindo a saída do anterior.
Qualquer passo pode haltar, ou seja, parar sem produzir valor. A cadeia para e o parâmetro é omitido do evento. É o comportamento normal: um atributo ausente não é erro.
Os nós, por família:
Leem o elemento:
type | Produz | Halta quando |
|---|---|---|
Attr (name) | getAttribute(name) do elemento | o atributo não existe |
Text | textContent aparado | o texto é vazio |
Value | o .value do controle | |
Hostname | o hostname de um <a>/<area>, para link externo | |
Field (name) | o .value do campo daquele nome no formulário carregado |
Leem o contexto:
type | Produz | Halta quando |
|---|---|---|
EventProp (key) | uma propriedade do evento DOM; caminho com pontos percorrido segmento a segmento, como detail.threshold_pct | |
Location (part) | href, pathname, host, hostname, search, hash ou origin | |
UrlParam (name) | um parâmetro de query | o parâmetro não existe |
DocumentProp (key) | title, referrer, readyState, visibilityState, characterSet ou contentType | |
WindowProp (key) | scrollX, scrollY, innerWidth, innerHeight, outerWidth, outerHeight ou devicePixelRatio | |
Constant (value) | um valor fixo: string, número ou boolean. Útil como último ramo de um Or |
Andam pelo DOM:
type | Produz | Halta quando |
|---|---|---|
Closest (selector) | o elemento que casa com o seletor, subindo a partir do atual | não casa |
Find (selector) | querySelector(selector) a partir do elemento | não casa |
Parent | parentElement | a raiz está solta |
Filtram:
type | O que faz |
|---|---|
WhenMatches (regex) | mantém o valor se a forma em string casa com o regex, senão halta |
WhenEquals (value) | mantém o valor se for estritamente igual, senão halta |
Transformam:
type | O que faz |
|---|---|
Trim | apara o valor |
Lowercase | passa o valor para minúsculo |
ToNumber | converte para número; NaN halta |
RegexCapture (regex, group) | entrega um grupo de captura (0 a 9, padrão 1); sem casamento, halta |
Truncate (max_len) | corta em no máximo max_len caracteres (1 a 500) |
Slug | tira marcas de combinação, minúsculo, colapsa não alfanuméricos em _, apara _; vazio halta |
UrlDecode | decodeURIComponent; escape malformado halta |
Hash | SHA-256 da forma em string, em hexadecimal minúsculo, calculado pelo tag: o valor nunca sai do navegador em claro. Normalize antes (Trim, Lowercase) para o hash casar entre eventos e com as plataformas. O servidor não hasheia nada: parâmetro sem Hash é gravado como enviado |
Combinam:
type | O que faz |
|---|---|
Or (branches) | de 2 a 8 expressões tentadas em ordem contra o valor de entrada; vence a primeira que produzir valor; se todas haltarem, halta |
Script (source) | JavaScript, o corpo de (value, state) => result, rodado pelo tag via new Function; undefined halta |
Aviso
Existe um sistema de tipos implícito que o servidor não valida. Os nós que leem DOM (Attr, Text, Closest e afins) exigem um elemento; os que transformam exigem um valor. Uma cadeia como [{"type":"Location","part":"href"},{"type":"Attr","name":"id"}] passa pela API e estoura no navegador. Um extractor que estoura descarta o evento inteiro, não só aquele parâmetro. A ordem certa: comece no elemento, ande pelo DOM, leia um valor, depois transforme.
Aviso
Um Script ou um SideEffect com erro de sintaxe passa pela API. O servidor só limita o tamanho e recusa fonte vazia; ele não interpreta o JavaScript. O erro aparece no navegador, na instrumentação, e o tag pula aquela regra.
Exemplo: o id do produto, com um caminho alternativo, e o preço convertido para número.
{
"product_id": {
"type": "Or",
"branches": [
[
{ "type": "Closest", "selector": "[data-product]" },
{ "type": "Attr", "name": "data-product" }
],
{ "type": "UrlParam", "name": "sku" }
]
},
"price": [
{ "type": "Find", "selector": ".price" },
{ "type": "Text" },
{ "type": "ToNumber" }
]
}
Limites
Validados na escrita; estourar é 400 no envelope.
| Limite | Valor |
|---|---|
| Regras por servidor | 200 |
Bytes por regra (JSON serializado de trigger + extractors) | 16 KiB |
Profundidade do trigger (folha incluída) | 8 níveis |
Parâmetros em extractors | 32 |
| Nós por expressão | 32 |
| Profundidade por expressão | 8 níveis |
Ramos de um Or | 2 a 8 |
| Seletor CSS | 512 bytes, não vazio, sem caracteres de controle |
| Regex | 256 bytes |
source de Script e SideEffect | 8 KiB |
Nome de atributo, campo ou parâmetro de query (Attr, Field, UrlParam) | 128 bytes |
| Nome de evento DOM | ^[a-zA-Z][a-zA-Z0-9_:-]{0,63}$ |
key de EventProp | até 64 caracteres, segmentos separados por ponto |
O teto de 200 é checado excluindo a própria regra: um PUT sobre uma regra que já existe passa mesmo com o servidor no teto.
Os regex de OnPage, WhenMatches e RegexCapture seguem um dialeto que é subconjunto estrito do JavaScript: sem lookaround e sem backreference. Um padrão que funciona no navegador pode ser recusado aqui.
O servidor não interpreta CSS: um seletor sintaticamente inválido passa pela API e só falha no navegador.
Rascunho e publicação
POST, PUT e DELETE em /track/rules mudam o rascunho do servidor. O GET /track/tag entrega ao navegador a publicação em vigor, que só muda com uma publicação nova.
GET /track/rules devolve o rascunho e o compara com o que está no ar:
- cada regra traz
publish_state:new(não está na publicação em vigor),published(igual à publicada) oumodified; deleted_ruleslista as regras publicadas que o rascunho não tem mais; a próxima publicação as tira do site;etagé um hash opaco do conteúdo do rascunho;live_publish_idé a publicação em vigor, ounullse o servidor nunca publicou.
POST /track/versions publica o rascunho. Corpo: name (obrigatório, de 1 a 100 caracteres) e description (opcional, até 1000). Mande o etag lido em GET /track/rules no cabeçalho If-Match: assim vai ao ar exatamente o que foi revisado. A resposta é { "publish_id": "<id>", "published": true }; quando o rascunho já é igual à publicação em vigor, nada é criado e a resposta traz published: false com o publish_id em vigor.
412: o rascunho mudou depois da leitura (If-Matchnão confere). Leia e revise de novo.409: outra publicação entrou no ar ao mesmo tempo. Publique de novo.
GET /track/versions responde { "live_publish_id": ..., "publishes": [ ... ] }, da mais nova para a mais antiga (a primeira é a que está no ar). Cada item tem publish_id, name, description, published_at (UTC, ISO 8601 sem fuso), published_by (e-mail de quem publicou; null quando foi o sistema, como na instalação das regras padrão), restored_from (a publicação de origem, numa volta de versão) e rule_count. GET /track/versions/{publish_id} traz os mesmos campos e rules, as regras como foram publicadas, na ordem em que o tag as instrumenta; publicação inexistente dá 404 (Error 404: publish not found).
POST /track/versions/{publish_id}/republish põe no ar de novo uma publicação anterior, com o mesmo corpo name/description. Não usa If-Match e não altera o rascunho.
No painel do app, a publicação fica no botão "Publicar" de Rastreamento › Regras; o histórico e a volta de versão existem só pela API e pelo MCP.
Cache do GET /track/tag
- ETag fraco (
W/"<publish_id>") com o id da publicação em vigor (W/"0"quando o servidor nunca publicou). Cache-Control: max-age=60.- Um
If-None-Matchigual ao ETag responde 304, sem corpo, comETageCache-Control.
Uma publicação nova pode levar até um minuto para chegar aos navegadores que já carregaram a configuração.
GET /track/tag?draft=true entrega as regras habilitadas do rascunho, com Cache-Control: no-store. É o que a aba de inspeção do painel (Visualizar) usa para rodar as regras antes de publicar.
Ordem das regras
GET /track/rules e GET /track/tag devolvem as regras em ordem de criação (/track/tag, na ordem da publicação); /track/tag entrega só as habilitadas. É nessa ordem que o tag do navegador instrumenta as regras.
Regras padrão
Todo servidor de rastreamento nasce com 8 regras:
| rule_name | Dispara em | Parâmetros |
|---|---|---|
page_view | PageReady | nenhum |
scroll_depth | ScrollDepth em 25, 50 e 90 | threshold_pct |
time_engaged | TimeEngaged em 10 e 60 s | threshold_s |
outbound_link | clique em a[href^="http"] | url, text |
file_download | clique em link para .pdf, .zip, .xlsx, .xls, .docx, .pptx, .csv | url, file_name |
form_start | primeiro focusin em cada form (Once com per: Element) | form_id (id ou name) |
form_submit | submit em form | form_id |
contact_click | clique em link de WhatsApp (wa.me, api.whatsapp.com, whatsapp:), tel: ou mailto: | url, channel (whatsapp, phone, email) |
Elas vêm habilitadas e sem conversion, podem ser editadas ou excluídas como qualquer regra e contam no teto de 200 (GET /track/rules num servidor recém-criado devolve 8 itens). A instalação roda a cada PUT /servers do servidor, mas só quando ele não tem nenhuma regra nem nenhuma publicação, e já publica as 8 regras; um servidor com regras ou com alguma publicação (mesmo vazia) fica como está. Uma falha na instalação não derruba a criação do servidor.
Respostas
Sucesso e erros de validação da própria rota usam o envelope padrão ({"Ok": ...} / {"Err": "..."}, Content-Type: application/json), com o status derivado do prefixo Error NNN: da mensagem; ver convenções e erros. Os corpos abaixo chegam dentro do envelope: no fio, GET /track/rules responde { "Ok": { "rules": [...] } }, o POST { "Ok": { "rule_name": "<nome>" } }, e assim por diante.
| Endpoint | Corpo da resposta |
|---|---|
POST /track/rules | { "rule_name": "<nome>" } |
PUT /track/rules/{rule_name} | { "rule_name": "<nome>", "created": true | false }. true quando o PUT criou a regra, false quando substituiu. |
GET /track/rules | { "rules": [ ... ], "deleted_rules": [ ... ], "etag": "...", "live_publish_id": ... }, cada regra com rule_name, event_name, enabled, conversion, trigger, extractors, created_at, updated_at, publish_state. |
POST /track/versions e .../republish | { "publish_id": "<id>" | null, "published": true | false }. |
DELETE /track/rules/{rule_name} | { "deleted": true | false }. false quando nada foi excluído. |
GET /track/tag | { "rules": [ ... ] }, cada item com rule_name, event_name, trigger, extractors; só as habilitadas da publicação em vigor (ou do rascunho, com ?draft=true). |
Erros no envelope:
400: regra que não passa na validação (limite estourado, regex que não compila, seletor vazio,thresholdsfora de ordem,Oncecomper: Elementsem elemento). A mensagem nomeia o que falhou, por exemploError 400: trigger: ...ouError 400: extractor '<nome>': ....400: nome inválido, corpoError 400: invalid rule_name (want 1 to 64 characters, no leading or trailing whitespace, none of / % ? #, no control characters, not only dots).400: teto alcançado, corpoError 400: rule limit of 200 per server reached.404:Error 404: server not found. Em/track/rules, para o Host e a conta do token; emGET /track/tag, só pelo Host.409:POST /track/rulescomrule_nameque já existe no servidor, corpoError 409: rule '<nome>' already exists; PUT /track/rules/<nome> updates it. O POST não sobrescreve.
Erros em texto puro, fora do envelope (produzidos antes de a rota processar a requisição, sem {"Err": ...}). Não faça parse de {"Err": ...} em toda resposta não 2xx:
400:Host header does not match TLS server name.401(Not authorized,Bad format: ...) e403(Bad token,Insufficient privilege: ...,Feature disabled: WebTracking), da autenticação.- Rejeições de parse do corpo: um
typedesconhecido, um campo a mais, um valor fora das listas fechadas (part,key,per), um campo obrigatório ausente ou umConstantcom objeto ou null falham antes da validação. Voltam em texto puro, tipicamente com 422; JSON malformado dá 400, e a falta deContent-Type: application/jsondá 415.
Veja também
- Servidores: o servidor de rastreamento dono das regras.
- Destinos de rastreamento: a outra metade da medição, para onde o evento capturado é enviado.
- Autenticação: os níveis Standard e ReadOnly e a feature
WebTrackingexigidos pelas rotas.