Verificando assinaturas de webhook
Como verificar o cabeçalho X-Tokeflow-Signature em webhooks recebidos usando HMAC-SHA256, com um exemplo completo em Node/Express.
Todo webhook que a Tokeflow envia carrega um cabeçalho X-Tokeflow-Signature. Verificá-lo comprova que a requisição
veio da Tokeflow e não foi adulterada em trânsito. Sempre verifique antes de agir sobre um webhook — um
endpoint não verificado pode ser falsificado por qualquer pessoa que descubra sua URL.
O cabeçalho de assinatura
X-Tokeflow-Signature: t=1737030609,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd| Parte | Significado |
|---|---|
t | O timestamp Unix (em segundos) em que a assinatura foi gerada. |
v1 | A assinatura HMAC-SHA256, codificada em hexadecimal. |
A assinatura é calculada sobre a string {t}.{raw_request_body} — o timestamp, um . literal, e então
os bytes brutos exatos do corpo da requisição — usando o signing secret do seu endpoint como chave.
Você obtém o signing secret (que se parece com whsec_…) ao criar o endpoint no Dashboard da
Tokeflow. Trate-o como uma senha: armazene-o como um segredo, nunca faça commit dele, e use-o literalmente — incluindo
o prefixo whsec_ — como a chave HMAC.
Passos de verificação
- Leia o corpo bruto da requisição como bytes/string, antes de qualquer parsing de JSON.
- Leia o cabeçalho
X-Tokeflow-Signaturee divida-o emtev1. - Calcule
HMAC_SHA256(secret,+ "${t}.${rawBody}" +)e codifique-o em hexadecimal. - Compare o seu valor com
v1usando uma comparação de tempo constante. - Rejeite a requisição se o timestamp
testiver fora da sua tolerância (por exemplo, mais antigo que 5 minutos) para prevenir ataques de replay.
Capture o corpo bruto. A assinatura é calculada sobre os bytes exatos que a Tokeflow enviou. Se o seu framework faz parsing do JSON e o reserializa, a ordem das chaves e os espaços em branco podem mudar e a assinatura deixará de corresponder. Configure sua rota para expor o corpo bruto (veja o exemplo abaixo).
Exemplo em Node / Express
import express from 'express';
import crypto from 'crypto';
const app = express();
const SIGNING_SECRET = process.env.TOKEFLOW_WEBHOOK_SECRET; // "whsec_..."
const TOLERANCE_SECONDS = 5 * 60;
// Use the raw body parser on the webhook route so the bytes are untouched.
app.post(
'/webhooks/tokeflow',
express.raw({ type: 'application/json' }),
(req, res) => {
const header = req.get('X-Tokeflow-Signature') || '';
const rawBody = req.body; // a Buffer, thanks to express.raw
if (!verify(rawBody, header, SIGNING_SECRET)) {
return res.status(400).send('Invalid signature');
}
const event = JSON.parse(rawBody.toString('utf8'));
// Acknowledge immediately, then process asynchronously.
res.status(200).send('ok');
handleEvent(event); // dedupe on event.id, reconcile via the API
},
);
function verify(rawBody, signatureHeader, secret) {
// Parse "t=...,v1=..."
const parts = Object.fromEntries(
signatureHeader.split(',').map((kv) => kv.split('=')),
);
const timestamp = Number(parts.t);
const provided = parts.v1;
if (!timestamp || !provided) return false;
// Reject stale timestamps (replay protection).
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - timestamp) > TOLERANCE_SECONDS) return false;
// Recompute the HMAC over `${t}.${rawBody}`.
const signedPayload = `${timestamp}.${rawBody.toString('utf8')}`;
const expected = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
// Constant-time compare.
const a = Buffer.from(expected, 'hex');
const b = Buffer.from(provided, 'hex');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}O esquema de assinatura é compatível com o padrão HMAC-SHA256 t=…,v1=… amplamente utilizado, então bibliotecas
auxiliares existentes que verificam esse formato funcionarão — basta passar o seu segredo whsec_… como a chave e verificar contra
o corpo bruto.
Após verificar
- Responda com um status
2xxrapidamente; faça o trabalho pesado de forma assíncrona. - Uma resposta não-
2xxou um timeout faz com que a Tokeflow repita a entrega com backoff. - Deduplique pelo
iddo evento e reconcilie o estado via a API. Veja Tipos de evento de webhook.