> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.recurrente.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.recurrente.com/_mcp/server.

# Migrar a Webhooks Unificados (intent.*)

> Cómo pasar de los eventos por tipo al formato unificado intent.*

Recurrente unificó todos los eventos de pago en un solo formato: **`intent.*`**. En lugar de un evento distinto por método de pago (`payment_intent.*`, `bank_transfer_intent.*`, etc.), recibís un único evento `intent.*` con una estructura común, discriminada por el campo `type`. Escribís **un** handler en vez de uno por tipo.

> **Note**
>
> **Las cuentas existentes conservan su contrato.** Esta guía es para cuentas que reciben ambos formatos y quieren migrar su integración. Las cuentas nuevas reciben solo el unificado cuando hay reemplazo; no necesitan dar de baja el duplicado legacy.

Para reconocer cobros guardados antes de la migración, compara `legacy_id` con el ID anterior por tipo. Una vez vinculados, `id` y `receipt_number` identifican el mismo cobro en ambos formatos. Conserva también el tipo de evento: un cambio de estado posterior no es un duplicado.

## Eventos nuevos a configurar

Suscribí tu endpoint a estos eventos desde tu dashboard de Webhooks:

| Evento             | Cuándo se emite                                                |
| ------------------ | -------------------------------------------------------------- |
| `intent.succeeded` | Cobro exitoso (cualquier método de pago)                       |
| `intent.pending`   | Cobro iniciado o pendiente (transferencia, cripto)             |
| `intent.failed`    | Cobro fallido                                                  |
| `intent.canceled`  | Intent cancelado                                               |
| `intent.paid`      | Solo para pagos con balance: le llega a la cuenta **pagadora** |

## Cómo funciona

1. Para las cuentas existentes, Recurrente sigue enviando **los dos formatos** por cada pago compatible: el legacy por tipo (`payment_intent.*`, etc.) **y** el unificado (`intent.*`), como entregas separadas.
2. En tu dashboard de Webhooks suscribís tu endpoint a los `intent.*` que te interesen.
3. Tu handler ramifica según el campo `type` y procesa.

> **Warning**
>
> Si tu cuenta conserva ambos formatos y tu endpoint **no filtra por tipo de evento**, recibirá tanto los `payment_intent.*` como los `intent.*` para el mismo pago. Para no procesar el mismo cobro dos veces, suscribite a **uno solo** de los dos formatos (o chequeá el campo `event_type` en tu handler).

## El payload unificado

```json
{
  "type": "payment",            // payment | bank_transfer | crypto | balance | cash
  "event_type": "intent.succeeded",
  "id": "in_8c3a1f20",
  "status": "succeeded",        // pending | succeeded | failed | canceled | paid
  "raw_status": "succeeded",    // el estado concreto, sin normalizar
  "amount_in_cents": 25000,
  "currency": "GTQ",
  "customer": { "id": "cus_...", "email": "...", "nit": "CF" },
  "product": { "id": "prod_..." },
  "checkout": { "...": "..." },
  "payment":  { "...": "..." },
  "details": { "fee": 875, "installments": 1, "channel": "Tarjeta", "...": "..." }
}
```

El envelope y el núcleo común son idénticos para los cinco tipos; solo cambia `type` y el contenido de `details`. Ver la estructura completa en la [documentación de Webhooks](/guias-espanol/comenzar/webhooks).

## Mapeo: legacy → unificado

| Evento legacy                                               | Evento unificado                     | `type`          |
| ----------------------------------------------------------- | ------------------------------------ | --------------- |
| `payment_intent.succeeded`                                  | `intent.succeeded`                   | `payment`       |
| `payment_intent.failed`                                     | `intent.failed`                      | `payment`       |
| `payment_intent.requires_capture` / `requires_verification` | `intent.pending`                     | `payment`       |
| `bank_transfer_intent.{pending,succeeded,failed}`           | `intent.{pending,succeeded,failed}`  | `bank_transfer` |
| `crypto_intent.{pending,succeeded,failed}`                  | `intent.{pending,succeeded,failed}`  | `crypto`        |
| `balance_intent.succeeded`                                  | `intent.succeeded`                   | `balance`       |
| `balance_intent.paid`                                       | `intent.paid`                        | `balance`       |
| `cash_intent.{succeeded,failed,canceled}`                   | `intent.{succeeded,failed,canceled}` | `cash`          |

Los estados intermedios de tarjeta (`requires_capture`, `requires_verification`) se **normalizan** a `pending`. El estado concreto siempre está disponible en `raw_status`.

## Pasos para migrar

1. **Revisá el payload nuevo.** Mirá la estructura en la [documentación de Webhooks](/guias-espanol/comenzar/webhooks), o consultá un pago real con `GET /api/intents/{id}` (devuelve el mismo formato que el webhook).
2. **Actualizá tu handler** para consumir `intent.*` y ramificar por `type`:
   ```js
   switch (event.type) {
     case "payment":       /* event.details.fee, .installments, ... */ break
     case "bank_transfer": /* event.details.bank_reference, .sender_comment */ break
     case "crypto":
     case "balance":
     case "cash":          break
   }
   // el estado siempre en event.status: pending | succeeded | failed | canceled | paid
   ```
3. **Suscribí tu endpoint** a los `intent.*` desde el dashboard de Webhooks.
4. **(Opcional) Dá de baja los eventos por tipo.** Cuando tu handler unificado funcione, desuscribite de los `payment_intent.*`, `bank_transfer_intent.*`, etc., para dejar de recibir el formato duplicado.

## Eventos que no cambian

`subscription.*`, `refund.*`, `dispute.*`, `automated_bank_transfer_intent.*` y `setup_intent.*` siguen exactamente igual — no son parte del recurso `Intent` unificado.