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

# Webhooks

> Recibe notificaciones en tiempo real de eventos de facturación: validación, rechazo, contingencia y estado de conexión con el SIAT. Firma HMAC-SHA256 con el estándar Standard Webhooks.

## Webhooks

En lugar de hacer polling a `GET /api/v1/invoices/{id}/status`, registra una URL y CUCU te notifica en tiempo real cada vez que ocurre un evento relevante: una factura se valida o rechaza, se abre o cierra una contingencia, o cambia el estado de la conexión con el SIAT.

***

## Registrar un endpoint

`POST /api/v1/webhooks/endpoints`

| Parametro     | Ubicacion | Tipo           | Req | Descripcion                                                                           |
| ------------- | --------- | -------------- | --- | ------------------------------------------------------------------------------------- |
| `X-API-Key`   | Header    | string         | Si  | Tu API Key                                                                            |
| `url`         | Body      | string         | Si  | URL `https://` que recibirá las entregas                                              |
| `events`      | Body      | array\<string> | Si  | Eventos a suscribir. Acepta `*`, `namespace.*` (ej. `contingency.*`) o el tipo exacto |
| `description` | Body      | string         | No  | Nota interna para identificar el endpoint                                             |

<CodeGroup>
  ```bash cURL theme={"system"}
  curl -X POST https://sandbox.cucu.bo/api/v1/webhooks/endpoints \
    -H "Content-Type: application/json" \
    -H "X-API-Key: YOUR_API_KEY" \
    -d '{
      "url": "https://tuservidor.com/webhooks/cucu",
      "events": ["invoice.validated", "invoice.rejected", "contingency.*"],
      "description": "Sincronizacion con ERP"
    }'
  ```

  ```javascript JavaScript theme={"system"}
  const response = await fetch('https://sandbox.cucu.bo/api/v1/webhooks/endpoints', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': process.env.CUCU_API_KEY
    },
    body: JSON.stringify({
      url: 'https://tuservidor.com/webhooks/cucu',
      events: ['invoice.validated', 'invoice.rejected', 'contingency.*'],
      description: 'Sincronizacion con ERP'
    })
  });
  ```

  ```python Python theme={"system"}
  import requests

  response = requests.post(
      'https://sandbox.cucu.bo/api/v1/webhooks/endpoints',
      headers={'X-API-Key': 'YOUR_API_KEY'},
      json={
          'url': 'https://tuservidor.com/webhooks/cucu',
          'events': ['invoice.validated', 'invoice.rejected', 'contingency.*'],
          'description': 'Sincronizacion con ERP'
      }
  )
  ```
</CodeGroup>

**Respuesta (201):**

```json theme={"system"}
{
  "success": true,
  "data": {
    "id": "wh_ep_a1b2c3d4",
    "url": "https://tuservidor.com/webhooks/cucu",
    "events": ["invoice.validated", "invoice.rejected", "contingency.*"],
    "secret": "whsec_...",
    "active": true,
    "createdAt": "2026-07-01T10:00:00"
  }
}
```

<Warning>
  El `secret` solo se muestra completo en este momento (y al rotarlo). Guárdalo de forma segura — lo necesitas para verificar la firma de cada entrega.
</Warning>

***

## Gestionar endpoints

| Accion                           | Endpoint                                             |
| -------------------------------- | ---------------------------------------------------- |
| Listar endpoints                 | `GET /api/v1/webhooks/endpoints`                     |
| Ver un endpoint                  | `GET /api/v1/webhooks/endpoints/{id}`                |
| Actualizar (url, events, activo) | `PUT /api/v1/webhooks/endpoints/{id}`                |
| Eliminar                         | `DELETE /api/v1/webhooks/endpoints/{id}`             |
| Rotar secreto                    | `POST /api/v1/webhooks/endpoints/{id}/rotate-secret` |

Todas requieren el header `X-API-Key`.

<Note>
  Cada tenant puede registrar un máximo de **20 endpoints**. Si necesitas más, contacta a soporte.
</Note>

***

## Catálogo de eventos

| Evento                         | Descripción                                                                                                                                                                                                        |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `invoice.*`                    | Un evento por cada cambio de estado de una factura (validada, rechazada, anulada), reflejando los mismos estados documentados en el [ciclo de vida de la factura](/api/informacion-general#estados-de-una-factura) |
| `contingency.opened`           | Se activó contingencia offline en un punto de venta                                                                                                                                                                |
| `contingency.closed`           | La contingencia finalizó y las facturas se sincronizaron con el SIAT                                                                                                                                               |
| `contingency.package_rejected` | El SIAT rechazó el paquete de facturas enviado al reconectar                                                                                                                                                       |
| `siat.connection_lost`         | CUCU detectó que el SIAT dejó de responder                                                                                                                                                                         |
| `siat.connection_restored`     | La conexión con el SIAT se restableció                                                                                                                                                                             |
| `siat.token_expiring`          | El token de sesión SIAT está por expirar                                                                                                                                                                           |
| `certificate.expiring`         | El certificado digital del tenant está por vencer                                                                                                                                                                  |
| `cufd.renewal_failed`          | Falló la renovación automática del CUFD                                                                                                                                                                            |
| `cuis.renewal_failed`          | Falló la renovación automática del CUIS                                                                                                                                                                            |

Al registrar un endpoint puedes suscribirte a `*` (todos los eventos), a un namespace completo (`contingency.*`, `siat.*`) o a un tipo exacto. La suscripción se valida contra este mismo catálogo, así que nunca puedes suscribirte a un evento inexistente.

***

## Seguridad

* **HTTPS obligatorio.** Solo se aceptan URLs `https://`.
* **Anti-SSRF.** Al registrar o rotar un endpoint, CUCU resuelve el host y rechaza direcciones en rangos privados o de loopback.
* **Firma en cada entrega.** Todas las entregas se firman con HMAC-SHA256 siguiendo el estándar [Standard Webhooks](https://www.standardwebhooks.com/), para que la verificación sea sencilla y predecible en cualquier lenguaje.

### Headers en cada entrega

| Header              | Descripción                                                       |
| ------------------- | ----------------------------------------------------------------- |
| `webhook-id`        | UUID único de esta entrega. Úsalo para deduplicar en tu receptor. |
| `webhook-timestamp` | UNIX timestamp del momento del envío.                             |
| `webhook-signature` | Firma de autenticidad. Formato: `v1,<base64(HMAC-SHA256)>`.       |
| `webhook-event`     | Tipo de evento entregado (ej. `invoice.validated`).               |

### Verificación de firma

```
mensaje_a_firmar = "{webhook-id}.{webhook-timestamp}.{raw_body_bytes}"
firma_esperada   = base64( HMAC-SHA256(mensaje_a_firmar, tu_secret) )
```

Compara `firma_esperada` con el valor de `webhook-signature` (después del prefijo `v1,`) usando una comparación de tiempo constante.

***

## Payload de ejemplo

```json theme={"system"}
{
  "type": "invoice.validated",
  "occurredAt": "2026-07-01T10:00:05",
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "cuf": "2872F7294502332E637FABFBC3654EA82202AD48E0969F14EBEB8AF74",
    "invoiceNumber": 42,
    "state": "VALIDATED",
    "amountTotal": 500.00
  }
}
```

***

## Política de reintentos

Tu endpoint debe responder `2xx` dentro del timeout configurado para que la entrega se considere exitosa. Si no responde o responde con error, CUCU reintenta con **backoff exponencial hasta 8 intentos**. Un dispatcher interno procesa la cola de entregas pendientes cada 10 segundos.

<Tip>
  Diseña tu receptor para ser idempotente usando `webhook-id` — el mismo evento puede llegar más de una vez ante un reintento de red.
</Tip>

***

## Auditoría y reenvío manual

| Accion                           | Endpoint                                                  |
| -------------------------------- | --------------------------------------------------------- |
| Listar entregas de un endpoint   | `GET /api/v1/webhooks/endpoints/{id}/deliveries`          |
| Forzar el reenvío de una entrega | `POST /api/v1/webhooks/deliveries/{deliveryId}/redeliver` |

Útil para debugging: revisa el historial de intentos, el código de respuesta recibido y fuerza un reenvío manual sin esperar al backoff automático.
