Assinatura
Como confirmar que a entrega veio mesmo da plataforma.
O header
Se a cobrança foi criada com webhook_secret, ou o endpoint cadastrado tem secret, toda entrega chega com X-Webhook-Signature: o HMAC SHA-256 do corpo exato da requisição, em hexadecimal, prefixado por sha256=.
X-Webhook-Signature: sha256=6f1a...c93bVerificando
Calcule o HMAC sobre o corpo cru, antes de qualquer parse. Serializar o JSON de novo muda espaços e ordem de chaves, e a assinatura deixa de bater.
import { createHmac, timingSafeEqual } from 'crypto'
app.post(
'/hooks/pagamentos',
express.raw({ type: 'application/json' }),
(req, res) => {
const recebida = req.header('X-Webhook-Signature') ?? ''
const esperada =
'sha256=' +
createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(req.body)
.digest('hex')
const a = Buffer.from(recebida)
const b = Buffer.from(esperada)
if (a.length !== b.length || !timingSafeEqual(a, b)) {
return res.sendStatus(401)
}
res.sendStatus(200)
processarDepois(JSON.parse(req.body.toString('utf8')))
},
)Compare em tempo constante, com timingSafeEqual ou equivalente. Comparação com === vaza, pela diferença de tempo, quantos caracteres iniciais estavam certos.
Boas práticas
Recuse a requisição quando a assinatura não bater, com 401, e registre o caso: assinatura inválida é tentativa de forjar evento, não erro de rede.
secret cadastrado não há header de assinatura, e qualquer um que descubra a URL consegue simular um pagamento confirmado. Configure o segredo antes de colocar a integração no ar.Para trocar o segredo sem perder entregas, cadastre um segundo endpoint com o segredo novo, valide que ele está recebendo e só então remova o antigo.