Como validar que os eventos recebidos vieram da Cordialy usando HMAC-SHA256
Cada requisição inclui o header X-Cordialy-Signature com uma assinatura HMAC-SHA256 do corpo. Valide essa assinatura antes de processar qualquer evento.
O secret é gerado automaticamente quando você cadastra um webhook em Integrações → Webhooks. Ele é exibido uma única vez logo após a criação — copie e guarde em um local seguro.
Se você perder o secret, não é possível recuperá-lo. Delete o webhook e crie um novo para gerar um secret diferente.
A assinatura é calculada sobre o body bruto (bytes exatos recebidos na requisição).
Se você parsear o body antes de validar (ex: express.json() no Node.js), o body já foi transformado e a assinatura não vai bater. Leia o body como stream/raw antes de qualquer parsing.
import crypto from 'crypto';import express from 'express';const app = express();// IMPORTANTE: use express.raw() na rota do webhook, não express.json()app.post('/webhook/cordialy', express.raw({ type: 'application/json' }), (req, res) => { const signature = req.headers['x-cordialy-signature'] as string; const secret = process.env.CORDIALY_WEBHOOK_SECRET!; if (!validarAssinatura(req.body, signature, secret)) { return res.status(401).send('Assinatura inválida'); } const evento = JSON.parse(req.body.toString()); // processar evento... res.status(200).send('ok');});function validarAssinatura(rawBody: Buffer, signature: string, secret: string): boolean { const esperado = 'sha256=' + crypto .createHmac('sha256', secret) .update(rawBody) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(esperado) );}
Por que timingSafeEqual / compare_digest / hash_equals?
Comparações simples (===, ==) são vulneráveis a ataques de timing — um atacante pode descobrir o secret medindo o tempo de resposta. As funções de comparação constante eliminam esse risco.