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

# Webhooks

Los webhooks permiten que tu aplicación reciba notificaciones en tiempo real cuando ocurren eventos en la plataforma de Fluz. En lugar de hacer polling para detectar cambios, registras un endpoint HTTPS y Fluz envía datos del evento en el momento en que sucede algo: una transacción con tarjeta virtual, una compra rechazada, un depósito completado y más.

<Note>
  Esta página está pensada como una sección de Webhooks de nivel superior, ya que los eventos de webhook abarcan múltiples áreas de la plataforma (flujos del widget y actividad de transacciones) en lugar de pertenecer a una sola función.
</Note>

Los webhooks funcionan en todos los tipos de aplicación de la plataforma de Fluz: apps privadas que operan en tu propia cuenta, apps públicas OAuth que actúan en nombre de otros usuarios vía API y apps con widgets embebidos.

## Cómo funciona

1. Registras una URL de webhook en tu aplicación en el Portal de Desarrolladores.
2. Seleccionas qué tipos de eventos escuchar (o te suscribes a todos).
3. Cuando ocurre un evento coincidente, Fluz envía un `POST` HTTP a tu URL con un payload JSON firmado.
4. Tu servidor verifica la firma, responde con un `2xx` y procesa el evento.

```mermaid theme={null}
sequenceDiagram
    participant Platform as Fluz Platform
    participant Dispatcher as Webhook Dispatcher
    participant You as Your Endpoint

    Platform->>Dispatcher: Event occurs
    Dispatcher->>Dispatcher: Match subscriptions and verify scopes
    Dispatcher->>Dispatcher: Sign payload with HMAC
    Dispatcher->>You: HTTP POST with JSON payload
    You-->>Dispatcher: 200 OK
```

Si tu endpoint no está disponible o devuelve un error, Fluz reintenta hasta **5 veces** con backoff exponencial antes de marcar el evento como `FAILED`.

## Webhooks por tipo de app

Cómo se enrutan los eventos a tu app depende de tu modelo de aplicación.

### Apps privadas — tu propia cuenta

Los webhooks se disparan por actividad en **tu propia cuenta**. Cualquier transacción, rechazo o depósito en cuentas que posees dispara webhooks registrados en tu app.

**Casos de uso comunes:** notificaciones cuando se autoriza o liquida una transacción con tarjeta virtual; alertas en tiempo real sobre rechazos; monitoreo de depósitos y cambios de balance en tus cuentas de gasto.

### Apps públicas (OAuth) — actuando en nombre de usuarios

Los webhooks se disparan por actividad en **cuentas que han autorizado tu app**. Cuando un usuario otorga acceso a tu app vía OAuth, sus eventos se enrutan a tus webhooks registrados, siempre que la concesión OAuth del usuario incluya los scopes requeridos. Esto funciona tanto si el usuario interactúa a través de tu integración directa por API como mediante un widget embebido; el requisito clave es una relación OAuth entre el usuario y tu app.

**Casos de uso comunes:** monitorear gasto con tarjetas virtuales en cuentas conectadas; notificaciones de rechazos en tiempo real; seguimiento de depósitos completados; recibir actualizaciones de estado de KYC.

### Apps de widget

Las apps de widget son una app pública especializada. Los eventos se enrutan de la misma manera (en base a la relación OAuth), y además las apps de widget admiten eventos específicos del widget como transferencias completadas y compras de tarjetas de regalo.

## Tipos de eventos

Fluz solo entrega un evento si tu aplicación — **y**, para apps públicas/OAuth, la concesión OAuth del usuario individual — posee los scopes requeridos.

### Eventos de transacción

Cubren el ciclo de vida completo de la transacción. Aplican a **todos los tipos de app**.

| Evento                | Descripción                                                                                  | Scopes requeridos                |
| --------------------- | -------------------------------------------------------------------------------------------- | -------------------------------- |
| `TRANSACTION_CREATE`  | Se creó una nueva transacción (p. ej., compra con tarjeta virtual, depósito, transferencia). | `LIST_PAYMENT`, `LIST_PURCHASES` |
| `TRANSACTION_UPDATE`  | Cambió el estado o el monto de una transacción (p. ej., liquidada, ajustada).                | `LIST_PAYMENT`, `LIST_PURCHASES` |
| `TRANSACTION_DECLINE` | Se rechazó una transacción (p. ej., fondos insuficientes, controles de tarjeta).             | `LIST_PAYMENT`, `LIST_PURCHASES` |

### Eventos de depósito

| Evento             | Descripción                                                               | Scopes requeridos |
| ------------------ | ------------------------------------------------------------------------- | ----------------- |
| `DEPOSIT_COMPLETE` | Se completó un depósito desde una fuente de fondos a una cuenta de gasto. | `MAKE_DEPOSIT`    |

### Eventos específicos de widget

Aplican a integraciones de widget y OAuth donde los usuarios interactúan mediante flujos embebidos de Fluz.

| Evento                      | Descripción                                               | Scopes requeridos              |
| --------------------------- | --------------------------------------------------------- | ------------------------------ |
| `WIDGET_KYC_INITIATION`     | Un usuario inició la verificación de identidad (KYC).     | `VERIFY_KYC`                   |
| `WIDGET_DEPOSIT_COMPLETE`   | Se completó una transferencia de un cliente hacia tu app. | `MAKE_PAYOUT_TRANSFER_SEND`    |
| `WIDGET_WITHDRAW_COMPLETE`  | Se completó una transferencia de tu app hacia un cliente. | `MAKE_PAYOUT_TRANSFER_RECEIVE` |
| `WIDGET_PURCHASE_GIFT_CARD` | Se completó una compra de tarjeta de regalo.              | `PURCHASE_GIFTCARD`            |

> 📘 Nota sobre la nomenclatura: `WIDGET_DEPOSIT_COMPLETE` y `WIDGET_WITHDRAW_COMPLETE` describen *transferencias* cliente↔app; la redacción `DEPOSIT`/`WITHDRAW` es histórica. Trata "deposit" como "cliente → app" y "withdraw" como "app → cliente".

## Configuración de webhooks

### 1. Abre el Portal de Desarrolladores

Ve al [Developer Portal](https://app.fluz.app/for-developers) y selecciona tu aplicación.

### 2. Abre la sección de Webhooks

* **Apps OAuth:** pestaña **OAuth** → **Webhook URLs**.
* **Apps de widget:** pestaña **Widget** → **Webhook URLs**.
* **Apps API / privadas:** la sección **Webhook URLs** en la configuración de tu app.

### 3. Agrega una URL de webhook

Haz clic en **Add new URL** e ingresa tu endpoint HTTPS (p. ej., `https://api.yourapp.com/webhooks/fluz`).

### 4. Selecciona eventos

Elige los tipos de eventos que deseas recibir. **Deja la selección vacía para actuar como catch-all** y recibir todos los eventos para los que tu app tenga scopes.

### 5. Guardar

Haz clic en **Create Webhook**. Tu endpoint comenzará a recibir eventos de inmediato.

## Gestión de webhooks

* **Múltiples endpoints** — puedes registrar más de una URL de webhook por aplicación.
* **Cambiar eventos suscritos** — elimina el webhook y vuelve a crearlo con la nueva selección de eventos.
* **Eliminar un webhook** — haz clic en **Remove** junto a él. El webhook se archiva de inmediato y deja de recibir eventos.

## Recepción de webhooks

### Formato de la solicitud

Cada webhook se entrega como un `POST` HTTP con estos headers:

| Header             | Descripción                                                                   |
| ------------------ | ----------------------------------------------------------------------------- |
| `Content-Type`     | Siempre `application/json`.                                                   |
| `X-HMAC-Signature` | Firma HMAC-SHA256 del cuerpo JSON en crudo, firmada con la API key de tu app. |
| `X-Event-ID`       | UUID único para este evento: úsalo para desduplicación.                       |

El cuerpo es un objeto JSON y cada payload incluye un campo `eventType` que identifica el evento.

### Requisitos del endpoint

* **Solo HTTPS** — los endpoints HTTP simples se rechazan al momento del registro.
* **Accesible públicamente** y capaz de aceptar solicitudes `POST`.
* **Responder con un `2xx` dentro de 30 segundos.** Respuestas no-`2xx` o timeouts generan reintentos.
* **Verificar la firma HMAC** en cada solicitud.

## Verificación de firmas

Cada entrega incluye un header `X-HMAC-Signature`: un hash HMAC-SHA256 del JSON **crudo**, firmado con la **API key** de tu aplicación. Verifícalo siempre antes de confiar en un payload.

> ⚠️ Verifica contra el cuerpo crudo de la solicitud. Calcula el HMAC sobre los bytes exactos que envió Fluz — **no** reserialices el JSON parseado. Reconvertir a string puede reordenar claves o cambiar espacios en blanco y causar que fallen firmas válidas. Los ejemplos a continuación capturan el cuerpo crudo por esta razón.

### Node.js (Express)

```javascript theme={null}
const express = require('express');
const crypto = require('crypto');
const app = express();

// Capture the raw body so the HMAC is computed over the exact bytes Fluz sent.
app.use('/webhooks/fluz', express.raw({ type: 'application/json' }));

function verifyFluzWebhook(rawBody, signature, apiKey) {
  const expected = crypto.createHmac('sha256', apiKey).update(rawBody).digest('hex');
  const received = Buffer.from(signature || '', 'hex');
  const computed = Buffer.from(expected, 'hex');
  return received.length === computed.length &&
         crypto.timingSafeEqual(received, computed);
}

app.post('/webhooks/fluz', (req, res) => {
  const signature = req.headers['x-hmac-signature'];
  const eventId = req.headers['x-event-id'];

  // req.body is a Buffer because of express.raw above
  if (!verifyFluzWebhook(req.body, signature, process.env.FLUZ_API_KEY)) {
    return res.status(401).send('Invalid signature');
  }

  // Acknowledge immediately, then process asynchronously
  res.status(200).send('OK');

  const event = JSON.parse(req.body.toString('utf8'));
  handleEvent(eventId, event).catch(err =>
    console.error('Webhook processing error:', err)
  );
});
```

### Python (Flask)

```python theme={null}
import hmac, hashlib
from flask import Flask, request

app = Flask(__name__)

def verify_fluz_webhook(raw_body: bytes, signature: str, api_key: str) -> bool:
    expected = hmac.new(api_key.encode("utf-8"), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature or "")

@app.post("/webhooks/fluz")
def fluz_webhook():
    signature = request.headers.get("X-HMAC-Signature")
    event_id = request.headers.get("X-Event-ID")

    # request.get_data() returns the raw bytes — do NOT use request.json for verification
    if not verify_fluz_webhook(request.get_data(), signature, FLUZ_API_KEY):
        return "Invalid signature", 401

    enqueue(event_id, request.get_json())  # process asynchronously
    return "OK", 200
```

## Responder a webhooks

Tu endpoint **debe**:

* Responder con un estado `2xx` dentro de 30 segundos.
* Responder rápido: reconoce primero, luego procesa de forma asíncrona.
* Ser accesible por HTTPS.

Tu endpoint **no debe**:

* Responder con redirecciones (`3xx`).
* Responder con `4xx`/`5xx` para webhooks válidos (esto dispara reintentos).

## Política de reintentos

| Comportamiento                             | Detalle                                                             |
| ------------------------------------------ | ------------------------------------------------------------------- |
| Intentos máximos                           | 5                                                                   |
| Programación                               | Backoff exponencial                                                 |
| Disparador                                 | Cualquier respuesta no-`2xx` o timeout                              |
| Timeout                                    | 30 segundos por intento                                             |
| Después de que todos los reintentos fallen | El evento se marca como `FAILED`; no habrá más intentos de entrega. |
| Registro                                   | Cada intento se registra individualmente para auditoría.            |

La entrega de nuevos eventos se reanuda automáticamente una vez que tu endpoint se recupere. Para reenviar eventos que ya llegaron a `FAILED`, contacta al soporte con el `X-Event-ID` correspondiente.

### Ciclo de vida del estado del evento

| Estado           | Significado                                      |
| ---------------- | ------------------------------------------------ |
| `CREATED`        | Evento registrado y encolado.                    |
| `IN_PROGRESS`    | Haciendo match con suscripciones de webhooks.    |
| `NO_SUBSCRIBERS` | Ningún webhook activo coincidió con este evento. |
| `SUCCESS`        | Entregado exitosamente.                          |
| `FAILED`         | Se agotaron todos los intentos de reintento.     |

## Idempotencia y orden

Los webhooks pueden entregarse **más de una vez**, y **no se garantiza el orden de entrega**.

* **Desduplica** usando el header `X-Event-ID`. Persiste los IDs procesados (Redis o una base de datos en producción) y omite eventos que ya manejaste.
* **Ordena por datos, no por llegada.** Si la secuencia importa, ordena por timestamps del payload (`createdAt`, `updatedAt`, `transactionDateTime`) y por IDs de evento.

```javascript theme={null}
const seen = new Set(); // use Redis or a database in production

async function handleEvent(eventId, event) {
  if (seen.has(eventId)) return;  // already processed — skip
  seen.add(eventId);

  switch (event.eventType) {
    case 'TRANSACTION_CREATE':    await onTransactionCreated(event); break;
    case 'TRANSACTION_UPDATE':    await onTransactionUpdated(event); break;
    case 'TRANSACTION_DECLINE':   await onTransactionDeclined(event); break;
    case 'DEPOSIT_COMPLETE':      await onDepositComplete(event); break;
    case 'WIDGET_KYC_INITIATION': await onKycInitiated(event); break;
    // ...handle remaining widget events
  }
}
```

## Identificación de la app y el usuario

* **Usuario:** `userId` es el ID de usuario de Fluz. Para eventos de OAuth/widget, `externalReferenceId` se asigna a tu identificador de usuario del flujo OAuth.
* **App:** los payloads de transacción incluyen `connectedAppId` y `connectedAppName`. Si enrutas múltiples apps a un endpoint, ramifica según `connectedAppId`.

## Referencia de payloads

Cada payload incluye un `eventType`. La disponibilidad de campos puede variar según el evento; los handlers deben ignorar campos no reconocidos para compatibilidad futura.

### Transacción creada (`TRANSACTION_CREATE`)

Se dispara para cualquier transacción nueva: compras con tarjeta virtual, depósitos, transferencias y más.

```json theme={null}
{
  "eventType": "TRANSACTION_CREATE",
  "recordId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "transactionId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "accountId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
  "userId": "550e8400-e29b-41d4-a716-446655440000",
  "transactionType": "PURCHASE",
  "amount": 42.99,
  "destination": "Coffee Shop",
  "source": "Virtual Card",
  "status": "COMPLETED",
  "channel": "VIRTUAL_CARD",
  "connectedAppId": "your-app-id",
  "connectedAppName": "Your App",
  "createdAt": "2025-01-15T10:30:00.000Z",
  "merchantName": "Coffee Shop",
  "merchantCity": "New York",
  "merchantState": "NY",
  "merchantCountry": "US",
  "cardLastFour": "1234",
  "virtualCardId": "vc-uuid-here",
  "cashbackRate": 0.05
}
```

| Campo                                                                 | Tipo              | Descripción                                                                                         |
| --------------------------------------------------------------------- | ----------------- | --------------------------------------------------------------------------------------------------- |
| `eventType`                                                           | String            | Siempre `TRANSACTION_CREATE`.                                                                       |
| `recordId`                                                            | String (UUID)     | ID único del registro del evento de webhook.                                                        |
| `transactionId`                                                       | String (UUID)     | El ID de la transacción.                                                                            |
| `accountId`                                                           | String (UUID)     | El ID de la cuenta de Fluz.                                                                         |
| `userId`                                                              | String (UUID)     | El ID del usuario de Fluz.                                                                          |
| `transactionType`                                                     | String (enum)     | p. ej., `PURCHASE`. *(Confirmar el conjunto completo.)*                                             |
| `amount`                                                              | Number            | Monto de la transacción en USD.                                                                     |
| `destination`                                                         | String            | Dónde fueron los fondos (p. ej., nombre del comercio).                                              |
| `source`                                                              | String            | Fuente de fondos (p. ej., `Virtual Card`).                                                          |
| `status`                                                              | String (enum)     | p. ej., `COMPLETED`. *(Confirmar el conjunto completo — p. ej., `PENDING`, `SETTLED`, `DECLINED`.)* |
| `channel`                                                             | String (enum)     | p. ej., `VIRTUAL_CARD`. *(Confirmar el conjunto completo.)*                                         |
| `connectedAppId`                                                      | String            | La app a la que se enruta el evento.                                                                |
| `connectedAppName`                                                    | String            | Nombre para mostrar de la app conectada.                                                            |
| `createdAt`                                                           | String (ISO 8601) | Cuándo se creó la transacción.                                                                      |
| `merchantName` / `merchantCity` / `merchantState` / `merchantCountry` | String            | Detalles de ubicación del comercio.                                                                 |
| `cardLastFour`                                                        | String            | Últimos cuatro dígitos de la tarjeta usada.                                                         |
| `virtualCardId`                                                       | String (UUID)     | El ID de la tarjeta virtual, si aplica.                                                             |
| `cashbackRate`                                                        | Number            | Tasa de cashback decimal (p. ej., `0.05` = 5%).                                                     |

### Transacción actualizada (`TRANSACTION_UPDATE`)

Se dispara cuando cambian el estado o los detalles de una transacción, por ejemplo, cuando una autorización pendiente se liquida.

```json theme={null}
{
  "eventType": "TRANSACTION_UPDATE",
  "recordId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "transactionId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "accountId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
  "userId": "550e8400-e29b-41d4-a716-446655440000",
  "transactionType": "PURCHASE",
  "amount": 42.99,
  "status": "SETTLED",
  "createdAt": "2025-01-15T10:30:00.000Z",
  "updatedAt": "2025-01-16T08:00:00.000Z"
}
```

Los campos coinciden con `TRANSACTION_CREATE` cuando están presentes, además de `updatedAt` (ISO 8601) indicando cuándo ocurrió el cambio.

> 🚧 Verificación de consistencia: confirmar que `userId` y `connectedAppId`/`connectedAppName` estén presentes en `TRANSACTION_UPDATE` (y `TRANSACTION_DECLINE`) para que los consumidores puedan identificar al usuario y la app de forma consistente a lo largo del ciclo de vida.

### Transacción rechazada (`TRANSACTION_DECLINE`)

Se dispara cuando una transacción es rechazada. Incluye motivos de rechazo estructurados con los que tu app puede actuar.

```json theme={null}
{
  "eventType": "TRANSACTION_DECLINE",
  "transactionId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "transactionType": "PURCHASE",
  "amount": 500.00,
  "fluzAmount": 500.00,
  "externalFundingAmount": 0,
  "currency": "USD",
  "accountId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
  "userId": "550e8400-e29b-41d4-a716-446655440000",
  "status": "DECLINED",
  "merchantName": "Electronics Store",
  "cardLastFour": "1234",
  "virtualCardId": "vc-uuid-here",
  "isCashBalanceUsed": true,
  "isPrepaymentBalanceUsed": false,
  "isRewardsBalanceUsed": false,
  "isReserveBalanceUsed": false,
  "transactionDateTime": "2025-01-15T10:30:00.000Z",
  "declineTitle": "Transaction Declined",
  "declineReason": "Insufficient funds",
  "declineDescription": "Your account balance was not sufficient for this transaction.",
  "declineCategory": "BALANCE"
}
```

| Campo                                                                                             | Tipo              | Descripción                                                                                                |
| ------------------------------------------------------------------------------------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------- |
| `amount`                                                                                          | Number            | Monto total intentado.                                                                                     |
| `fluzAmount`                                                                                      | Number            | Porción tomada del balance de Fluz.                                                                        |
| `externalFundingAmount`                                                                           | Number            | Porción tomada de financiamiento externo.                                                                  |
| `currency`                                                                                        | String            | Código de moneda ISO (p. ej., `USD`).                                                                      |
| `status`                                                                                          | String            | `DECLINED`.                                                                                                |
| `isCashBalanceUsed` / `isPrepaymentBalanceUsed` / `isRewardsBalanceUsed` / `isReserveBalanceUsed` | Boolean           | De qué tipo(s) de balance se intentó debitar.                                                              |
| `transactionDateTime`                                                                             | String (ISO 8601) | Cuándo ocurrió el rechazo.                                                                                 |
| `declineTitle`                                                                                    | String            | Título corto orientado al usuario.                                                                         |
| `declineReason`                                                                                   | String            | Motivo breve (p. ej., `Insufficient funds`).                                                               |
| `declineDescription`                                                                              | String            | Explicación más larga orientada al usuario.                                                                |
| `declineCategory`                                                                                 | String (enum)     | p. ej., `BALANCE`. *(Confirmar el conjunto completo — ver [Códigos de rechazo](/features/decline-codes).)* |

### Depósito completado (`DEPOSIT_COMPLETE`)

Se dispara cuando se completa un depósito desde una fuente de fondos hacia una cuenta de gasto.

```json theme={null}
{
  "eventType": "DEPOSIT_COMPLETE",
  "userId": "550e8400-e29b-41d4-a716-446655440000",
  "accountId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
  "externalReferenceId": "your-reference-123",
  "amount": 50.00
}
```

### Inicio de KYC (`WIDGET_KYC_INITIATION`)

Se dispara cuando un usuario inicia la verificación de identidad. Solo apps públicas/widget.

```json theme={null}
{
  "eventType": "WIDGET_KYC_INITIATION",
  "userId": "550e8400-e29b-41d4-a716-446655440000",
  "accountId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
  "externalReferenceId": "your-reference-123"
}
```

### Transferencia completada — cliente a app (`WIDGET_DEPOSIT_COMPLETE`)

Se dispara cuando un usuario transfiere fondos a tu aplicación.

```json theme={null}
{
  "eventType": "WIDGET_DEPOSIT_COMPLETE",
  "userId": "550e8400-e29b-41d4-a716-446655440000",
  "accountId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
  "externalReferenceId": "your-reference-123",
  "amount": 25.50
}
```

### Transferencia completada — app a cliente (`WIDGET_WITHDRAW_COMPLETE`)

Se dispara cuando tu aplicación transfiere fondos a un usuario.

```json theme={null}
{
  "eventType": "WIDGET_WITHDRAW_COMPLETE",
  "userId": "550e8400-e29b-41d4-a716-446655440000",
  "accountId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
  "externalReferenceId": "your-reference-123",
  "amount": 15.00
}
```

### Compra de tarjeta de regalo (`WIDGET_PURCHASE_GIFT_CARD`)

Se dispara cuando se completa una compra de tarjeta de regalo a través del widget.

> 🚧 Por completar. Agregar el ejemplo de payload y la tabla de campos de `WIDGET_PURCHASE_GIFT_CARD` (p. ej., comercio, monto, identificador de la tarjeta de regalo).

**Campos comunes de payload en widget:** `userId` (ID de usuario de Fluz), `accountId` (ID de cuenta de Fluz), `externalReferenceId` (tu identificador de usuario del flujo OAuth) y `amount` cuando corresponda.

## Mejores prácticas

* **Responde rápido, procesa después.** Devuelve `200` inmediatamente y maneja el evento de forma asíncrona para evitar timeouts y reintentos innecesarios.
* **Verifica cada solicitud.** Valida `X-HMAC-Signature` contra el cuerpo crudo usando tu API key antes de procesar.
* **Desduplica con IDs de evento.** Rastrea `X-Event-ID` para manejar entregas reintentadas/duplicadas.
* **Acepta campos desconocidos.** Los payloads pueden ganar campos con el tiempo; ignora los no reconocidos en lugar de fallar.
* **Monitorea tu endpoint.** Alerta ante respuestas repetidas no-`2xx`: después de 5 fallos los eventos se marcan `FAILED`.

## Solución de problemas

| Síntoma                                     | Causa probable                                                   | Solución                                                                                                               |
| ------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| No recibes webhooks                         | Endpoint inaccesible o devolviendo errores                       | Confirma que la URL sea correcta, accesible públicamente por HTTPS y que devuelva `2xx`.                               |
| Faltan ciertos tipos de eventos             | A tu app le faltan los scopes requeridos                         | Asegúrate de que tu app tenga los scopes listados para ese evento.                                                     |
| App pública no recibe eventos de un usuario | El usuario no ha autorizado los scopes requeridos                | Confirma que la concesión OAuth del usuario incluya los scopes necesarios.                                             |
| Falla la verificación de firma              | API key incorrecta, o verificación sobre un cuerpo reserializado | Usa la API key de tu app y verifica contra el cuerpo crudo de la solicitud.                                            |
| Entregas duplicadas                         | Reintento tras un timeout                                        | Implementa idempotencia usando `X-Event-ID`.                                                                           |
| Dejaron de llegar eventos                   | 5 fallos consecutivos agotaron los reintentos                    | Repara tu endpoint; los nuevos eventos se reanudan automáticamente. Contacta soporte para reintentar eventos `FAILED`. |

## ¿Necesitas ayuda?

* **Problemas técnicos:** revisa los logs de tu endpoint y contacta soporte con el `X-Event-ID`.
* **Preguntas sobre scopes:** consulta [Application Scopes](/fluz-dashboard/application-scopes) y [Códigos de rechazo](/features/decline-codes).
