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

> Eventos en tiempo real con firma Standard Webhooks (HMAC-SHA256). Política de 6 reintentos con backoff.

## Webhooks Salientes

CUCU entrega eventos a tu URL registrada usando el estándar **Standard Webhooks** para que la verificación de firma sea sencilla y predecible.

***

## Headers en cada entrega

| Header              | Descripción                                                                            |
| ------------------- | -------------------------------------------------------------------------------------- |
| `webhook-id`        | UUID único de esta entrega. Úsalo como `delivery_id` para idempotencia 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 (`payment.confirmed`, `payment.failed`, etc.).                          |

***

## Verificación de firma

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

Compara `firma_esperada` con el valor en `webhook-signature` (después del prefijo `v1,`).

**Ejemplo en Python:**

```python theme={"system"}
import base64
import hashlib
import hmac

def verificar_webhook_cucu(
    body: bytes,
    webhook_id: str,
    webhook_timestamp: str,
    webhook_signature: str,
    secret: str,
) -> bool:
    mensaje = f"{webhook_id}.{webhook_timestamp}.{body.decode()}"
    firma = base64.b64encode(
        hmac.new(secret.encode(), mensaje.encode(), hashlib.sha256).digest()
    ).decode()
    esperada = webhook_signature.removeprefix("v1,")
    return hmac.compare_digest(firma, esperada)
```

**Ejemplo en Node.js:**

```javascript theme={"system"}
const crypto = require('crypto');

function verificarWebhookCucu({ body, webhookId, webhookTimestamp, webhookSignature, secret }) {
  const mensaje = `${webhookId}.${webhookTimestamp}.${body}`;
  const firma = crypto
    .createHmac('sha256', secret)
    .update(mensaje)
    .digest('base64');
  const esperada = webhookSignature.replace('v1,', '');
  return crypto.timingSafeEqual(Buffer.from(firma), Buffer.from(esperada));
}
```

***

## Eventos

### `payment.confirmed`

El pago fue recibido y confirmado en la blockchain. Acredita el pedido en tu sistema.

```json theme={"system"}
{
  "event": "payment.confirmed",
  "occurred_at": "2026-01-25T15:22:45Z",
  "charge_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "external_id": "CHG-20260125-00042",
  "provider": "cucu_pay",
  "amount": "50.00",
  "currency": "USDT",
  "status": "completed",
  "metadata": {
    "user_id": "usr_abc123",
    "plan": "pro",
    "invoice_id": "inv_0042"
  }
}
```

### `payment.failed`

El pago fue detectado pero no pudo procesarse (monto incorrecto, error de red, etc.).

```json theme={"system"}
{
  "event": "payment.failed",
  "occurred_at": "2026-01-25T15:23:10Z",
  "charge_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "external_id": "CHG-20260125-00042",
  "provider": "cucu_pay",
  "amount": "50.00",
  "currency": "USDT",
  "status": "failed",
  "error": "amount_mismatch",
  "metadata": {}
}
```

### `charge.expired`

El usuario no pagó dentro del tiempo de expiración del cobro.

```json theme={"system"}
{
  "event": "charge.expired",
  "occurred_at": "2026-01-25T15:30:00Z",
  "charge_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "external_id": "CHG-20260125-00042",
  "status": "expired",
  "metadata": {}
}
```

### `charge.cancelled`

El cobro fue cancelado programáticamente (via `POST /cancel`).

```json theme={"system"}
{
  "event": "charge.cancelled",
  "occurred_at": "2026-01-25T15:20:00Z",
  "charge_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "external_id": "CHG-20260125-00042",
  "status": "cancelled",
  "metadata": {}
}
```

### `webhook.test`

Evento sintético generado al llamar `POST /merchants/me/webhook/test`. No representa un pago real.

```json theme={"system"}
{
  "event": "webhook.test",
  "occurred_at": "2026-01-25T14:00:00Z",
  "merchant_id": "392b8825-18d3-404f-a029-2f11c8d14d32",
  "delivery_id": "d4e5f6a7-b8c9-0123-defa-456789abcdef"
}
```

***

## Política de reintentos

| Intento       | Delay   |
| ------------- | ------- |
| 1 (inmediato) | 0s      |
| 2             | \~30s   |
| 3             | \~2min  |
| 4             | \~8min  |
| 5             | \~30min |
| 6             | \~2h    |

Si los 6 intentos fallan, la entrega queda en estado `failed` y puedes consultarla en tu dashboard o contactar soporte CUCU.

**Tu endpoint debe responder `2xx` para que CUCU considere la entrega exitosa.** Cualquier otro código HTTP (incluyendo `3xx` y `5xx`) activa el siguiente reintento.
