Verificação e Segurança
Como verificar a assinatura HMAC de um webhook da ValidaPay antes de confiar no payload.
Todo webhook chega com o header X-Webhook-Signature, que não é um HMAC simples do corpo. Ele é composto:
Texto
X-Webhook-Signature: t=1757083200000,v1=9f2b8c...A string assinada é o timestamp, um ponto, e o corpo:
Texto
v1 = HMAC_SHA256(secret, `${t}.${corpo_bruto}`)Use o corpo bruto, byte a byte. Se você fizer
JSON.parse e reserializar, o espaçamento e a ordem das chaves mudam e o HMAC deixa de bater.Em Express, isso significa capturar o raw body antes do express.json():
JavaScript
import crypto from 'crypto';
import express from 'express';
const app = express();
app.post('/webhooks/validapay',
express.raw({ type: 'application/json' }),
(req, res) => {
const header = req.get('X-Webhook-Signature') ?? '';
const { t, v1 } = Object.fromEntries(
header.split(',').map((part) => part.split('='))
);
const esperado = crypto
.createHmac('sha256', process.env.VALIDAPAY_WEBHOOK_SECRET)
.update(`${t}.${req.body.toString('utf8')}`)
.digest('hex');
const confere =
v1?.length === esperado.length &&
crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(esperado));
if (!confere) return res.sendStatus(401);
const idadeMs = Date.now() - Number(t);
if (idadeMs > 5 * 60 * 1000) return res.sendStatus(401);
res.sendStatus(200);
processarDepois(JSON.parse(req.body.toString('utf8')));
}
);Compare com
timingSafeEqual, não com ===. E rejeite assinaturas antigas. Sem janela de tolerância, uma assinatura capturada vale para sempre.Headers
X-Webhook-Signaturet=<timestamp>,v1=<hmac hex>
x-access-tokenToken estático opcional, se configurado no cadastro
Próximos passos
Essa página foi útil?