Inicio rápido de webhooks
Un receptor que verifica firmas, corriendo en tu máquina y sin cuenta.
Al terminar tendrás un receptor en tu máquina que acepta, rechaza y deduplica eventos firmados. No necesitas cuenta: un script firma los eventos con un secreto público de prueba.
Necesitas Node 22, bash, curl y openssl.
1. Guarda el receptor
Guárdalo como webhook-receptor.mjs:
import { createHmac, timingSafeEqual } from 'node:crypto';
import { createServer } from 'node:http';
const SECRETO = process.env.FITAL_WEBHOOK_SECRET ?? 'secreto_de_prueba_publico_no_usar_en_produccion';
const VENTANA_S = 300;
const vistos = new Set(); // en producción, en tu base de datos
function verificar(crudo, ts, firmas) {
if (!/^[0-9]+$/.test(ts)) return false; // sólo dígitos: otra cosa no pasa por la ventana
if (Math.abs(Date.now() / 1000 - Number(ts)) > VENTANA_S) return false;
const esperada = createHmac('sha256', SECRETO).update(`${ts}.`).update(crudo).digest();
return String(firmas).split(',').some((f) => {
const m = /^v1=([0-9a-f]{64})$/.exec(f.trim()); // otras versiones se ignoran
return m !== null && timingSafeEqual(Buffer.from(m[1], 'hex'), esperada);
});
}
createServer((req, res) => {
const partes = [];
req.on('data', (p) => partes.push(p));
req.on('end', () => {
const crudo = Buffer.concat(partes);
if (!verificar(crudo, req.headers['x-fital-timestamp'], req.headers['x-fital-signature'])) {
console.log('rechazado');
res.writeHead(400).end();
return;
}
const evento = JSON.parse(crudo);
if (vistos.has(evento.id)) { // el id del cuerpo va firmado; X-Fital-Event-Id, no
console.log('duplicado', evento.id);
res.writeHead(200).end();
return;
}
vistos.add(evento.id);
console.log('aceptado', evento.id, evento.type, evento.sequence);
res.writeHead(200).end(); // responde ya; procesa en segundo plano
});
}).listen(3000);2. Arráncalo
node webhook-receptor.mjs3. Envíale eventos
En otra terminal, descarga fital-webhook-test.sh y córrelo contra tu receptor. Firma con openssl y no llama a FITAL ni a ningún servicio alojado.
curl -sSO https://merchants.fital.xyz/docs/herramientas/fital-webhook-test.sh
bash fital-webhook-test.sh http://localhost:3000/webhook payment.completed4. Mira el resultado
El script debe imprimir esto y terminar con código 0. Con cualquier FALLA termina con un código distinto de 0:
ok evt_prueba_1 payment.completed -> 200
ok evt_prueba_2 payment.failed -> 200
ok evt_prueba_3 payment.returned -> 200
ok evt_prueba_mala payment.completed -> 400
ok evt_prueba_vieja payment.completed -> 400
ok evt_prueba_futura payment.completed -> 400
ok evt_prueba_alterada payment.completed -> 400
ok evt_prueba_texto payment.completed -> 400
ok evt_prueba_1 payment.completed -> 200
ok evt_prueba_1 payment.completed -> 200
ok evt_prueba_6 payment.completed -> 200
ok evt_prueba_5 payment.processing -> 200Tu receptor imprime aceptado cinco veces, rechazado cinco y duplicado dos. Los rechazos son una firma de otro secreto, un timestamp vencido, uno en el futuro, un cuerpo alterado después de firmar y un timestamp no numérico: el script espera un 4xx en cada uno. Los duplicados repiten evt_prueba_1, el segundo con otro X-Fital-Event-Id: responden 200 sin procesarlo otra vez.
El script sólo ve el código HTTP. Que un duplicado no se procese dos veces y que gane el sequence mayor lo compruebas en el registro de tu receptor.
5. Pásalo a un destino real
- Publica tu receptor en HTTPS, con los requisitos del receptor.
- Crea el destino en el panel y usa su secreto: pon
FITAL_WEBHOOK_SECRETen tu entorno. Ver Dar de alta un destino. - Prueba con Enviar evento de prueba, o con
POST /sandbox/v1/webhooks/triggeren la cuenta de prueba.
En producción, guarda los identificadores de evento en tu base y procesa en segundo plano: este receptor los guarda en memoria y sólo imprime.
Errores comunes
FALLA ... -> 000: tu receptor no está corriendo o el puerto no es3000.FALLA ... se esperaba ok: firmas con otro secreto. SinFITAL_WEBHOOK_SECRET, el receptor y el script usan el mismo secreto público.FALLA ... se esperaba rechazo: tu receptor aceptó una entrega que debía rechazar, o respondió5xx. Revisa Verificar la firma.- Dejar el secreto público en producción. Sólo sirve para esta prueba.