Segurança (HMAC)
Webhooks com autenticação HMAC — o padrão para novos webhooks — assinam toda requisição para que você possa verificar que ela veio da Magnet Customer.
Importante: Sempre valide a assinatura antes de processar qualquer payload de webhook. Rejeite requisições com assinaturas inválidas ou ausentes.
Como a assinatura é gerada
Calculamos HMAC-SHA256 sobre o corpo da requisição serializado em JSON usando o seu webhook secret, e enviamos o resultado no header X-Webhook-Signature, no formato sha256=<hex digest>. Esse header só é enviado quando o tipo de autenticação do webhook é HMAC com um secret configurado — webhooks com Bearer, Basic, headers customizados ou sem autenticação não recebem essa assinatura.
Validação em Node.js
const crypto = require('crypto');
function verifyWebhook(req) {
const signature = req.headers['x-webhook-signature'];
const secret = process.env.WEBHOOK_SECRET;
// Use raw body buffer — parsed JSON will break the signature
const hmac = crypto
.createHmac('sha256', secret)
.update(req.rawBody) // rawBody must be the original Buffer
.digest('hex');
const expected = `sha256=${hmac}`;
// Use timingSafeEqual to prevent timing attacks
const sigBuffer = Buffer.from(signature ?? '', 'utf8');
const expBuffer = Buffer.from(expected, 'utf8');
if (sigBuffer.length !== expBuffer.length) {
throw new Error('Invalid signature');
}
if (!crypto.timingSafeEqual(sigBuffer, expBuffer)) {
throw new Error('Invalid signature');
}
}
// Express example
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
try {
verifyWebhook({ headers: req.headers, rawBody: req.body });
} catch {
return res.status(401).json({ error: 'Unauthorized' });
}
const event = JSON.parse(req.body.toString());
// Process event...
res.sendStatus(200);
});Validação em Python
import hmac
import hashlib
import os
def verify_webhook(raw_body: bytes, signature_header: str) -> bool:
"""Verify HMAC-SHA256 signature from Magnet Customer webhook."""
secret = os.environ["WEBHOOK_SECRET"].encode("utf-8")
expected = "sha256=" + hmac.new(secret, raw_body, hashlib.sha256).hexdigest()
# Use compare_digest to prevent timing attacks
return hmac.compare_digest(expected, signature_header)
# Flask example
from flask import Flask, request, abort
app = Flask(__name__)
@app.route("/webhook", methods=["POST"])
def webhook():
signature = request.headers.get("X-Webhook-Signature", "")
if not verify_webhook(request.get_data(), signature):
abort(401)
event = request.json
# Process event...
return "", 200Headers enviados em cada requisição
O X-Webhook-Signature só aparece em webhooks com autenticação HMAC. Os demais headers abaixo são enviados em toda entrega, independente do tipo de autenticação.
| Header | Exemplo de valor |
|---|---|
| X-Webhook-Signature | sha256=3ecb5a3d8f... |
| X-Webhook-Id | 65f2c1a4e8b9a400123abcde |
| X-Webhook-Event-Id | evt_1737054821123_k9d3f7a1x |
| X-Webhook-Timestamp | 1737054821123 |
| X-Webhook-Delivery-Attempt | 1 |
| User-Agent | MagnetCustomer-Webhook/1.0 |
Proteção contra replay
A assinatura não cobre o X-Webhook-Timestamp — timestamp e assinatura são independentes, então uma requisição capturada poderia, tecnicamente, ser reenviada com a assinatura original ainda válida. Como camada adicional de defesa, recomendamos rejeitar entregas cujo X-Webhook-Timestamp (epoch Unix, em milissegundos) seja mais antigo que alguns minutos. Isso é uma recomendação para a sua própria integração, não uma garantia criptográfica dada pela assinatura.