Pular para o conteúdo

Webhooks

Além de consultar pagamentos (pull), você pode receber push: o Vmix Pay faz um HTTP POST assinado ao seu endpoint quando um evento ocorre. Os endpoints de webhook são geridos no painel, em /painel/webhooks (não há endpoint público para configurá-los).

Evento Quando
payment.received Cobrança paga (inclui paid_at).
charge.expired Cobrança vencida.
charge.cancelled Cobrança cancelada.
refund.completed Devolução (total/parcial) concluída (inclui refund_id, amount).

O corpo é um objeto plano (flat), em snake_case, sem PII. O amount é uma string em reais. Exemplo de payment.received:

{
"event": "payment.received",
"event_id": "550e8400-e29b-41d4-a716-446655440000",
"charge_id": "9b2f1c7a-3d4e-4f5a-8b6c-7d8e9f0a1b2c",
"account_id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"status": "paid",
"amount": "199.90",
"paid_at": "2026-06-22T12:00:00.000Z"
}

Cada entrega traz:

Header Conteúdo
X-Vmix-Event O tipo do evento (ex.: payment.received).
X-Vmix-Timestamp Epoch em segundos usado na assinatura.
X-Vmix-Signature sha256=<hex> do HMAC-SHA256(secret, "${timestamp}.${rawBody}").
X-Vmix-Delivery-Id UUID único por tentativa — use para deduplicar.
  1. Anti-replay: rejeite se o X-Vmix-Timestamp estiver fora da sua janela (ex.: ±5 minutos).

  2. Assinatura: recompute o HMAC-SHA256 sobre `${timestamp}.${rawBody}` — usando o corpo cru recebido (não reserializado) e o secret do endpoint — e compare com o X-Vmix-Signature em tempo constante.

  3. Dedup: o transporte é at-least-once; você pode receber a mesma entrega mais de uma vez. Deduplique por X-Vmix-Delivery-Id (ou pelo event_id do corpo).

  4. Responda 2xx para confirmar. Qualquer outro código (ou timeout) é tratado como falha e dispara retry.

O secret é o valor render-once mostrado ao criar o endpoint no painel.

import crypto from "node:crypto";
const SECRET = process.env.VMIX_WEBHOOK_SECRET; // o secret render-once
function verify(req, rawBody) {
const timestamp = req.headers["x-vmix-timestamp"];
const signature = req.headers["x-vmix-signature"];
// 1) Rejeite timestamps antigos (proteção contra replay).
const ageSec = Math.abs(Date.now() / 1000 - Number(timestamp));
if (!timestamp || ageSec > 300) return false;
// 2) Recompute o HMAC-SHA256 sobre "{timestamp}.{rawBody}".
const expected =
"sha256=" +
crypto
.createHmac("sha256", SECRET)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
// 3) Compare em tempo constante.
const a = Buffer.from(expected);
const b = Buffer.from(signature ?? "", "utf8");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}

A URL do endpoint pode ser http ou https — a assinatura HMAC garante a autenticidade e integridade mesmo sobre http; usar http perde apenas a confidencialidade do payload. https é recomendado.

Uma falha de entrega (não-2xx ou timeout) é reenviada automaticamente até o limite de tentativas configurado; ao esgotar, a entrega vai para uma fila de dead-letter. A idempotência por (endpoint, event_id) garante que um mesmo evento não é entregue em duplicidade — mas, como o transporte é at-least-once, sempre deduplique pelo X-Vmix-Delivery-Id ou event_id no seu lado.