Webhooks assinados
O CDE assina o que envia com HMAC-SHA256. São dois formatos, e eles não são intercambiáveis: a publicação em site próprio assina o horário junto com o corpo, nos cabeçalhos x-cde-timestamp e x-cde-signature; os webhooks de evento assinam só o corpo, no cabeçalho cde-signature.
A diferença existe por um motivo: a publicação precisa resistir a reenvio de uma requisição capturada, então o horário entra na assinatura e o destino recusa o que estiver fora da janela. O webhook de evento é entregue com registro e pode ser reenviado por você, de propósito — ali o horário na assinatura atrapalharia.
Publicação em site próprio
- x-cde-timestamp: horário do envio, em segundos desde 1970.
- x-cde-signature: sha256=<hexadecimal> do HMAC sobre a cadeia `${timestamp}.${corpo}`.
- O destino recalcula a mesma cadeia a partir do corpo BRUTO e do cabeçalho de horário.
- Recuse o que estiver fora da janela que você considerar segura (o modelo do CDE usa cinco minutos).
Webhooks de evento
- cde-event: o nome do evento que você registrou no painel.
- cde-signature: sha256=<hexadecimal> do HMAC sobre o corpo, sem horário.
- cde-delivery-id: identificador da entrega, para você descartar repetição.
- cde-endpoint e cde-version: qual endpoint recebeu e a versão do formato.
Verificar a assinatura
Node.js
import { createHmac, timingSafeEqual } from 'node:crypto';
function confere(segredo, corpoBruto, assinatura) {
const esperado = 'sha256=' + createHmac('sha256', segredo).update(corpoBruto).digest('hex');
const a = Buffer.from(esperado);
const b = Buffer.from(assinatura ?? '');
return a.length === b.length && timingSafeEqual(a, b);
}PHP
function confere(string $segredo, string $corpoBruto, ?string $assinatura): bool {
$esperado = 'sha256=' . hash_hmac('sha256', $corpoBruto, $segredo);
return is_string($assinatura) && hash_equals($esperado, $assinatura);
}Python
import hmac, hashlib
def confere(segredo: str, corpo_bruto: bytes, assinatura: str) -> bool:
esperado = 'sha256=' + hmac.new(segredo.encode(), corpo_bruto, hashlib.sha256).hexdigest()
return hmac.compare_digest(esperado, assinatura or '')- Use sempre o corpo BRUTO da requisição. Se o seu servidor já converteu o JSON em objeto e você o serializar de novo, a assinatura não vai bater.
- Compare com função de tempo constante (timingSafeEqual, hash_equals, compare_digest), não com igualdade simples.
- Guarde o identificador da entrega e descarte repetição: o reenvio é um recurso, não um erro.
Não achou o que precisa?
A documentação segue o produto e cresce com as perguntas que chegam. Escreva pelo formulário de contato dizendo o que faltou.