Validar a assinatura
Como confirmar que o aviso veio de nós e não foi alterado no caminho.
Qualquer um que descubra a sua URL pode mandar um POST para ela. A assinatura é o que separa um aviso nosso de um falso.
Como funciona
- Pegue o segredo do canal no painel, em Notificações e logs (Mostrar / Gerar outro). Guarde no servidor, como
PROJECT_WEBHOOK_SECRET. - Calcule o HMAC-SHA256 de
"{X-Project-Timestamp}.{corpo cru}"com o segredo, em hex, e ponhasha256=na frente. - Compare com
X-Project-Signature-256em tempo constante. - Recuse avisos com mais de 5 minutos (
X-Project-Timestamp), para ninguém reaproveitar um aviso antigo. - Guarde o
idde cada aviso e ignore um que já processou.
Use o corpo cru
A assinatura é sobre os bytes exatos que chegaram. Converter para JSON e de volta para texto muda espaços e a ordem das chaves, e a assinatura deixa de bater — é o erro mais comum de quem integra webhook.
Exemplos
const crypto = require('crypto');
const express = require('express');
const app = express();
// O corpo CRU (express.raw): a assinatura é sobre o texto exato que chegou.
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const timestamp = req.get('X-Project-Timestamp') || '';
const received = req.get('X-Project-Signature-256') || '';
const expected = 'sha256=' + crypto
.createHmac('sha256', process.env.PROJECT_WEBHOOK_SECRET)
.update(timestamp + '.' + req.body)
.digest('hex');
const valid = received.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
const recent = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300;
if (!valid || !recent) return res.sendStatus(401);
const event = JSON.parse(req.body);
// event.id é o mesmo numa repetição: ignore o que já processou.
res.sendStatus(204);
});Trocar o segredo
Em Notificações e logs, Gerar outro cria um segredo novo para o canal. Atualize PROJECT_WEBHOOK_SECRET no seu servidor logo em seguida: a partir daí, os avisos chegam assinados com o segredo novo.