← Webhooks

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

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

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 "", 200

Headers 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.

HeaderExemplo de valor
X-Webhook-Signaturesha256=3ecb5a3d8f...
X-Webhook-Id65f2c1a4e8b9a400123abcde
X-Webhook-Event-Idevt_1737054821123_k9d3f7a1x
X-Webhook-Timestamp1737054821123
X-Webhook-Delivery-Attempt1
User-AgentMagnetCustomer-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.