Webhooks · SegurançaAbrir para IA
Valide os bytes antes de interpretar o JSON
A assinatura usa HMAC-SHA256 sobre o timestamp e o corpo bruto. Faça a comparação em tempo constante antes de executar qualquer regra de negócio.
Headers assinados
evt_…
Unix seconds
1
v1=<base64url>
Entrada assinada
Concatene o valor exato de Mandai-Timestamp, um ponto e o corpo bruto recebido: <timestamp>.<raw body>. Não faça parse, reserialização, trim ou alteração de encoding antes da verificação.
Exemplo Node.js e Bun
import { createHmac, timingSafeEqual } from 'node:crypto';
const MAX_AGE_SECONDS = 300;
// Keep both entries during a rotation overlap; drop the old one once it closes.
const SECRETS: Record<string, string> = {
'1': process.env.MANDAI_WEBHOOK_SECRET!,
};
export function verifyMandaiWebhook(input: {
rawBody: string;
timestamp: string;
signature: string;
version: string;
}): boolean {
const timestamp = Number(input.timestamp);
const received = input.signature.match(/^v1=(.+)$/)?.[1];
const secret = SECRETS[input.version];
const now = Math.floor(Date.now() / 1000);
if (!Number.isSafeInteger(timestamp) || !received || !secret) return false;
if (Math.abs(now - timestamp) > MAX_AGE_SECONDS) return false;
const expected = createHmac('sha256', secret)
.update(`${input.timestamp}.${input.rawBody}`)
.digest('base64url');
const left = Buffer.from(received);
const right = Buffer.from(expected);
return left.length === right.length && timingSafeEqual(left, right);
}Rotação de segredo
Ao rotacionar, você informa overlap_seconds. Durante essa janela o Mandaí continua assinando com o segredo anterior; quando ela fecha, passa a assinar com o novo. Antes de rotacionar, faça o deploy de um verificador que aceita as duas versões e usa Mandai-Signature-Version para escolher o segredo. Depois que a janela fechar, remova o segredo antigo. overlap_seconds igual a zero corta na hora.
Checklist de verificação
- Rejeite timestamp com diferença superior a cinco minutos.
- Aceite apenas versões que você conhece; durante uma rotação isso inclui a anterior e a nova.
- Use o segredo do mesmo ambiente do endpoint.
- Compare o digest em tempo constante.
- Persista event_id antes de responder 2xx.