Webhooks são a forma recomendada de saber quando um pagamento foi confirmado. Neste tutorial você vai montar um endpoint idempotente usando o corpo bruto e a assinatura X-Sinnu-Signature.
Passo 1: exponha um endpoint
Crie uma rota HTTP pública que aceite POST e configure essa URL no painel Sinnu, em Integrações.
Passo 2: capture o corpo bruto
A validação HMAC depende do corpo exatamente como recebido. Em Express, use express.raw() nessa rota.
Passo 3: valide a assinatura
import crypto from "node:crypto";
import express from "express";
const app = express();
const WEBHOOK_SECRET = process.env.SINNU_WEBHOOK_SECRET;
const processados = new Set(); // troque por seu banco em produção
function extrairAssinatura(header = "") {
return header.startsWith("sha256=") ? header.slice("sha256=".length) : header;
}
function assinaturaValida(rawBody, header) {
const assinatura = extrairAssinatura(header);
const esperada = crypto
.createHmac("sha256", WEBHOOK_SECRET)
.update(rawBody)
.digest("hex");
const a = Buffer.from(assinatura, "hex");
const b = Buffer.from(esperada, "hex");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
app.post(
"/webhooks/sinnu",
express.raw({ type: "application/json" }),
(req, res) => {
const header = req.header("X-Sinnu-Signature") ?? "";
if (!assinaturaValida(req.body, header)) {
return res.status(400).send("assinatura inválida");
}
const evento = JSON.parse(req.body.toString("utf8"));
const chave = [evento.type, evento.data.id, evento.data.status].join(":");
if (processados.has(chave)) {
return res.status(200).send("já processado");
}
if (evento.type === "transaction.approved") {
console.log("Pagamento confirmado:", evento.data.id);
// marque o pedido como pago no seu banco
}
processados.add(chave);
return res.status(200).send("ok");
},
);
app.listen(3000, () => console.log("ouvindo webhooks na porta 3000"));Passo 4: garanta idempotência
O mesmo evento pode ser entregue mais de uma vez. Use uma chave derivada de type, data.id e data.status para evitar processamento duplicado.
Responda rápido
Devolva 200 assim que validar e persistir o recebimento. Se o processamento for demorado, enfileire-o e processe de forma assíncrona.
Passo 5: teste de ponta a ponta
- Suba o servidor e exponha-o com um túnel, como
ngrok. - Configure a URL pública no painel Sinnu.
- Crie um link seguindo o tutorial Cobrar via Pix em 5 minutos.
- Realize o pagamento e confirme que o evento
transaction.approvedchegou e foi processado uma única vez.
Fechamento
Veja também o guia Receber webhooks para detalhes de payload e a referência da API.