# 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`:

```js
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

```bash
node webhook-receptor.mjs
```

## 3. Envíale eventos

En otra terminal, descarga [`fital-webhook-test.sh`](/docs/herramientas/fital-webhook-test.sh) y córrelo contra tu receptor. Firma con `openssl` y no llama a FITAL ni a ningún servicio alojado.

```bash
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`:

```text
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](/docs/webhooks/#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](/docs/webhooks/#dar-de-alta-un-destino).
3. Prueba con **Enviar evento de prueba**, o con [`POST /sandbox/v1/webhooks/trigger`](/docs/operaciones/emitir-evento-de-prueba/) 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](/docs/webhooks/verificar-firma/).
- Dejar el secreto público en producción. Sólo sirve para esta prueba.

## Qué sigue

[Verificar la firma](/docs/webhooks/verificar-firma/) y [Webhooks](/docs/webhooks/).
