Verificar assinaturas de webhooks
Garantir a procedência dos webhooks é crítico. Um invasor poderia enviar um payload falso (p. ex. {"event":"quote.paid"}) para que você libere algo sem que o pagamento seja real. Por isso você deve validar a assinatura.
Como o Cord assina
Cada entrega envia duas assinaturas do corpo bruto, para que você possa migrar sem janela de corte:
X-Cord-Signature-V1(recomendada):t=<unix timestamp>,v1=<hex hmac-sha256 of "{t}.{body}">. Inclui o timestamp dentro do que é assinado, então você pode rejeitar uma entrega antiga reenviada (proteção contra replay). Durante uma rotação de segredo, ela pode trazer dois paresv1=; basta que um deles confira.X-Cord-Signature(legada, mantida por compatibilidade):sha256=<hex hmac-sha256 of the body>, sem timestamp.
Além da assinatura, cada entrega traz X-Cord-Event (nome do evento), X-Cord-Event-Id (id estável do evento, igual nas novas tentativas e em um reenvio manual — use-o para deduplicar), X-Cord-Delivery-Id (muda a cada tentativa) e Idempotency-Key (repete o mesmo valor de X-Cord-Event-Id, para frameworks que o leem automaticamente).
Com o SDK oficial (recomendado)
O @flouviahq/node já traz o verificador pronto: exige X-Cord-Signature-V1, compara em tempo constante e rejeita uma entrega com mais de 300 segundos (configurável com tolerance). A assinatura legada só é aceita se você pedir com allowLegacySignature: true.
import { constructEvent, CordWebhookSignatureError } from '@flouviahq/node';
// Exemplo com um Route Handler do Next.js — req.text() já é o corpo bruto
export async function POST(req: Request) {
try {
const event = await constructEvent(await req.text(), req.headers, process.env.CORD_WEBHOOK_SECRET!);
// event.event, event.data — o catálogo de eventos está no guia de webhooks
return new Response('ok');
} catch (err) {
if (err instanceof CordWebhookSignatureError) return new Response('Invalid signature', { status: 400 });
throw err;
}
}
npx @flouviahq/cli init cria essa rota para você, e npx @flouviahq/cli listen permite testá-la na sua máquina com assinaturas reais.
Verificação manual em Node.js (Express)
Se você não usa o SDK, pode verificar a assinatura legada manualmente:
const crypto = require('crypto');
app.post('/webhook/cord', express.raw({ type: 'application/json' }), (req, res) => {
const received = req.headers['x-cord-signature'] || ''; // "sha256=<hex>"
const secret = process.env.CORD_WEBHOOK_SECRET;
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(req.body) // req.body é o Buffer bruto, sem parsear
.digest('hex');
// Comparação em tempo constante (os dois buffers devem ter o mesmo tamanho)
const ok = received.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
if (!ok) return res.status(401).send('Invalid signature');
const event = req.headers['x-cord-event'];
const payload = JSON.parse(req.body.toString('utf8'));
// ... processe o evento (payload.data.id, payload.data.folio, etc.)
res.status(200).send('Received');
});
Importante: assine sobre o corpo bruto (use express.raw, não express.json), ou o hash não vai coincidir. Para verificar X-Cord-Signature-V1 manualmente, calcule o HMAC sobre "{timestamp}.{body}" em vez de só o corpo, e valide que o timestamp está dentro da sua tolerância — ou use o SDK, que já faz isso por você.