Verificación de firmas de webhooks
Cómo verificar el encabezado X-Tokeflow-Signature en los webhooks entrantes usando HMAC-SHA256, con un ejemplo completo en Node/Express.
Cada webhook que envía Tokeflow incluye un encabezado X-Tokeflow-Signature. Verificarlo demuestra que la solicitud
provino de Tokeflow y que no fue manipulada en tránsito. Verifica siempre antes de actuar sobre un webhook: un
endpoint sin verificar puede ser suplantado por cualquiera que conozca su URL.
El encabezado de firma
X-Tokeflow-Signature: t=1737030609,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd| Part | Significado |
|---|---|
t | La marca de tiempo Unix (en segundos) en la que se generó la firma. |
v1 | La firma HMAC-SHA256, codificada en hexadecimal. |
La firma se calcula sobre la cadena {t}.{raw_request_body}: la marca de tiempo, un . literal, y luego
los bytes crudos exactos del cuerpo de la solicitud, con clave en el secreto de firma de tu endpoint.
Obtienes el secreto de firma (tiene el aspecto whsec_…) cuando creas el endpoint en el Dashboard de
Tokeflow. Trátalo como una contraseña: guárdalo como un secreto, nunca lo incluyas en el control de versiones y úsalo tal cual —incluyendo
el prefijo whsec_— como clave HMAC.
Pasos de verificación
- Lee el cuerpo crudo de la solicitud como bytes/cadena, antes de cualquier análisis de JSON.
- Lee el encabezado
X-Tokeflow-Signaturey divídelo entyv1. - Calcula
HMAC_SHA256(secret,+ "${t}.${rawBody}" +)y codifícalo en hexadecimal. - Compara tu valor con
v1usando una comparación de tiempo constante. - Rechaza la solicitud si la marca de tiempo
testá fuera de tu tolerancia (por ejemplo, más antigua que 5 minutos) para evitar ataques de repetición.
Captura el cuerpo crudo. La firma se calcula sobre los bytes exactos que envió Tokeflow. Si tu framework analiza el JSON y lo vuelve a serializar, el orden de las claves y los espacios en blanco pueden cambiar y la firma dejará de coincidir. Configura tu ruta para exponer el cuerpo crudo (consulta el ejemplo a continuación).
Ejemplo en 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);
}El esquema de firma es compatible con el ampliamente usado patrón HMAC-SHA256 t=…,v1=…, por lo que las bibliotecas
auxiliares existentes que verifican ese formato funcionarán: solo pasa tu secreto whsec_… como clave y verifica contra
el cuerpo crudo.
Después de verificar
- Responde con un estado
2xxrápidamente; realiza el trabajo pesado de forma asíncrona. - Una respuesta que no sea
2xxo un tiempo de espera agotado hace que Tokeflow reintente la entrega con retroceso. - Elimina duplicados según el
iddel evento y reconcilia el estado mediante la API. Consulta Tipos de eventos de webhook.