Skip to content

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 campo

No 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:

  1. esta página;
  2. um evento real completo, sem dados pessoais quando isso não for necessário;
  3. os campos que deseja produzir;
  4. os status da plataforma que devem ser aceitos ou descartados;
  5. 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].id

Objetos 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çãoParâmetrosUso
trimnenhumRemove espaços nas extremidades
lowernenhumConverte texto para minúsculas
uppernenhumConverte texto para maiúsculas
to_stringnenhumConverte valor para string
to_numbernenhumConverte string/número para int ou float
mapvalues obrigatório; unknown opcionalTraduz valores
dividevalue numérico obrigatórioDivide um número
multiplyvalue numérico obrigatórioMultiplica um número
concatvalues obrigatórioConcatena literais, $value e campos mapeados
parse_datetimeexatamente um de format/formats; timezone opcionalInterpreta data textual
epoch_to_datetimeunit: seconds ou milliseconds; timezone opcionalInterpreta timestamp
parse_jsonnenhumInterpreta uma string JSON
extractpath obrigatórioExtrai um path do valor atual
uniquenenhumRemove duplicatas de um array
joinseparator opcionalJunta itens de um array
vda_decodenenhumDecodifica 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 ​

CampoTipoObrigatórioDescrição
transactionstringsimIdentificador único da transação
prodIdstringnãoIdentificador do produto
prodNamestringnãoNome do produto
emailstringnãoE-mail do comprador
phonesstringnãoTelefone normalizado
statusstringnãoStatus normalizado
recurrencyinteiro ou booleannãoCiclo/indicador de recorrência
recurrencyPeriodstringnãoPeríodo da recorrência
pricenúmeronãoPreço da venda
commissionnúmeronãoComissão/valor recebido
amountinteironãoQuantidade
offerstringnãoOferta
srcstringnãoOrigem complementar
sckstringnãoCódigo de rastreamento
currencyCodeFromstringnãoCódigo da moeda
paymentTypestringnãoTipo de pagamento
purchaseDatetimedatetimesimData da compra
purchaseConfirmdatetimesimData da confirmação
subscriptionDateNextChargedatetimenãoPróxima cobrança
versioninteironãoVersão do payload
vdastringnãoVestige 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 ​

  1. version é 1.
  2. Todos os paths começam por request.body, request.query ou request.headers.
  3. transaction, purchaseDatetime e purchaseConfirm estão configurados.
  4. Datas terminam como datetime por meio de parse_datetime ou epoch_to_datetime.
  5. Valores monetários estão na unidade final esperada.
  6. Status externos são normalizados antes do filtro.
  7. VDA usa somente o campo vda.
  8. O botão Testar configuração apresenta contrato válido para eventos reais.
  9. Eventos que devem ser ignorados aparecem como filtered, não como erro.
  10. 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.