Validação de Assinatura HMAC-SHA256

Verificação criptográfica de autenticidade, headers anti-replay e exemplos em Node.js e Python.

Para garantir que as notificações HTTP recebidas em seu endpoint foram de fato enviadas pela NFER e que seu conteúdo não foi interceptado nem alterado no trânsito, cada requisição inclui uma assinatura criptográfica HMAC-SHA256.

1. Cabeçalhos HTTP Enviados pela NFER

Cabeçalho HTTPExemplo de ValorFinalidade
X-NFER-Signaturesha256=d3b07384...5f31Assinatura hexadecimal calculada via HMAC-SHA256 sobre ${timestamp}.${rawBody}.
X-NFER-Timestamp1726859100Timestamp UNIX em segundos do momento do despacho para prevenção de ataques de replay.
X-NFER-Eventnfe.autorizadaNome canônico do evento contido no payload.
X-NFER-Deliverydlv_01HXYZ7890ABCDEFIdentificador único desta tentativa de entrega para logs e rastreabilidade.
User-AgentNFER-Webhooks/1.0Identificação oficial do cliente HTTP da NFER.

2. Como a Assinatura é Calculada

O cálculo da assinatura vincula o timestamp ao corpo bruto (raw body) da requisição:

signed_payload = timestamp + "." + raw_body
expected_signature = "sha256=" + HMAC_SHA256(signed_payload, webhook_secret)

Utilize Sempre o Raw Body

Ao validar a assinatura, nunca utilize o JSON já parseado ou re-serializado (ex: JSON.stringify(req.body)). Diferenças de ordenação de chaves ou espaços em branco invalidam o hash. Configure seu framework para preservar o buffer bruto (express.raw() ou similar).

3. Exemplos de Implementação

Node.js / Express (TypeScript)

import express, { Request, Response } from 'express';
import crypto from 'crypto';
const app = express();
const WEBHOOK_SECRET = process.env.NFER_WEBHOOK_SECRET || 'whsec_...';
// IMPORTANTE: Capturar o corpo da requisição em formato Buffer bruto
app.post(
'/webhooks/nfer',
express.raw({ type: 'application/json' }),
(req: Request, res: Response) => {
const signature = req.headers['x-nfer-signature'] as string;

Python / FastAPI

from fastapi import FastAPI, Request, HTTPException, status
import hmac
import hashlib
import time
import os
app = FastAPI()
WEBHOOK_SECRET = os.getenv("NFER_WEBHOOK_SECRET", "whsec_...")
@app.post("/webhooks/nfer")
async def nfer_webhook(request: Request):
signature = request.headers.get("x-nfer-signature")