X-Connect-Signature-256: sha256=HMAC_SHA256(secret, "<X-Connect-Timestamp>.<corpo cru>"). Calcule o HMAC sobre o timestamp + "." + os bytes crus da requisição (re-serializar o JSON muda o hash) e compare-o em tempo constante. O timestamp vai DENTRO do HMAC → um payload capturado não pode ser reenviado; recuse entregas com mais de 5 minutos. O secret (whsec_…) é mostrado UMA vez ao criar o endpoint.
# Verify X-Connect-Signature-256 on the server that RECEIVES the webhook.
# The header is: sha256=HMAC_SHA256(secret, "<X-Connect-Timestamp>.<raw body>")
# Recompute it over the timestamp + "." + the EXACT raw request body and compare.
# (Shell alone can't compare in constant time — use one of the snippets below.)
echo -n "${TIMESTAMP}.${RAW_BODY}" | openssl dgst -sha256 -hmac "whsec_YOUR_ENDPOINT_SECRET"
import crypto from "crypto";
const SECRET = "whsec_YOUR_ENDPOINT_SECRET";
function verify(req) {
const ts = req.header("X-Connect-Timestamp");
const sig = req.header("X-Connect-Signature-256") || "";
// Reject anything older than 5 minutes (anti-replay).
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const expected = "sha256=" + crypto
.createHmac("sha256", SECRET)
.update(ts + "." + req.rawBody) // req.rawBody = the EXACT bytes received
.digest("hex");
const a = Buffer.from(sig), b = Buffer.from(expected);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
É o ponto em que mais gente trava e em que muitos acabam não validando nada (uma brecha de segurança). Este código está completo e correto nas 4 linguagens.
O secret real sai do seu endpoint em Webhooks (é mostrado uma única vez; você pode girá-lo).