Appearance
Integrações customizadas e VML
Uma integração customizada permite receber webhooks de qualquer plataforma de vendas. O Vestige Mapping Language (VML) transforma o evento recebido no contrato canônico de venda do Vestige Metrics.
Esta página é a fonte de verdade pública para pessoas e LLMs que precisam criar uma configuração VML.
Fluxo
text
Webhook recebido
↓
Envelope request.body / request.query / request.headers
↓
VML: map → transform → filter
↓
Contrato de venda
↓
Resultado normalizado ou erro por campoNo painel, acesse Configurações → Integrações → Plataformas de vendas → Integração customizada. Crie a integração, abra suas configurações, clique em Receber eventos e envie um webhook para a URL apresentada.
Como pedir a uma LLM para gerar o VML
Forneça à LLM:
- esta página;
- um evento real completo, sem dados pessoais quando isso não for necessário;
- os campos que deseja produzir;
- os status da plataforma que devem ser aceitos ou descartados;
- a unidade monetária do preço, por exemplo reais ou centavos.
Prompt recomendado:
text
Use exclusivamente a especificação VML do Vestige Metrics em
https://docs.vestigemetrics.com.br/tracking/integrations/custom-sales-vml.md.
Gere um objeto JSON VML v1 para o evento abaixo. Não invente operações.
Mapeie os campos do contrato, normalize status e datas, converta valores
numéricos e adicione filtros apenas quando necessário. Retorne somente JSON.
EVENTO:
{ ... }Estrutura mínima
json
{
"version": 1,
"map": {
"transaction": { "extract": "request.body.transaction_id" },
"purchaseDatetime": { "extract": "request.body.created_at" },
"purchaseConfirm": { "extract": "request.body.confirmed_at" }
},
"transform": {
"purchaseDatetime": [
{ "op": "parse_datetime", "format": "%Y-%m-%dT%H:%M:%SZ", "timezone": "UTC" }
],
"purchaseConfirm": [
{ "op": "parse_datetime", "format": "%Y-%m-%dT%H:%M:%SZ", "timezone": "UTC" }
]
}
}version deve ser 1. As chaves permitidas na raiz são version, map, transform e filter.
Paths do evento
Na integração customizada, os paths começam pelo envelope recebido:
text
request.body.customer.email
request.query.campaign
request.headers.x-signature
request.body.items[0].idObjetos usam ponto e arrays aceitam índices entre colchetes. Um path ausente produz null; não apaga o mapping.
Etapa map
Cada campo possui no máximo uma primitive de origem.
extract
json
"email": { "extract": "request.body.customer.email" }constant
json
"currencyCodeFrom": { "constant": "BRL" }coalesce
Usa o primeiro path existente e não nulo.
json
"email": {
"coalesce": [
"request.body.customer.email",
"request.body.buyer.email"
]
}default
Pode acompanhar extract, coalesce, array_find ou if.
json
"status": {
"extract": "request.body.status",
"default": "pending"
}array_find
json
"prodId": {
"array_find": {
"path": "request.body.items",
"where": { "field": "type", "operator": "eq", "value": "product" },
"extract": "id"
}
}if
json
"status": {
"if": {
"condition": { "field": "request.body.refunded", "operator": "eq", "value": true },
"then": "refunded",
"else": "approved"
}
}Etapa transform
Transforms são executadas na ordem declarada para cada campo.
| Operação | Parâmetros | Uso |
|---|---|---|
trim | nenhum | Remove espaços nas extremidades |
lower | nenhum | Converte texto para minúsculas |
upper | nenhum | Converte texto para maiúsculas |
to_string | nenhum | Converte valor para string |
to_number | nenhum | Converte string/número para int ou float |
map | values obrigatório; unknown opcional | Traduz valores |
divide | value numérico obrigatório | Divide um número |
multiply | value numérico obrigatório | Multiplica um número |
concat | values obrigatório | Concatena literais, $value e campos mapeados |
parse_datetime | exatamente um de format/formats; timezone opcional | Interpreta data textual |
epoch_to_datetime | unit: seconds ou milliseconds; timezone opcional | Interpreta timestamp |
parse_json | nenhum | Interpreta uma string JSON |
extract | path obrigatório | Extrai um path do valor atual |
unique | nenhum | Remove duplicatas de um array |
join | separator opcional | Junta itens de um array |
vda_decode | nenhum | Decodifica VDA; aplicado automaticamente pelo builder |
Normalizar status com map
json
"status": [
{
"op": "map",
"values": {
"paid": "approved",
"refunded": "refunded",
"chargeback": "chargeback"
},
"unknown": "preserve"
}
]unknown aceita preserve ou null.
Converter centavos
json
"price": [
{ "op": "to_number" },
{ "op": "divide", "value": 100 }
]Concatenar telefone
json
"phones": [
{
"op": "concat",
"values": [
{ "field": "phoneCountryCode" },
{ "field": "phoneAreaCode" },
"$value"
]
}
]$value representa o valor atual. { "field": "nome" } referencia outro campo já mapeado.
Datas
json
"purchaseDatetime": [
{
"op": "parse_datetime",
"format": "%Y-%m-%dT%H:%M:%SZ",
"timezone": "UTC"
}
]Também é possível tentar vários formatos:
json
{
"op": "parse_datetime",
"formats": ["%Y-%m-%dT%H:%M:%S", "%d/%m/%Y %H:%M:%S"],
"timezone": "America/Sao_Paulo"
}Filtros
O filtro roda depois de map e transform, portanto seus fields apontam para campos mapeados.
Operadores disponíveis: eq, neq, in, not_in, exists e not_exists.
json
"filter": {
"field": "status",
"operator": "in",
"value": ["approved", "refunded", "chargeback"]
}Combine condições com all (AND) ou any (OR):
json
"filter": {
"all": [
{ "field": "status", "operator": "eq", "value": "approved" },
{ "field": "price", "operator": "neq", "value": 0 }
]
}Um evento que não atende ao filtro recebe status filtered; não é erro de processamento.
Contrato da venda
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
transaction | string | sim | Identificador único da transação |
prodId | string | não | Identificador do produto |
prodName | string | não | Nome do produto |
email | string | não | E-mail do comprador |
phones | string | não | Telefone normalizado |
status | string | não | Status normalizado |
recurrency | inteiro ou boolean | não | Ciclo/indicador de recorrência |
recurrencyPeriod | string | não | Período da recorrência |
price | número | não | Preço da venda |
commission | número | não | Comissão/valor recebido |
amount | inteiro | não | Quantidade |
offer | string | não | Oferta |
src | string | não | Origem complementar |
sck | string | não | Código de rastreamento |
currencyCodeFrom | string | não | Código da moeda |
paymentType | string | não | Tipo de pagamento |
purchaseDatetime | datetime | sim | Data da compra |
purchaseConfirm | datetime | sim | Data da confirmação |
subscriptionDateNextCharge | datetime | não | Próxima cobrança |
version | inteiro | não | Versão do payload |
vda | string | não | Vestige Direct Attribution |
ltuId e pviId são internos e não devem ser mapeados pelo cliente. Mapeie somente vda; o builder inclui vda_decode automaticamente.
Campos auxiliares podem existir em map para uso em transforms, como partes de telefone ou horário. O contrato final descarta campos que não pertencem à venda.
Contexto confiável
Não mapeie nem aceite do webhook os campos accId, platform, date, datetime, integrationHash ou receivedAt. Eles são preenchidos pelo backend com dados confiáveis da conta e da integração.
Exemplos completos
O exemplo Hotmart usa o payload da integração Hotmart v1, não o webhook mais recente da Hotmart.
Checklist antes de salvar
versioné1.- Todos os paths começam por
request.body,request.queryourequest.headers. transaction,purchaseDatetimeepurchaseConfirmestão configurados.- Datas terminam como datetime por meio de
parse_datetimeouepoch_to_datetime. - Valores monetários estão na unidade final esperada.
- Status externos são normalizados antes do filtro.
- VDA usa somente o campo
vda. - O botão Testar configuração apresenta contrato válido para eventos reais.
- Eventos que devem ser ignorados aparecem como
filtered, não como erro. - A configuração foi testada contra mais de um evento real.
Regras para LLMs
- Produza JSON válido, sem comentários.
- Use somente primitives documentadas nesta página.
- Não invente campos do contrato.
- Não gere código Python ou JavaScript; gere apenas VML.
- Não use
eval, templates executáveis, imports, filesystem ou chamadas externas. - Não mapeie campos confiáveis nem identificadores internos.
- Se o formato do webhook estiver incompleto, solicite um evento real em vez de adivinhar paths.
- Se o formato de data ou a unidade monetária forem ambíguos, declare a dúvida antes de gerar uma configuração definitiva.