Webhooks
Verificar assinatura
Cada webhook é assinado com HMAC-SHA256, no mesmo esquema do Stripe. Sempre valide a assinatura antes de processar o evento.
O header de assinatura
O header x-onnix-signature traz dois campos: o t (timestamp) e o v1 (a assinatura). A assinatura é o HMAC-SHA256 da string {t}.{corpo_bruto}, usando como segredo o webhook_secret da cobrança (ou o secret do webhook configurado no dashboard).
Como verificar
- Extraia
tev1do header. - Concatene
{t}.{corpo_bruto}usando o corpo exatamente como recebido (bytes brutos). - Calcule o HMAC-SHA256 com seu segredo e compare com
v1em tempo constante. - Rejeite se o
testiver fora de uma janela (ex.: 5 min) para mitigar replay.
Use o corpo bruto
Frameworks que fazem parse automático do JSON quebram a verificação: a reserialização muda os bytes. Configure a rota para receber oraw body antes de validar.Header
x-onnix-signature: t=1780515992,v1=3a7f4b...e9d2
# t = timestamp (segundos Unix)
# v1 = HMAC-SHA256(secret, "{t}.{corpo_bruto}") em hexVerificação
import crypto from "node:crypto";
function verifyOnnixWebhook(rawBody, header, secret, toleranceSec = 300) {
// header: "t=1780515992,v1=3a7f...e9d2"
const parts = Object.fromEntries(
header.split(",").map((p) => p.split("=")),
);
const t = Number(parts.t);
const expected = crypto
.createHmac("sha256", secret)
.update(`${t}.${rawBody}`)
.digest("hex");
// comparação em tempo constante
const ok = crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(parts.v1),
);
// proteção contra replay
const fresh = Math.abs(Date.now() / 1000 - t) <= toleranceSec;
return ok && fresh;
}