Assinatura e verificação

Verifique o header X-FonteData-Signature (HMAC-SHA256) antes de confiar no conteúdo, com exemplos em Python e Node.

Toda requisição de webhook enviada pela FonteData carrega um cabeçalho de assinatura que permite confirmar que o conteúdo realmente veio da FonteData e não foi alterado no caminho.

O cabeçalho

X-FonteData-Signature: sha256=<hex>

O valor é o HMAC-SHA256 calculado sobre o corpo bruto (raw body) da requisição, usando como chave o segredo de assinatura gerado quando você cadastrou o webhook (veja Visão geral). O resultado é codificado em hexadecimal e prefixado com sha256=.

Por que verificar

Sem verificar a assinatura, qualquer requisição POST para o seu endpoint — inclusive de terceiros mal-intencionados que descobrirem a URL — seria tratada como um evento legítimo. A verificação garante que só eventos assinados com o seu segredo de assinatura sejam processados.

Use sempre uma comparação em tempo constante (hmac.compare_digest em Python, crypto.timingSafeEqual em Node) em vez de ==/===, para não expor o segredo a um ataque de timing.

Verificação em Python

import hashlib
import hmac

def verificar_assinatura(corpo_bruto: bytes, header_assinatura: str, secret: str) -> bool:
    esperado = "sha256=" + hmac.new(
        secret.encode("utf-8"),
        corpo_bruto,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(esperado, header_assinatura)
# Exemplo de uso em um handler Flask/FastAPI
from fastapi import FastAPI, Request, HTTPException

app = FastAPI()
WEBHOOK_SECRET = "seu_segredo_de_assinatura"

@app.post("/webhooks/fontedata")
async def receber_webhook(request: Request):
    corpo_bruto = await request.body()
    assinatura = request.headers.get("X-FonteData-Signature", "")

    if not verificar_assinatura(corpo_bruto, assinatura, WEBHOOK_SECRET):
        raise HTTPException(status_code=401, detail="Assinatura inválida")

    evento = await request.json()
    # processar evento...
    return {"ok": True}

Verificação em Node.js

const crypto = require("crypto");

function verificarAssinatura(corpoBruto, headerAssinatura, secret) {
  const esperado =
    "sha256=" +
    crypto.createHmac("sha256", secret).update(corpoBruto).digest("hex");

  const bufEsperado = Buffer.from(esperado);
  const bufRecebido = Buffer.from(headerAssinatura || "");

  if (bufEsperado.length !== bufRecebido.length) {
    return false;
  }

  return crypto.timingSafeEqual(bufEsperado, bufRecebido);
}
// Exemplo de uso em um handler Express
const express = require("express");
const app = express();
const WEBHOOK_SECRET = "seu_segredo_de_assinatura";

app.post(
  "/webhooks/fontedata",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const assinatura = req.headers["x-fontedata-signature"] || "";

    if (!verificarAssinatura(req.body, assinatura, WEBHOOK_SECRET)) {
      return res.status(401).json({ erro: "Assinatura inválida" });
    }

    const evento = JSON.parse(req.body);
    // processar evento...
    return res.status(200).json({ ok: true });
  }
);

Rotação do segredo

Se o segredo de assinatura vazar, ou você simplesmente quiser trocá-lo, rotacione pelo painel (Webhooks → seu webhook → rotacionar segredo). Um novo segredo é gerado e exibido uma única vez, exatamente como na criação; o anterior deixa de valer a partir da rotação.