Webhook genérico: eventos, payload e assinatura

Receba no seu servidor um POST em JSON a cada evento escolhido, assinado com um segredo que só você e o VSL Max conhecem.

O webhook genérico manda para o seu servidor um POST em JSON sempre que acontece um dos eventos que você escolheu. É o caminho para ligar o VSL Max a um ERP, a uma área de membros ou a qualquer sistema próprio.

Como cadastrar um endpoint

  1. Abra Configurações › Integrações e, no cartão Webhook genérico, clique em Conectar.
  2. Em Adicionar endpoint, informe a URL do endpoint. Ela precisa ser https, sem usuário e senha na URL, na porta padrão, 443 ou 8443, e apontar para um endereço público. Endereço local ou de rede privada é recusado.
  3. Escolha os eventos e clique em Adicionar endpoint.
  4. Copie o segredo de assinatura que aparece na hora. Ele começa com whsec_ e não aparece de novo: depois disso a tela mostra só os últimos caracteres. Se perder, gere outro.

Só administradores criam e alteram endpoints. A conta aceita até 10 endpoints.

Eventos disponíveis

  • sale.approved: venda aprovada.
  • sale.refunded: reembolso.
  • sale.chargeback: chargeback.
  • sale.canceled: venda cancelada.
  • video.ready: um vídeo terminou de processar e está pronto.
  • balance.low: o aviso de saldo de plays baixo foi disparado (o mesmo gatilho do e-mail).
  • organization.suspended: a conta foi suspensa.

Se você não escolher nada, o endpoint recebe os quatro eventos de venda. O botão Testar manda um test.ping.

Formato da requisição

Toda entrega leva estes cabeçalhos:

  • Content-Type: application/json e User-Agent: VSLMax-Webhooks/1.0.
  • X-VSLMax-Event: o tipo do evento, o mesmo do campo type do corpo.
  • X-VSLMax-Delivery: o id da entrega, igual em todas as tentativas.
  • X-VSLMax-Signature: no formato t=<horário unix>,v1=<assinatura>.

Exemplo de corpo de uma venda aprovada:

{
  "id": "evt_3f2a9c1e-8b7d-4e21-9a55-0c1d2e3f4a5b",
  "type": "sale.approved",
  "createdAt": "2026-09-23T15:04:05.000Z",
  "data": {
    "sale": {
      "id": "3f2a9c1e-8b7d-4e21-9a55-0c1d2e3f4a5b",
      "externalId": "HP123456",
      "status": "approved",
      "amount": 197,
      "amountCents": 19700,
      "currency": "BRL",
      "occurredAt": "2026-09-23T15:03:58.000Z"
    },
    "conversion": { "id": "…", "name": "Hotmart principal", "platform": "hotmart" },
    "video": { "id": "…", "title": "VSL v3" },
    "attribution": "key",
    "visitorId": "…"
  }
}

Em reembolso e chargeback, amount e amountCents vêm negativos. video é null quando a venda não foi atribuída a nenhum vídeo, e attribution diz como ela foi ligada ao vídeo (none quando não foi).

Nos outros eventos, data traz:

  • video.ready: video com id, title e durationSeconds.
  • balance.low: balance com o aviso, plays inclusos, usados, restantes e o início e fim do ciclo.
  • organization.suspended: organization com id, status e o motivo.
  • test.ping: uma mensagem de teste.

Como verificar a assinatura

Confira a assinatura antes de confiar no corpo:

  1. Separe t e v1 do cabeçalho X-VSLMax-Signature.
  2. Calcule o HMAC-SHA256, com o seu segredo, do texto formado por t, um ponto e o corpo cru da requisição (antes de converter o JSON).
  3. Compare o resultado em hexadecimal com v1, em tempo constante.
  4. Recuse se t estiver a mais de 5 minutos do seu relógio. Isso evita o reenvio de uma requisição capturada.
  5. Use o id do evento para ignorar repetições: a retentativa manda o mesmo id.

Exemplo em Node.js:

import crypto from 'node:crypto'

function assinaturaValida(rawBody, header, segredo) {
  const partes = Object.fromEntries(header.split(',').map((p) => p.split('=')))
  const t = Number(partes.t)
  if (Math.abs(Date.now() / 1000 - t) > 300) return false
  const esperado = crypto.createHmac('sha256', segredo).update(`${t}.${rawBody}`).digest('hex')
  const a = Buffer.from(esperado)
  const b = Buffer.from(partes.v1 ?? '')
  return a.length === b.length && crypto.timingSafeEqual(a, b)
}

Respostas e retentativas

Responda com qualquer status 2xx em até 10 segundos. Qualquer outra resposta, ou falta de resposta, conta como falha. Redirecionamento não é seguido.

Depois de uma falha, tentamos de novo em 1 minuto, 5 minutos e então a cada 15 minutos, até 6 tentativas no total. Se todas falharem, a entrega fica como falha no histórico do endpoint e o cartão mostra quantas falhas seguidas houve.

Pausar, testar e trocar o segredo

  • Testar manda um test.ping na hora e mostra se foi entregue, o status HTTP e o tempo de resposta.
  • Últimas entregas lista as entregas recentes do endpoint, com status e número de tentativas.
  • Pausar suspende os envios sem apagar o endpoint. Retomar volta a enviar os eventos novos.
  • Gerar novo segredo troca o segredo na hora: as próximas entregas já saem assinadas com o novo. Atualize o seu servidor logo em seguida.
  • Excluir endpoint pede a sua senha e para as entregas imediatamente. Não tem como desfazer.