> For the complete documentation index, see [llms.txt](https://docs.qentaz.com/documentation/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.qentaz.com/documentation/webhooks/introduccion.md).

# Introducción

Los *webhooks* te permiten recibir notificaciones automáticas cada vez que ocurre un evento importante en Trazo. En lugar de realizar un *polling* a la API para verificar si hay nuevos eventos, los *webhooks* permiten enviar datos directamente al endpoint que definas en tiempo real.

En Trazo, hemos implementado *webhooks* para una variedad de eventos clave que pueden ocurrir durante la operación. Los principales eventos que actualmente soportamos son:

* **Cobros**: Notificaciones cuando se completa un cobro exitoso o cuando hay actualizaciones en el estado de un cobro.
* **Dispersiones**: Alertas cuando se confirma una dispersión, permitiéndote mantener un seguimiento preciso de los movimientos de dinero, o cuando hay actualizaciones en el estado.
* **Suscripciones**: Notificaciones cuando una suscripción se activa, se vence, se cancela o completa su ciclo.
* **Equipo**: Eventos relacionados con los límites de efectivo o novedades del equipo que se encuentra operando en el día.

Todo lo que se explica en esta sección (encabezados, firma, reintentos y el `event_id`) aplica por igual a cualquier tipo de evento.

### Encabezados

<table><thead><tr><th width="212.0390625">Encabezado</th><th>Contenido</th><th>Uso</th></tr></thead><tbody><tr><td><code>Authorization</code></td><td>Bearer <em>{integration_key}</em></td><td>Identifica que la solicitud proviene de Trazo.</td></tr><tr><td><code>X-Trazo-Signature</code></td><td>timestamp=<em>{timestamp}</em>,signature=<em>{firma}</em></td><td>Valida el origen y la integridad del mensaje. Es el encabezado que debes verificar siempre.</td></tr><tr><td><code>X-Trazo-Event-Id</code></td><td>Identificador único del evento</td><td>Detectar reintentos: es el mismo en los 3 intentos de una misma entrega.</td></tr><tr><td><code>X-Trazo-Event-Attempt</code></td><td>1, 2 o 3</td><td>Indica qué intento de la entrega estás recibiendo.</td></tr><tr><td><code>Content-Type</code></td><td>application/json</td><td>Formato del cuerpo de la solicitud.</td></tr><tr><td><em><code>custom_header</code></em></td><td>El que hayas configurado</td><td>Opcional. Solo se envía si se configuró uno para el comercio.</td></tr></tbody></table>

### Seguridad y autenticación

Cada solicitud que enviamos incluye dos mecanismos de validación. Recomendamos verificar ambos en el endpoint antes de procesar el evento.

El primero es el encabezado `Authorization`, que viaja con la `integration_key`. Permite descartar rápidamente cualquier solicitud que no provenga de Trazo.

El segundo es el encabezado `X-Trazo-Signature`, una firma calculada sobre el contenido del mensaje usando el `auth_key` como secreto. A diferencia del `Authorization`, la llave con la que se calcula la firma nunca viaja en la solicitud, por lo que solo el sistema debe conocerla y puede verificarla. Esto permite confirmar que el contenido no fue alterado y que la solicitud no es la repetición de una anterior.

Si necesitas un encabezado adicional propio de su integración, debes solicitar el encabezado personalizado para el comercio, y Trazo lo enviará en cada solicitud junto con los anteriores.

{% hint style="warning" %}
Valida siempre la firma, no solo el `Authorization`. El `integration_key` viaja en cada solicitud, por lo que queda registrado en los *logs*, *proxies* y demás intermediarios por los que pase; la firma es la que realmente prueba el origen y la integridad del mensaje.
{% endhint %}

### Validación de la firma

El encabezado `X-Trazo-Signature` tiene este formato:

{% code overflow="wrap" %}

```
timestamp=1754006400,signature=5257a869e7ecebeda32affa62cdca3fa66d9f7...
```

{% endcode %}

* `timestamp`: momento en que se generó la solicitud, en segundos desde *epoch*.
* `signature`: la firma, en hexadecimal.

Para validarla:

1. Toma el cuerpo de la solicitud **exactamente como lo recibiste, sin parsear ni volver a serializar**. Si tu *framework* lo convierte a objeto y luego lo serializas de nuevo para firmar, el resultado casi nunca es idéntico byte a byte al original (cambia el orden de las claves, los espacios) y la validación falla aunque el mensaje sea legítimo.
2. Concatena el `timestamp` y el cuerpo con un punto: `{timestamp}.{body}`.
3. Calcula el HMAC-SHA256 de esa cadena usando el `auth_key` como secreto.
4. Compara el resultado con `signature`, usando una comparación de tiempo constante (no un `===` directo sobre los strings).
5. Verifica que `timestamp` no tenga más de 5 minutos de antigüedad, para descartar la repetición de una solicitud capturada anteriormente.

```javascript
const crypto = require("crypto");

function validarFirma(rawBody, signatureHeader, authKey) {
  if (!signatureHeader || !rawBody) return false;

  const partes = Object.fromEntries(
    signatureHeader.split(",").map((p) => p.split("="))
  );

  // 1. Validar existencia de campos
  if (!partes.timestamp || !partes.signature) return false;

  // 2. Validar vigencia del timestamp primero (evita calcular el HMAC si ya expiró)
  const vigente = Math.abs(Date.now() / 1000 - Number(partes.timestamp)) <= 300;
  if (!vigente) return false;

  // 3. Calcular el HMAC esperado
  const esperada = crypto
    .createHmac("sha256", authKey)
    .update(`${partes.timestamp}.${rawBody}`)
    .digest("hex");

  // 4. Comparación en tiempo constante
  if (partes.signature.length !== esperada.length) return false;

  return crypto.timingSafeEqual(
    Buffer.from(partes.signature),
    Buffer.from(esperada)
  );
}
```

### Entrega y reintentos

Al enviar un evento esperamos hasta 15 segundos por la respuesta de tu endpoint.

Si tu endpoint responde con un error 5xx, volvemos a intentarlo hasta completar 3 intentos:

<table><thead><tr><th width="117.33203125">Intento</th><th>Cuándo</th></tr></thead><tbody><tr><td>1º</td><td>Inmediato</td></tr><tr><td>2º</td><td>5 segundos después de la respuesta del intento anterior</td></tr><tr><td>3º</td><td>10 segundos después de la respuesta del intento anterior</td></tr></tbody></table>

Por ejemplo, si tu endpoint responde con un error 5xx cada vez:

{% code overflow="wrap" %}

```
Intento 1: 05:30:00  ->  500
Intento 2: 05:30:05  ->  500
Intento 3: 05:30:15  ->  500  ->  evento no entregado
```

{% endcode %}

No reintentamos cuando tu endpoint responde con un error 4xx ni cuando no responde dentro de los 15 segundos: en ambos casos damos el evento por no entregado.

{% hint style="warning" %}
Si tu endpoint necesita más de 15 segundos para procesar el evento, responde primero y procesa después. Una respuesta que llega tarde se descarta y el evento no se reintenta.
{% endhint %}

### Cómo identificar un reintento

Cada evento tiene un identificador único, el `event_id`, que viaja tanto en el cuerpo como en el encabezado `X-Trazo-Event-Id`. Ese identificador es el mismo en los tres intentos, por lo que te permite reconocer que se trata del mismo evento y no de una notificación nueva.

Además, el encabezado `X-Trazo-Event-Attempt` indica el número de intento (`1`, `2` o `3`).

Te recomendamos guardar el `event_id` de cada evento que proceses y descartar los que ya hayas visto. Esto es importante porque un evento puede llegarte repetido incluso cuando tu sistema lo procesó correctamente: si tu endpoint alcanza a registrar el cambio pero responde con un error 5xx (o si el error lo devuelve un intermediario), para Trazo la entrega falló y se intenta.

Ten en cuenta que dos eventos con el mismo identificador de referencia (por ejemplo, `external_reference`) y el mismo `event.status` no son necesariamente el mismo evento: el `event_id` es el único dato que lo determina.
