Assinatura HMAC — o que é e como usar
Os webhooks do SmartPerformance SFA — de entrada (você recebe de fora) e de saída (o SFA notifica sistemas externos) — podem usar HMAC. Esta página explica o conceito e mostra, com exemplos prontos, como assinar e verificar.
Para o webhook de entrada, há também o modo API key (um token num header, sem assinar o corpo) — default e bem mais simples de integrar. Ver API keys da organização. Use HMAC quando quiser mais garantias (segredo não trafega + integridade do corpo).
O que é HMAC
HMAC (Hash-based Message Authentication Code) é uma assinatura calculada sobre uma mensagem usando uma chave secreta compartilhada entre quem envia e quem recebe:
assinatura = HMAC_sha256(corpo_da_requisição, segredo)
Quem recebe recalcula o HMAC do corpo com o mesmo segredo e compara com a assinatura recebida. Se baterem, prova duas coisas ao mesmo tempo:
- Autenticidade — só quem tem o segredo consegue gerar a assinatura correta.
- Integridade — qualquer alteração de 1 byte no corpo muda a assinatura.
Não é criptografia (o corpo não é cifrado — trafegue sempre por HTTPS); é uma prova de origem e de que o conteúdo não foi adulterado. Diferente de uma API key fixa, a assinatura muda a cada requisição (depende do corpo), então não adianta capturar e reenviar outro payload.
- Assine o corpo bruto (os bytes exatos enviados), antes de qualquer reserialização. Se você
faz
JSON.parseeJSON.stringifyde novo, a ordem/espaços mudam e a assinatura não bate. - A saída é em hex (minúsculo). O SFA aceita o prefixo comum
sha256=. - O segredo é revelado uma única vez (na criação/rotação). Guarde-o com segurança.
Webhook de ENTRADA (o sistema externo → SFA)
O sistema externo assina o corpo e envia a assinatura no header configurado (default
x-signature). O SFA valida em tempo constante; assinatura ausente/errada ⇒ 401 (a entrega
nem é gravada).
Os valores (algorithm, signatureHeader, URL de ingestão, segredo) aparecem ao criar o endpoint em
Webhooks → Entrada.
bash / openssl
BODY='{"cnpj":"12345678000190"}'
SECRET='<segredo-do-endpoint>'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')
curl -X POST "https://SUA-API/api/webhooks/ingest/<publicId>" \
-H "content-type: application/json" \
-H "x-signature: $SIG" \
-d "$BODY"
# -> 202 { "deliveryId": "..." } (assinatura inválida -> 401)
Node.js
import crypto from "node:crypto";
const body = JSON.stringify({ cnpj: "12345678000190" }); // assine ESTA string; envie ELA no -d
const sig = crypto.createHmac("sha256", SECRET).update(body).digest("hex");
await fetch("https://SUA-API/api/webhooks/ingest/<publicId>", {
method: "POST",
headers: { "content-type": "application/json", "x-signature": sig },
body, // exatamente o mesmo texto que foi assinado
});
Python
import hmac, hashlib, requests
body = '{"cnpj":"12345678000190"}' # a MESMA string assinada e enviada
sig = hmac.new(SECRET.encode(), body.encode(), hashlib.sha256).hexdigest()
requests.post("https://SUA-API/api/webhooks/ingest/<publicId>",
headers={"content-type": "application/json", "x-signature": sig},
data=body)
Depois de recebida, a entrega dispara o pipeline do gatilho do endpoint — onde você trata o payload e aciona ações (chamar/rodar conectores, chamar outra API). Ver Webhooks de entrada.
Webhook de SAÍDA (SFA → o seu sistema)
Quando um gatilho ocorre, o SFA faz um POST assinado na sua URL. Você verifica a assinatura
para ter certeza de que veio do SFA. Headers enviados:
X-SFA-Event— a key do gatilho (ex.:sync.completed).X-SFA-Delivery— id da entrega.X-SFA-Signature—sha256=<hmac>do corpo bruto com o segredo da assinatura (revelado ao criar).
Verificação (Node.js), em tempo constante:
import crypto from "node:crypto";
function verify(rawBody, header, secret) {
const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
const got = (header ?? "").replace(/^sha256=/, "");
const a = Buffer.from(got, "hex");
const b = Buffer.from(expected, "hex");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// app.post(..., (req) => verify(req.rawBody, req.headers["x-sfa-signature"], SECRET))
Use express.raw() (ou equivalente) para pegar o corpo bruto — não o objeto já parseado.
Segurança & limites
- HMAC prova autenticidade + integridade do corpo, não confidencialidade — use HTTPS.
- Rotação: trocar o segredo invalida o antigo na hora (Rotacionar segredo na UI). Faça a virada coordenada com o outro lado.
- Anti-replay (rejeitar reenvio do mesmo corpo dentro de uma janela, via timestamp/nonce) é follow-up; por ora, HMAC + HTTPS + idempotência no pipeline cobrem os casos comuns.
- O segredo fica em texto no banco (não há Vault/KMS ainda) — dívida registrada.