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).
Eventos
Seção intitulada “Eventos”| 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 payload
Seção intitulada “O payload”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"}Os headers
Seção intitulada “Os headers”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. |
Como validar a assinatura
Seção intitulada “Como validar a assinatura”-
Anti-replay: rejeite se o
X-Vmix-Timestampestiver fora da sua janela (ex.: ±5 minutos). -
Assinatura: recompute o
HMAC-SHA256sobre`${timestamp}.${rawBody}`— usando o corpo cru recebido (não reserializado) e osecretdo endpoint — e compare com oX-Vmix-Signatureem tempo constante. -
Dedup: o transporte é at-least-once; você pode receber a mesma entrega mais de uma vez. Deduplique por
X-Vmix-Delivery-Id(ou peloevent_iddo corpo). -
Responda
2xxpara confirmar. Qualquer outro código (ou timeout) é tratado como falha e dispara retry.
Código de verificação
Seção intitulada “Código de verificação”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);}<?php$secret = getenv('VMIX_WEBHOOK_SECRET'); // o secret render-once
function verify(string $secret, array $headers, string $rawBody): bool { $timestamp = $headers['X-Vmix-Timestamp'] ?? ''; $signature = $headers['X-Vmix-Signature'] ?? '';
// 1) Anti-replay (±5 min). if ($timestamp === '' || abs(time() - (int) $timestamp) > 300) { return false; }
// 2) Recompute o HMAC sobre "{timestamp}.{rawBody}". $expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);
// 3) Compare em tempo constante. return hash_equals($expected, $signature);}import hashlibimport hmacimport time
SECRET = os.environ["VMIX_WEBHOOK_SECRET"] # o secret render-once
def verify(headers, raw_body: bytes) -> bool: timestamp = headers.get("X-Vmix-Timestamp", "") signature = headers.get("X-Vmix-Signature", "")
# 1) Anti-replay (±5 min). if not timestamp or abs(time.time() - int(timestamp)) > 300: return False
# 2) Recompute o HMAC sobre "{timestamp}.{rawBody}". signed = f"{timestamp}.".encode() + raw_body expected = "sha256=" + hmac.new(SECRET.encode(), signed, hashlib.sha256).hexdigest()
# 3) Compare em tempo constante. return hmac.compare_digest(expected, signature)URL: HTTP ou HTTPS
Seção intitulada “URL: HTTP ou HTTPS”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.