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

> Te avisamos cuando te pagan. Firma Standard Webhooks, 6 reintentos.

## Webhooks

Registrás una URL y te avisamos cada vez que te pagan un QR. No hace falta consultar el estado en un loop.

***

## Registrar

```bash theme={"system"}
curl -X POST https://qrsimple.cucu.bo/api/v1/webhook \
  -H "X-Api-Key: <tu_api_key>" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://tu-sistema.com/webhooks/cucu"}'
```

```json theme={"system"}
{
  "url": "https://tu-sistema.com/webhooks/cucu",
  "secret": "whsec_...",
  "events": ["qr.paid"],
  "createdAt": "2026-09-22T20:15:00Z"
}
```

El `secret` sale **una sola vez**. Guardalo en tu gestor de secretos: después solo se puede rotar.

La URL tiene que ser `https` y resolver a una dirección pública. Se valida al registrar y antes de cada entrega.

| Método   | Ruta                     | Qué hace                                                               |
| -------- | ------------------------ | ---------------------------------------------------------------------- |
| `POST`   | `/api/v1/webhook`        | Registra y devuelve el secreto                                         |
| `GET`    | `/api/v1/webhook`        | Tu webhook, con el secreto enmascarado y su estado                     |
| `POST`   | `/api/v1/webhook/rotate` | Secreto nuevo; el anterior sigue firmando durante la ventana de gracia |
| `POST`   | `/api/v1/webhook/test`   | Encola un evento de prueba (5 por minuto)                              |
| `DELETE` | `/api/v1/webhook`        | Baja                                                                   |

***

## El evento

```json theme={"system"}
{
  "event": "qr.paid",
  "occurredAt": "2026-09-22T20:42:48Z",
  "data": {
    "qrId": "36146199",
    "amount": "75.00",
    "currency": "BOB",
    "status": "PAID",
    "externalReference": "ORD-0871",
    "paidAt": "2026-09-22T20:42:48Z"
  }
}
```

`amount` viaja como string: es plata y un float pierde centavos. `externalReference` es el que mandaste al crear el QR.

***

## Headers

| Header              | Qué trae                                                          |
| ------------------- | ----------------------------------------------------------------- |
| `webhook-id`        | Id de la entrega. Usalo para no procesar dos veces el mismo aviso |
| `webhook-timestamp` | UNIX del envío                                                    |
| `webhook-signature` | `v1,<base64>` — puede traer más de una firma durante una rotación |
| `webhook-event`     | `qr.paid`                                                         |

***

## Verificar la firma

Es [Standard Webhooks](https://www.standardwebhooks.com/): cualquier librería oficial sirve, y la misma verificación vale para la API de cripto de CUCU.

```python theme={"system"}
from standardwebhooks import Webhook

Webhook(secret).verify(raw_body, dict(request.headers))  # lanza si no valida
```

A mano:

```
firmado = f"{webhook-id}.{webhook-timestamp}.{cuerpo_crudo}"
firma   = base64(HMAC-SHA256(clave, firmado))
```

La clave HMAC son los **bytes decodificados** del secreto sin el prefijo `whsec_`, no su texto. Firmá sobre el cuerpo **crudo**, antes de parsearlo.

***

## Reintentos

Respondé `2xx` apenas recibas; procesá después.

* 6 intentos: al instante, 5 s, 30 s, 5 min, 30 min, 2 h.
* 15 segundos de timeout por intento.
* Una redirección cuenta como fallo: registrá la URL final.
* Si tu endpoint acumula fallos, se pausa un rato y se reanuda solo.

El mismo pago se puede entregar más de una vez (un timeout de tu lado, un reintento del nuestro). Deduplicá por `webhook-id`.

***

## Si preferís preguntar vos

El webhook no reemplaza a [consultar el estado](/payments/cobros-qr/consultar-estado): siempre podés preguntar por un `qrId`. Lo que ya no necesitás es preguntar en loop.
