FITALAPI

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.mjs

3. 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.completed

4. 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 -> 200

Tu 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

  1. Publica tu receptor en HTTPS, con los requisitos del receptor.
  2. Crea el destino en el panel y usa su secreto: pon FITAL_WEBHOOK_SECRET en tu entorno. Ver Dar de alta un destino.
  3. Prueba con Enviar evento de prueba, o con POST /sandbox/v1/webhooks/trigger en 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 es 3000.
  • FALLA ... se esperaba ok: firmas con otro secreto. Sin FITAL_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.

Qué sigue

Verificar la firma y Webhooks.

Ver esta página en Markdown

En esta página