> ## Documentation Index
> Fetch the complete documentation index at: https://devs.tapipay.la/llms.txt
> Use this file to discover all available pages before exploring further.

# Seguridad de los webhooks

> Los mecanismos con los que puedes comprobar que un webhook viene de TapiPay, y cómo pedir que se configuren.

Tu endpoint de webhooks es público, así que cualquiera puede llamarlo. TapiPay ofrece varios mecanismos para que compruebes que cada petición viene de TapiPay antes de procesarla. Puedes activar uno o combinar varios.

> [!WARNING]
>
> **La seguridad no se configura desde la API.** Debes contactarte directamente con el equipo de TapiPay para pedir una configuración de seguridad. Mientras no lo hagas, tus webhooks llegan sin ningún mecanismo activo.

> [!NOTE]
>
> Esta página aplica a los webhooks de [débito bancario](https://devs.tapipay.la/es/webhooks/debito-fallido.md), de [tarjeta](https://devs.tapipay.la/es/webhooks/pago-con-tarjeta-fallido.md), de [creación](https://devs.tapipay.la/es/webhooks/creacion-de-orden.md) y [pago de órdenes](https://devs.tapipay.la/es/webhooks/pago-de-orden.md), y al de [medio de pago registrado](https://devs.tapipay.la/es/webhooks/medio-de-pago-registrado.md). La [notificación de pago](https://devs.tapipay.la/es/webhooks/notificacion-de-pago.md) la envía otra plataforma y sus mecanismos están en esa misma página.

## Los mecanismos

| Mecanismo | Qué recibe tu endpoint | Qué validas | Qué entregas a TapiPay |
| - | - | - | - |
| [Headers fijos](#headers-fijos) | Uno o más headers con los nombres y valores que acuerdes, por ejemplo `x-api-key`. | Que el header venga con el valor esperado. | El nombre de cada header y su valor. |
| [Bearer token](#bearer-token) | `Authorization: Bearer <token>`. | Que el token sea el tuyo, o que lo haya emitido tu servidor de autorización. | El token fijo, o las credenciales de tu servidor OAuth 2.0. |
| [Hash cifrado](#hash-cifrado) | Un campo `hash` en el cuerpo, cifrado con tu llave pública. | Que al descifrarlo con tu llave privada contenga tu usuario de integración. | Tu llave pública RSA. |
| [Allowlist de IPs](#allowlist-de-ips) | Peticiones que salen de IPs conocidas. | Que la IP de origen esté en tu lista. | Nada: pides la lista de IPs. |

> [!TIP]
>
> Combina al menos dos, por ejemplo headers fijos más allowlist de IPs, o Bearer token más hash cifrado. Un solo mecanismo basado en un header es vulnerable si el valor se filtra.

El header `notificationType` llega siempre, con el mismo valor que el `type` del cuerpo. Sirve para enrutar, no para autenticar.

## Headers fijos

TapiPay agrega a cada petición los headers que acuerdes, con un valor fijo. El caso más común es una API key en `x-api-key`.

* **Qué entregas:** el nombre de cada header y su valor, por el canal seguro que te indique el equipo de TapiPay.
* **Qué validas:** que el header venga y que su valor coincida con el que entregaste. Compara en tiempo constante para no filtrar el valor por diferencias de tiempo.
* **Rotación:** para cambiar el valor, coordina con el equipo de TapiPay la fecha y hora del cambio. Durante la ventana del cambio, acepta el valor anterior y el nuevo.

## Bearer token

TapiPay envía un token en el header `Authorization: Bearer <token>`. Hay dos variantes:

| Variante | Cómo funciona | Qué entregas |
| - | - | - |
| **Token fijo** | TapiPay envía siempre el mismo token. | El token. |
| **Token de tu servidor OAuth 2.0** | TapiPay obtiene el token de tu servidor de autorización con el flujo `client_credentials` y lo renueva periódicamente. | La URL de tu endpoint de tokens, el `client_id` y el `client_secret`. |

En la variante OAuth 2.0, tu endpoint valida el token como cualquier otro que emita tu servidor de autorización: firma, vencimiento y audiencia.

## Hash cifrado

TapiPay incluye en el cuerpo un campo `hash`, cifrado con la llave pública RSA que entregas. Solo tú puedes descifrarlo, con tu llave privada.

| Aspecto | Valor |
| - | - |
| Algoritmo | RSA con padding OAEP (SHA-1). |
| Codificación | Hexadecimal. |
| Contenido, versión por defecto | Tu usuario de integración, como texto. |
| Contenido, versión `v2` | El JSON `{ "clientUsername": "<tu usuario>", "operationId": "<identificador>" }`. |

En `v2`, `operationId` es el identificador de esa notificación: el campo `id` en los webhooks de [órdenes](https://devs.tapipay.la/es/webhooks/pago-de-orden.md) y de [medio de pago registrado](https://devs.tapipay.la/es/webhooks/medio-de-pago-registrado.md), y el campo `operationId` en los de débito bancario y tarjeta. Comprueba que coincidan: así un `hash` copiado de otra notificación no te sirve para validar esta.

> [!WARNING]
>
> **El hash no firma el payload.** Prueba que la petición la armó TapiPay para tu integración, pero no que el resto del cuerpo no se haya alterado en el camino. Combínalo con otro mecanismo.

Si no entregaste una llave pública, el campo `hash` llega igual, con valor `null`. La versión (`v2` o por defecto) se define al configurar tu integración: pide `v2`.

Para generar el par de llaves:

```bash theme={null}
# Llave privada: guárdala en tu servidor, nunca la compartas
openssl genrsa -out tapipay-webhooks-privada.pem 2048

# Llave pública: esta es la que entregas a TapiPay
openssl rsa -in tapipay-webhooks-privada.pem -pubout -out tapipay-webhooks-publica.pem
```

## Allowlist de IPs

TapiPay te puede proporcionar la lista o los rangos de IPs desde los que salen las peticiones de los webhooks, para que tu endpoint acepte solo esas IPs. Pídela al equipo de TapiPay para cada ambiente (homologación y producción).

> [!NOTE]
>
> Pide que te avisen antes de cualquier cambio en esa lista. Una IP nueva que tu allowlist no conoce hace que rechaces webhooks legítimos.

## Ejemplo: validar una petición

El orden correcto en tu handler es: validar el origen, responder 2xx rápido y recién después procesar. Este ejemplo combina un header fijo con el hash cifrado en su versión `v2`.

```javascript theme={null}
import crypto from "node:crypto";
import express from "express";

const app = express();
const PRIVATE_KEY = process.env.TAPI_WEBHOOK_PRIVATE_KEY;
const CLIENT_USERNAME = process.env.TAPI_CLIENT_USERNAME;

function headerValido(valor, esperado) {
  if (!valor) return false;
  const a = Buffer.from(valor);
  const b = Buffer.from(esperado);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

function hashValido(notificacion) {
  if (!notificacion.hash) return false;
  try {
    const contenido = crypto
      .privateDecrypt(
        { key: PRIVATE_KEY, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, oaepHash: "sha1" },
        Buffer.from(notificacion.hash, "hex"),
      )
      .toString("utf8");
    const { clientUsername, operationId } = JSON.parse(contenido);
    // En órdenes y medio de pago registrado es `id`; en débito y tarjeta, `operationId`
    const identificador = notificacion.id ?? notificacion.operationId;
    return clientUsername === CLIENT_USERNAME && operationId === identificador;
  } catch {
    return false;
  }
}

app.post("/tapi/webhooks", express.json(), (req, res) => {
  // 1. Valida el origen antes de procesar
  if (!headerValido(req.header("x-api-key"), process.env.TAPI_WEBHOOK_KEY) || !hashValido(req.body)) {
    return res.sendStatus(401);
  }

  // 2. Responde rápido y procesa después
  res.sendStatus(200);
  encolar(req.body);
});
```

> [!NOTE]
>
> En la versión por defecto del hash, el contenido descifrado es solo tu usuario de integración: compáralo directamente con `CLIENT_USERNAME`, sin `JSON.parse`.

## Reintentos ante un rechazo

Si tu endpoint no responde 2xx, TapiPay reintenta el envío un número limitado de veces en los minutos siguientes y después deja de reintentar. Un rechazo por credenciales (`401` o `403`) también se reintenta, pero vuelve a fallar mientras la configuración no se corrija.

> [!WARNING]
>
> **Prueba la configuración en homologación antes de salir a producción.** Si un mecanismo quedó mal configurado, por ejemplo un header con otro valor, tu endpoint rechaza todos los webhooks y se pierden los avisos de ese período. Si empiezas a rechazar webhooks que esperabas, contacta al equipo de TapiPay.

## Cómo pedir la configuración

**1. Elige los mecanismos**

Decide qué mecanismos quieres combinar y para qué ambientes: homologación, producción o los dos.

**2. Contacta al equipo de TapiPay**

Pide la configuración e indica los mecanismos y los webhooks que recibes. Los webhooks de [órdenes y medios de pago](https://devs.tapipay.la/es/webhooks.md#órdenes-y-medios-de-pago) comparten una misma configuración; pide que la seguridad cubra **todos** los grupos de webhooks que recibes, para que ninguno llegue sin ella.

**3. Entrega los datos por un canal seguro**

Entrega los valores (headers, tokens, credenciales, llave pública) por el canal que te indique el equipo de TapiPay. Nunca envíes un secreto por correo o chat en texto plano. La llave pública no es un secreto.

**4. Prueba en homologación**

Valida que tu endpoint acepta los webhooks de homologación con los mecanismos activos antes de pedir la misma configuración en producción.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.