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

# Events

> Log canónico de eventos de tu organización. Pull desde la API, push vía webhooks — mismo origen.

ZoPay Connect mantiene un **log de eventos** como registro canónico de cada efecto colateral visible para el partner. Cada evento es un objeto durable e inmutable, consultable vía API.

Los webhooks son la entrega **push** de esos eventos; el log de eventos es la entrega **pull**. **Úsalos juntos**: el webhook es notificación; el log es auditoría, fuente de verdad y herramienta de recuperación.

## Dos superficies, una fuente de verdad

```
┌─────────────────────────────────────────────────────────┐
│  Llega un depósito on-chain                             │
│  (o el simulador lo dispara en sandbox)                 │
└──────────────────────────┬──────────────────────────────┘
                           │
                           ▼
              ┌────────────────────────────┐
              │  connect_events row        │  ← registro canónico
              │  evt_01KT…                 │     (durable, inmutable,
              │                            │      replicable)
              └────────────┬───────────────┘
                           │
                           ▼
              ┌────────────────────────────┐
              │  webhook_deliveries row    │  ← estado de entrega
              │                            │     (cuerpo + firma se
              │                            │      generan en cada
              │                            │      intento)
              └────────────┬───────────────┘
                           │
                           ▼
                ┌──────────────────────┐
                │  HTTP POST a tu URL  │
                └──────────────────────┘
```

La fila del evento se escribe **dentro de la misma transacción de base de datos** que la transición de estado subyacente (el payment intent pasando a `completed`, la dirección recibiendo un depósito). Existe haya o no webhook configurado — partners sin URL configurada igual pueden hacer `GET /events` y recuperar todo lo registrado.

## Forma del objeto

```json theme={null}
{
  "id": "evt_01KT76B86MR17Y20YWCPTTMZCT",
  "object": "event",
  "type": "payment_intent.completed",
  "livemode": true,
  "related_to": "8c3f1d4a-1e22-4b6f-9a72-9d4eb6c20fce",
  "related_to_kind": "payment_intent",
  "api_version": "v1",
  "created_at": "2026-06-04T10:32:14.872Z",
  "data": {
    "ref_code": "ABC12",
    "amount": 100.0,
    "currency": "USDT",
    "address_receiver": "<destination address>",
    "address_sender": "<sender address>",
    "network": "SOL",
    "hash": "<on-chain tx hash>",
    "status": "completed",
    "metadata": {}
  }
}
```

| Campo             | Notas                                                                                                                                                                |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`              | ULID con prefijo `evt_` + 26 chars Crockford base32. Mismo id aparece en el body de webhook (`event_id`) y en el header `X-ZoPay-Event-Id` de cada entrega y replay. |
| `object`          | Siempre `event`. Discriminador estilo Stripe.                                                                                                                        |
| `type`            | Tipo del evento (ver abajo).                                                                                                                                         |
| `livemode`        | `true` si el evento se produjo en modo live; `false` en sandbox.                                                                                                     |
| `related_to`      | UUID del objeto principal del evento. Para `payment_intent.completed`, el id del intent.                                                                             |
| `related_to_kind` | Tabla a la que apunta `related_to`. Hoy: `payment_intent`.                                                                                                           |
| `api_version`     | Versión del schema del payload. Hoy `v1`. Cambios incompatibles bumpan; eventos viejos se leen en su versión original.                                               |
| `created_at`      | Timestamp del servidor cuando se escribió la fila.                                                                                                                   |
| `data`            | Payload de negocio. Su forma depende de `type`.                                                                                                                      |

## Tipos de evento

### `payment_intent.completed`

Un payment intent llegó al estado `completed` — los depósitos acumulados igualaron o superaron el monto solicitado.

* `related_to_kind`: `payment_intent`
* `related_to`: UUID del intent
* Webhook wire name: `payment.completed` (legacy; el tipo canónico para filtrar en `GET /events?type=…` es `payment_intent.completed`).
* **Dispara exactamente una vez** por intent, en la transición terminal.
* `data`: ver forma arriba.

### `payment_intent.payment_received`

Un depósito aterrizó en una dirección del intent — **dispara en cada crédito**, incluyendo parciales. Distinto de `payment_intent.completed`:

|                                     | `payment.received` (nuevo)                       | `payment.completed` (existente)              |
| ----------------------------------- | ------------------------------------------------ | -------------------------------------------- |
| Dispara en parciales                | ✅ Sí                                             | ❌ No                                         |
| Dispara en el depósito que completa | ✅ Sí (junto a `payment.completed`)               | ✅ Sí                                         |
| Grano de idempotencia               | **Transacción** (un evento por fila de depósito) | **Intent** (un evento por intent en su vida) |
| Campo `status`                      | `"partial"` o `"completed"`                      | `"completed"`                                |
| Carga campos cumulativos            | ✅                                                | ❌                                            |

Este evento es **aditivo** — integraciones existentes que sólo manejan `payment.completed` siguen funcionando sin cambios. Suscríbete a `payment.received` si quieres notificaciones per-depósito (p. ej. barras de progreso de pago parcial o detección de carritos abandonados).

* `related_to_kind`: `payment_intent`
* `related_to`: UUID del intent
* Webhook wire name: `payment.received` (canónico: `payment_intent.payment_received`).
* `data` agrega tres campos cumulativos encima del base per-depósito:

```json theme={null}
{
  "ref_code": "ABC12",
  "amount": 3.0,                          // amount de ESTE depósito
  "currency": "USDT",
  "address_receiver": "<address that received the deposit>",
  "address_sender": "<sender wallet, if available>",
  "network": "SOL",
  "hash": "<on-chain transaction hash>",
  "status": "partial",                    // "partial" o "completed"
  "metadata": { ... },

  "amount_received": 3.0,                 // cumulativo DESPUÉS de este crédito
  "amount_remaining": 2.0,                // max(0, intent.amount - amount_received)
  "partial_payment": true                 // true si este crédito NO completó el intent
}
```

**Ramifica por `partial_payment`** para decidir qué renderizar:

```python theme={null}
if event["event"] == "payment.received":
    if event["partial_payment"]:
        show_partial_ui(event["amount_received"], event["amount_remaining"])
    # else: el evento payment.completed correspondiente también dispara;
    # deja que ese handler renderice éxito para que la lógica viva en un solo lugar.
```

**En el depósito que completa, los dos eventos disparan** — primero `payment.received` con `partial_payment=false`, después `payment.completed`. Cada uno carga su propio `event_id` así que la deduplicación receiver-side (sobre `X-ZoPay-Event-Id`) los maneja correctamente.

### `address.deposit.received` (próximamente)

Un depósito aterrizó en una dirección estándar provisionada con `POST /addresses` (no asociada a un intent). Es la señal de **wallet whitelabel**: tu usuario depositó crypto en la dirección que minteaste para él y necesitas acreditar su balance interno.

* `related_to_kind`: `address`
* `related_to`: UUID de la dirección
* Webhook wire name: `address.deposit.received`

**Regla de precedencia:** si el depósito cae en una dirección de payment intent → `payment_intent.payment_received` (y `payment_intent.completed` en el que cierra). Si cae en una dirección estándar sin intent → `address.deposit.received`. Nunca ambos paths para el mismo depósito.

Tu handler debe rutear por `type`, no por la URL — así estará listo cuando este tipo se active.

## Operaciones

* [Listar eventos](/api-reference/endpoints/get-events) — `GET /events` con filtros por `type`, `related_to`, `livemode`, `from`/`to`, `cursor`, `limit`.
* [Consultar un evento](/api-reference/endpoints/get-events-by-id) — `GET /events/{event_id}`.
* [Replicar un evento](/api-reference/endpoints/post-events-by-id-replay) — `POST /events/{event_id}/replay` encola una nueva entrega webhook con el mismo `evt_…`.

## Receta: "Mi handler nunca recibió el webhook del evento X"

1. **Encuentra el evento** con `GET /events?related_to=<intent-id>` o por rango temporal `?from=…&to=…`.
2. **Inspecciona** el evento (`GET /events/{id}`). Si falta entero, el problema está aguas arriba del log — abre ticket con el hash on-chain.
3. **Revisa el estado de entrega** en el dashboard (delivery rows asociadas).
4. **Arregla** lo que sea (servidor caído, secreto rotado, endpoint deshabilitado, etc.).
5. **Replay**: `POST /events/{id}/replay` con `Idempotency-Key`. El `X-ZoPay-Event-Id` será el mismo, así que tu deduplicación lo reconocerá.
6. **Confirma** en el dashboard que la entrega nueva llegó a `delivered`.

Para recuperación masiva (ventana de outage), lista por rango temporal y replica en bucle — el rate limit por clave pacea naturalmente.
