# Payloads de los Webhooks

Ejemplos de los payloads enviados para cada tipo de evento. Todos los webhooks son enviados como HTTP POST con `Content-Type: application/json`.

::: tip Headers de seguridad
Cada notificacion incluye los headers `X-Monetarie-Signature` (HMAC-SHA256), `X-Monetarie-Timestamp`, `X-Monetarie-Event-Id` y `X-Monetarie-Event-Type`. Consulte [Webhooks - Vision General](/es/webhooks) para detalles sobre validacion.
:::

---

## Referencia de Status

No todos los eventos significan que la transaccion esta concluida. Use la tabla abajo para saber cuando el dinero fue efectivamente liquidado.

| Evento | Status | Significado | Dinero liquidado? |
|--------|--------|-------------|-------------------|
| `pix.charge.created` | `created` | QR code generado o cash-in iniciado. Aguardando pago. | **No** - solo creado |
| `pix.charge.paid` | `paid` | PIX recibido y **liquidado en cuenta**. Saldo actualizado, tarifa cobrada. | **Si** |
| `pix.charge.expired` | `expired` | QR code expiro sin pago. | N/A |
| `pix.charge.cancelled` | `cancelled` | QR code cancelado antes del pago. **Aun no disparado en produccion.** | N/A |
| `pix.payout.queued` | `queued` | PIX enviado aguardando reprocesamiento automatico por limite DICT. Sin debito aun. | **No** -- aguardando disponibilidad |
| `pix.payout.processing` | `processing` | PIX enviado, aguardando confirmacion del destino. Saldo reservado (hold). | **No** - puede revertir |
| `pix.payout.confirmed` | `settled` | PIX enviado **confirmado** por el destino. Debito definitivo. | **Si** |
| `pix.payout.failed` | `rejected` | PIX enviado rechazado por el destino. Hold liberado, saldo restaurado. | **No** |
| `pix.payout.returned` | `returned` | PIX enviado devuelto despues de liquidacion. | **Si** (reverso) |
| `pix.refund.requested` | `requested` | Devolucion PIX solicitada (MED). Bloqueo cautelar creado. | Parcial |
| `pix.refund.completed` | `settled` / `completed` | Devolucion PIX concluida y liquidada. Debito definitivo. | **Si** |
| `pix.return.received` | `settled` | Devolucion PIX recibida y liquidada (credito en la cuenta). | **Si** |
| `pix.infraction.created` | `ACKNOWLEDGED` | Infraccion PIX reportada contra usted. Requiere accion. | Parcial - puede haber valor en disputa |
| `pix.infraction.resolved` | `CLOSED` / `CANCELLED` | Infraccion resuelta (devolucion ejecutada o negada). | N/A - efecto en otro evento |
| `pix.infraction.defense_submitted` | `defense_submitted` | Defensa enviada por el merchant. Aguardando BACEN. | N/A |
| `webhook.test` | `test` | Evento de prueba disparado manualmente via portal Admin/Merchant. | N/A |

**Reglas de reconciliacion**:

- Considere **entradas** de saldo solo en los status: `paid` (credito PIX IN) y `returned` (reverso de un PIX OUT previamente enviado).
- Considere **salidas** de saldo solo en los status: `settled` (debito PIX OUT confirmado) y `completed` (debito MED refund definitivo), y `settled` en `pix.return.received` (reverso de un PIX IN previamente recibido).
- Todos los demas status (`created`, `queued`, `processing`, `rejected`, `expired`, `requested`, `ACKNOWLEDGED`, `defense_submitted`, etc.) son **intermedios** - no disparan movimiento contable de su lado.
- No tratar `pix.payout.processing` como confirmacion; aguarde el evento terminal (`pix.payout.confirmed` o `pix.payout.failed`).

---

---

## Aviso de contrato (auditoria 2026-07-25)

El cuerpo entregado usa **camelCase** (`accountId`, `endToEndId`,
`payerDocument`) y siempre lleva **`eventType`**. El tipo de evento tambien
viaja en el header `X-Monetarie-Event-Type` - use el que prefiera.

Atencion a una diferencia deliberada: la **API HTTP de gestion de webhooks**
(`POST /api/external/webhooks`, `GET /webhooks/:id`) responde en **snake_case**
(`is_active`, `created_at`). Solo el **cuerpo entregado en su endpoint** es
camelCase.

### Forma estable: un campo documentado nunca llega ausente

Todo campo listado en este catalogo **siempre existe en el cuerpo**. Cuando no
tenemos el dato, llega como **`null`** - nunca ausente. La diferencia importa:
con `null` usted hace destructuring sin romper y distingue "no lo tenemos" de
"ese campo no existe".

Esto vale incluso cuando el mismo evento nace por caminos internos distintos. Un
`pix.charge.paid` originado por la liquidacion instantanea y otro originado por
la reconciliacion llegan con **la misma forma**; lo que cambia es cuanto del
contenido esta completo.

### Alias de campo

Algunos campos viajan con dos nombres, por compatibilidad. Ambos llevan el
**mismo valor** - use el que prefiera:

| evento | campo | alias |
|---|---|---|
| `pix.payout.confirmed` / `.processing` / `.failed` | `payer` | `sender` |
| `pix.infraction.*` | `endToEndId` | `e2eId` |
| `pix.refund.requested` / `.completed` | `endToEndId` | `e2eId` |

## Campos comunes

Todos los payloads de webhook incluyen estos campos:

| Campo | Tipo | Descripcion |
|-------|------|-------------|
| `eventType` | string | Tipo del evento que disparo el webhook (ej: `pix.charge.paid`) |
| `status` | string | Estado de la operacion - consulte la [Referencia de Status](#referencia-de-status) |
| `accountId` | integer | Numero de su cuenta en Monetarie |
| `entityId` | string (UUID) | Identificador de la entidad Monetarie |

**Valores monetarios**: Todos los valores son en **subcentavos** (1 BRL = 10.000 subcentavos). Para convertir a reales: `valor / 10000`. Ejemplo: `300000 / 10000 = R$ 30,00`.

---

## pix.charge.paid

Enviado cuando un PIX es recibido y **liquidado** en la cuenta. Este es el evento que confirma que el dinero entro.

### Ejemplo - vinculado a QR code

```json
{
  "eventType": "pix.charge.paid",
  "status": "paid",
  "accountId": 10014,
  "amount": 300000,
  "feeAmount": 400,
  "endToEndId": "E9040088820260402095758709999671",
  "entityId": "26a48541-edce-4581-8c6e-564e7f2e6cd7",
  "txId": "u5f26sfyrq4plkw7tjwa",
  "qrCodeId": "f401d5e3-a2b1-4c8e-9f3d-1234567890ab",
  "counterpartyName": "MARIA SANTOS",
  "payerDocument": "12345678901",
  "payerIspb": "60701190",
  "payerBankName": "Itau Unibanco S.A.",
  "externalId": "order-9876",
  "paidAt": "2026-04-02T09:58:05Z",
  "recipientKey": "26a48541-edce-4581-8c6e-564e7f2e6cd7",
  "recipientKeyType": "evp",
  "receiver": {
    "name": "PENHOTA GESTAO E INTERMEDIACAO LTDA",
    "document": "62188010000150",
    "account": "0000000019",
    "ispb": "46026562",
    "institutionName": "MONETARIE IP"
  }
}
```

### Ejemplo - transferencia directa (sin QR)

```json
{
  "eventType": "pix.charge.paid",
  "status": "paid",
  "accountId": 10014,
  "amount": 300000,
  "feeAmount": 400,
  "endToEndId": "E9040088820260402095758709999671",
  "entityId": "26a48541-edce-4581-8c6e-564e7f2e6cd7",
  "txId": null,
  "qrCodeId": null,
  "counterpartyName": "JOAO SILVA",
  "payerDocument": "98765432100",
  "payerIspb": "00000000",
  "payerBankName": "Banco do Brasil S.A.",
  "externalId": null,
  "paidAt": "2026-04-02T10:15:22Z",
  "recipientKey": "12345678901",
  "recipientKeyType": "cpf",
  "receiver": {
    "name": "PENHOTA GESTAO E INTERMEDIACAO LTDA",
    "document": "62188010000150",
    "account": "0000000019",
    "ispb": "46026562",
    "institutionName": "MONETARIE IP"
  }
}
```

| Campo | Tipo | Descripcion |
|-------|------|-------------|
| `eventType` | string | Siempre `pix.charge.paid` |
| `status` | string | Siempre `paid` |
| `accountId` | integer | Numero de la cuenta que recibio el PIX |
| `amount` | integer | Valor recibido en subcentavos. `300000` = R$ 30,00 |
| `feeAmount` | integer | Tarifa cobrada en subcentavos. `400` = R$ 0,04 |
| `endToEndId` | string | Identificador E2E del BACEN (unico por transaccion PIX) |
| `entityId` | string (UUID) | Identificador de la entidad Monetarie |
| `txId` | string o null | ID de la transaccion. Presente cuando esta vinculado a un QR code. `null` para transferencias directas |
| `qrCodeId` | string o null | UUID del QR code vinculado. `null` para transferencias directas |
| `counterpartyName` | string o null | Nombre del pagador (remitente) |
| `payerDocument` | string o null | CPF/CNPJ del pagador (solo digitos) |
| `payerIspb` | string o null | ISPB (8 digitos) de la institucion del pagador |
| `payerBankName` | string o null | Nombre de la institucion del pagador, resuelto via cache BCB (896 bancos) |
| `externalId` | string o null | Su identificador externo. Presente cuando el QR code fue creado via API con external_id. `null` para transferencias directas o QR sin external_id |
| `paidAt` | string (ISO 8601) | Fecha/hora de la liquidacion (UTC) |
| `recipientKey` | string o null | Clave PIX que recibio el pago (EVP, CPF, CNPJ, email o telefono) |
| `recipientKeyType` | string o null | Tipo de la clave PIX receptora: `evp`, `phone`, `email`, `cpf`, `cnpj` |
| `receiver` | object | Datos completos del receptor (usted). Incluye `name`, `document`, `account`, `ispb`, `institution_name` |

::: warning Variacion de payload: reconciliacion pos-deploy
En escenarios raros de reconciliacion operacional o replay retroactivo despues de incidente, `pix.charge.paid` puede llegar con **campos reducidos** - tipicamente sin `receiver`, `payer_ispb`, `payer_bank_name`, `recipient_key` ni `recipient_key_type`. Los campos que **siempre** estan presentes: `event_type`, `status`, `account_id`, `amount`, `end_to_end_id`, `fee_amount`, `counterparty_name`, `payer_document`, `external_id`, `paid_at`, `tx_id` (cuando vinculado a QR).

Su consumidor debe tratar todos los campos no-obligatorios como opcionales (nil/ausente) y reconciliar por el `end_to_end_id`.
:::

::: tip `qr_code_id` es un UUID v4 canonico
El campo `qr_code_id` es siempre serializado como UUID v4 en formato canonico (36 caracteres con guiones: `f401d5e3-a2b1-4c8e-9f3d-1234567890ab`) - nunca como binario crudo, base64 o hex sin guiones. Use para correlacion directa con la respuesta de `POST /api/external/pix/cash-in` (campo `transaction_id` en su request retorna el `tx_id` del QR, y `qr_code_id` aqui es la clave primaria interna).
:::

---

## pix.charge.expired

Disparado automaticamente cuando QR code expira sin pago. La verificacion de expiracion corre periodicamente y puede registrar el evento algunos minutos despues del `expires_at` real.

```json
{
  "eventType": "pix.charge.expired",
  "status": "expired",
  "accountId": 10014,
  "entityId": "26a48541-edce-4581-8c6e-564e7f2e6cd7",
  "txId": "abc123def456ghi789",
  "amount": 500000,
  "externalId": "order-9876",
  "expiredAt": "2026-04-02T14:30:00Z"
}
```

| Campo | Tipo | Descripcion |
|-------|------|-------------|
| `eventType` | string | Siempre `pix.charge.expired` |
| `status` | string | Siempre `expired` |
| `accountId` | integer | Cuenta que emitio el QR code |
| `entityId` | string (UUID) | Identificador de la entidad Monetarie |
| `txId` | string | ID del cobro/QR code |
| `amount` | integer | Valor esperado en subcentavos (no cobrado) |
| `externalId` | string o null | Su identificador externo, si enviado en la creacion |
| `expiredAt` | string (ISO 8601) | Momento en que la API registro la expiracion (UTC) - puede ser posterior al `expires_at` real del QR por algunos minutos |

---

## pix.charge.cancelled

::: warning Planeado
Este evento esta registrado como valido para suscripcion, pero aun no es disparado por el sistema. Usted puede incluirlo en la lista de `events` al crear un webhook (la API acepta), pero ninguna notificacion sera enviada hasta que la funcionalidad de cancelacion de QR Code sea implementada.
:::

---

## pix.charge.created

Enviado cuando un QR code es generado o un cash-in es iniciado. Ningun movimiento financiero ocurrio.

```json
{
  "eventType": "pix.charge.created",
  "status": "created",
  "accountId": 10014,
  "amount": 500000,
  "entityId": "26a48541-edce-4581-8c6e-564e7f2e6cd7",
  "txId": "abc123def456ghi789",
  "externalId": "order-9876"
}
```

| Campo | Tipo | Descripcion |
|-------|------|-------------|
| `eventType` | string | Siempre `pix.charge.created` |
| `status` | string | Siempre `created` |
| `amount` | integer | Valor esperado en subcentavos |
| `txId` | string | ID del cobro/QR code |
| `externalId` | string o null | Su identificador externo, retornado tal como enviado. `null` si no informado o QR generado por el portal |

---

## pix.payout.held

Sent when an outbound PIX is **held for review at the settlement agent** (authorization/anti-fraud queue, SPI status `AGUARDANDO_AUTORIZACAO`). The operation has NOT failed: it settles (`pix.payout.confirmed`) or is rejected (`pix.payout.failed`) once the agent decides - typically within minutes. **Do not resubmit the payment**: the amount remains reserved and a resubmission would create a duplicate payment. Emitted at most once per operation, after ~2 minutes without confirmation.

```json
{
  "eventType": "pix.payout.held",
  "status": "processing",
  "accountId": 10014,
  "amount": 500000,
  "endToEndId": "E4602656220260402101500000001",
  "transactionId": "PIXOUT1027803798e62a0f502761008061",
  "externalId": "payment-456",
  "reason": "held_at_settlement_agent",
  "spiStatus": "AGUARDANDO_AUTORIZACAO",
  "heldSince": "2026-06-10T16:45:36Z"
}
```

| Field | Type | Description |
|-------|------|-----------|
| `eventType` | string | Always `pix.payout.held` |
| `status` | string | Always `processing` - non-terminal state |
| `reason` | string | Always `held_at_settlement_agent` |
| `spiStatus` | string | SPI status queried at emission time (e.g. `AGUARDANDO_AUTORIZACAO`) |
| `heldSince` | string (ISO 8601) | When the PACS.008 was sent (hold start) |
| `amount` | integer | Amount in subcents |
| `externalId` | string or null | Your external identifier |

---

## pix.payout.confirmed

Enviado cuando un PIX enviado es **confirmado** por la institucion destino. Debito definitivo.

```json
{
  "eventType": "pix.payout.confirmed",
  "status": "settled",
  "accountId": 10014,
  "amount": 500000,
  "feeAmount": 200,
  "endToEndId": "E4602656220260402101500000001",
  "entityId": "26a48541-edce-4581-8c6e-564e7f2e6cd7",
  "transactionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "externalId": "payment-456",
  "pixKey": "destinatario@email.com",
  "pixKeyType": "EMAIL",
  "description": "Pagamento fornecedor",
  "initiatedAt": "2026-04-02T10:14:59Z",
  "recipient": {
    "name": "EMPRESA DESTINO LTDA",
    "document": "12345678000199",
    "ispb": "60701190",
    "account": "12345678",
    "agency": "0001",
    "institutionName": "Itau Unibanco S.A."
  },
  "sender": {
    "name": "MINHA EMPRESA LTDA",
    "document": "98765432000100",
    "ispb": "46026562",
    "account": "00001234",
    "agency": "0001"
  }
}
```

| Campo | Tipo | Descripcion |
|-------|------|-------------|
| `eventType` | string | Siempre `pix.payout.confirmed` |
| `status` | string | Siempre `settled` - debito definitivo |
| `amount` | integer | Valor enviado en subcentavos |
| `feeAmount` | integer | Tarifa cobrada en subcentavos |
| `endToEndId` | string | Identificador E2E del BACEN |
| `transactionId` | string (UUID) | Identificador unico de la transaccion |
| `externalId` | string o null | Su identificador externo |
| `pixKey` | string | Clave PIX del destinatario |
| `pixKeyType` | string | Tipo de la clave: `CPF`, `CNPJ`, `EMAIL`, `PHONE`, `EVP` |
| `description` | string o null | Descripcion informada por el remitente |
| `initiatedAt` | string (ISO 8601) | Momento en que **este webhook** fue disparado (UTC). **No** es el timestamp del request original de cash-out ni del settlement BACEN. Para correlacionar con el momento que usted envio el POST, use el `created_at` del `GET /api/external/transactions/ref/{external_id}`; para el momento exacto de la entrega del webhook, use el header `X-Monetarie-Timestamp` |
| `recipient` | object | Datos bancarios del destinatario (resueltos via DICT) |
| `recipient.name` | string o null | Nombre del titular de la cuenta destino |
| `recipient.document` | string o null | CPF/CNPJ del destinatario (solo digitos) |
| `recipient.ispb` | string o null | ISPB de la institucion destino |
| `recipient.account` | string o null | Numero de la cuenta destino |
| `recipient.agency` | string o null | Agencia de la cuenta destino |
| `recipient.institution_name` | string o null | Nombre de la institucion destino (resuelto via cache BCB) |
| `sender` | object | Datos bancarios de la cuenta remitente (su cuenta Monetarie) |
| `sender.name` | string o null | Nombre del titular de la cuenta remitente |
| `sender.document` | string o null | CPF/CNPJ del remitente (solo digitos) |
| `sender.ispb` | string o null | ISPB de Monetarie (`46026562`) |
| `sender.account` | string o null | Numero de la cuenta remitente |
| `sender.agency` | string o null | Agencia de la cuenta remitente |

---

## pix.payout.processing

Enviado cuando un PIX enviado esta siendo procesado. El saldo esta reservado (hold) pero **no es definitivo**. Este evento es **opcional** - si usted solo quiere ser notificado en el estado terminal, ignorelo y espere por el `pix.payout.confirmed` o `pix.payout.failed`.

```json
{
  "eventType": "pix.payout.processing",
  "status": "processing",
  "accountId": 10014,
  "amount": 500000,
  "feeAmount": 200,
  "endToEndId": "E4602656220260402101500000001",
  "entityId": "26a48541-edce-4581-8c6e-564e7f2e6cd7",
  "transactionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "externalId": "payment-456",
  "pixKey": "destinatario@email.com",
  "pixKeyType": "EMAIL",
  "description": "Pagamento fornecedor",
  "initiatedAt": "2026-04-02T10:14:59Z",
  "recipient": {
    "name": "EMPRESA DESTINO LTDA",
    "document": "12345678000199",
    "ispb": "60701190",
    "account": "12345678",
    "agency": "0001",
    "institutionName": "Itau Unibanco S.A."
  },
  "sender": {
    "name": "MINHA EMPRESA LTDA",
    "document": "98765432000100",
    "ispb": "46026562",
    "account": "00001234",
    "agency": "0001"
  }
}
```

| Campo | Tipo | Descripcion |
|-------|------|-------------|
| `eventType` | string | Siempre `pix.payout.processing` |
| `status` | string | Siempre `processing` - saldo reservado, puede revertir |
| `amount` | integer | Valor en subcentavos |
| `feeAmount` | integer | Tarifa en subcentavos (misma tarifa que aparece en `confirmed`/`failed` posteriormente - es calculada en la creacion del cash-out, no despues) |
| `endToEndId` | string | Identificador E2E del BACEN |
| `transactionId` | string (UUID) | Identificador unico de la transaccion |
| `externalId` | string o null | Su identificador externo |
| `pixKey` | string | Clave PIX del destinatario |
| `pixKeyType` | string | Tipo de la clave: `CPF`, `CNPJ`, `EMAIL`, `PHONE`, `EVP` |
| `description` | string o null | Descripcion informada por el remitente |
| `initiatedAt` | string (ISO 8601) | Momento del dispatch de este webhook (UTC) - vea nota en `pix.payout.confirmed` |
| `recipient` | object | Datos bancarios del destinatario (resueltos via DICT) |
| `recipient.name` | string o null | Nombre del titular de la cuenta destino |
| `recipient.document` | string o null | CPF/CNPJ del destinatario (solo digitos) |
| `recipient.ispb` | string o null | ISPB de la institucion destino |
| `recipient.account` | string o null | Numero de la cuenta destino |
| `recipient.agency` | string o null | Agencia de la cuenta destino |
| `recipient.institution_name` | string o null | Nombre de la institucion destino |
| `sender` | object | Datos bancarios de la cuenta remitente (su cuenta Monetarie) |
| `sender.name` | string o null | Nombre del titular de la cuenta remitente |
| `sender.document` | string o null | CPF/CNPJ del remitente (solo digitos) |
| `sender.ispb` | string o null | ISPB de Monetarie (`46026562`) |
| `sender.account` | string o null | Numero de la cuenta remitente |
| `sender.agency` | string o null | Agencia de la cuenta remitente |

::: tip Orden de los eventos
Un `pix.payout.processing` es **siempre seguido** (segundos a minutos despues) por un `pix.payout.confirmed` o `pix.payout.failed`. En transacciones rapidas (settlement inmediato), el `processing` puede ser omitido y usted recibe directamente el terminal.
:::

---

## pix.payout.failed

Enviado cuando un PIX enviado es rechazado. Hold liberado, saldo restaurado.

::: tip Actualizado en 10/04/2026
El payload incluye los campos estructurados `reason_code` (codigo BACEN SPI de 2-6 caracteres) y `reason_description` (descripcion en ingles). Nuevas integraciones deben usar esos campos para enrutamiento programatico de fallas.

**Exclusion mutua:** cuando la API identifica un codigo BACEN en el rechazo (ej: `"rejected: AC03"`), el payload envia solo `reason_code` + `reason_description` - el campo legacy `reason` es **removido**. Cuando la falla no tiene codigo BACEN parseable (ej: timeout interno, error de provider sin codigo), el payload envia solo `reason` (string libre) - sin `reason_code`. Trate ambos formatos en su consumidor.
:::

```json
{
  "eventType": "pix.payout.failed",
  "status": "rejected",
  "accountId": 10014,
  "amount": 500000,
  "feeAmount": 200,
  "endToEndId": "E4602656220260402101500000001",
  "entityId": "26a48541-edce-4581-8c6e-564e7f2e6cd7",
  "transactionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "externalId": "payment-456",
  "pixKey": "destinatario@email.com",
  "pixKeyType": "EMAIL",
  "description": "Pagamento fornecedor",
  "initiatedAt": "2026-04-02T10:14:59Z",
  "reasonCode": "AC03",
  "reasonDescription": "Invalid creditor account number",
  "reason": "Conta destinatario nao encontrada",
  "recipient": {
    "name": "EMPRESA DESTINO LTDA",
    "document": "12345678000199",
    "ispb": "60701190",
    "account": "12345678",
    "agency": "0001",
    "institutionName": "Itau Unibanco S.A."
  },
  "sender": {
    "name": "MINHA EMPRESA LTDA",
    "document": "98765432000100",
    "ispb": "46026562",
    "account": "00001234",
    "agency": "0001"
  }
}
```

| Campo | Tipo | Descripcion |
|-------|------|-------------|
| `eventType` | string | Siempre `pix.payout.failed` |
| `status` | string | Siempre `rejected` - hold liberado, saldo restaurado |
| `amount` | integer | Valor en subcentavos |
| `feeAmount` | integer | Tarifa en subcentavos. **La tarifa mostrada es el valor que habria sido cobrado** - en el ledger TB la transferencia pending es revertida automaticamente, por lo tanto en la practica no hay debito de tarifa en transacciones rechazadas |
| `endToEndId` | string | Identificador E2E del BACEN |
| `transactionId` | string (UUID) | Identificador unico de la transaccion |
| `externalId` | string o null | Su identificador externo |
| `pixKey` | string | Clave PIX del destinatario |
| `pixKeyType` | string | Tipo de la clave: `CPF`, `CNPJ`, `EMAIL`, `PHONE`, `EVP` |
| `description` | string o null | Descripcion informada por el remitente |
| `initiatedAt` | string (ISO 8601) | Momento del dispatch de este webhook (UTC) |
| `reasonCode` | string o ausente | **Codigo BACEN SPI estructurado** (2-6 caracteres). Ejemplos: `AC03`, `ED05`, `AM02`, `BE01`, `MD06`, `FOCR`. Presente cuando la API identifico codigo BACEN en el rechazo. Use este campo para enrutamiento programatico |
| `reasonDescription` | string o ausente | Descripcion en ingles del `reason_code`. Presente junto con `reason_code`. Ejemplo: `"Invalid creditor account number"` |
| `reason` | string o ausente | **[Legacy]** Descripcion libre del motivo. Presente **solo** cuando el rechazo no tiene codigo BACEN parseable - **mutuamente exclusivo** con `reason_code` |
| `recipient` | object | Datos bancarios del destinatario (resueltos via DICT) |
| `recipient.name` | string o null | Nombre del titular de la cuenta destino |
| `recipient.document` | string o null | CPF/CNPJ del destinatario (solo digitos) |
| `recipient.ispb` | string o null | ISPB de la institucion destino |
| `recipient.account` | string o null | Numero de la cuenta destino |
| `recipient.agency` | string o null | Agencia de la cuenta destino |
| `recipient.institution_name` | string o null | Nombre de la institucion destino |
| `sender` | object | Datos bancarios de la cuenta remitente (su cuenta Monetarie) |
| `sender.name` | string o null | Nombre del titular de la cuenta remitente |
| `sender.document` | string o null | CPF/CNPJ del remitente (solo digitos) |
| `sender.ispb` | string o null | ISPB de Monetarie (`46026562`) |
| `sender.account` | string o null | Numero de la cuenta remitente |
| `sender.agency` | string o null | Agencia de la cuenta remitente |

::: warning Variaciones de payload entre dispatch sites
`pix.payout.failed` puede ser emitido por mas de un flujo operacional. En algunos escenarios, el payload puede enviar **tanto `reason` como `reason_code`**, o solo `reason` sin estructura. Trate siempre los dos campos como opcionales y prefiera `reason_code` cuando este presente.
:::

### Codigos `reason_code` mas comunes (BACEN SPI)

| Codigo | Significado en ingles | Accion recomendada |
|--------|----------------------|--------------------|
| `AC03` | Invalid creditor account number | Confirmar datos bancarios del destinatario con el cliente final |
| `AC06` | Creditor account blocked | Cuenta destino bloqueada - no reintentar |
| `AM02` | Not allowed amount (limit exceeded) | Valor excede limite de PIX del destino u origen |
| `AM04` | Insufficient funds | Saldo insuficiente en el origen |
| `BE01` | End customer not in whitelist | Identificador del destinatario no reconocido |
| `ED05` | Settlement failed | Falla en el settlement - puede reintentar despues de investigacion |
| `MD06` | Refund requested by end customer | Devolucion solicitada por el cliente final |
| `FOCR` | Forbidden credit return | Devolucion de credito prohibida |

Lista completa: consulte el _Catalogo de Mensajes del SPI_ del BACEN.

---

## pix.payout.returned

Enviado cuando un PIX que usted **envio** es devuelto por el banco destino **despues de liquidacion**. Raro, pero puede ocurrir hasta varios dias despues. El saldo del merchant **aumenta** (entrada).

::: danger Distincion de nomenclatura
Tres flujos diferentes pueden ser confundidos:
- **`pix.return.received`**: un PIX que usted **recibio** esta siendo devuelto al pagador original. **Saldo DISMINUYE**.
- **`pix.payout.returned`** (este): un PIX que usted **envio** esta volviendo a usted. **Saldo AUMENTA**.
- **`pix.refund.requested`**: bloqueo cautelar MED en un PIX que usted recibio. Fondos congelados.
:::

::: warning Mismo payload, dos eventos, dos status diferentes
La API puede disparar `pix.return.received` y `pix.payout.returned` para la misma devolucion usando el **mismo payload base** con el campo `status` ajustado para cada evento:

- `pix.return.received` → `status: "settled"` (PIX que **usted recibio** esta siendo devuelto → saldo disminuye)
- `pix.payout.returned` → `status: "returned"` (PIX que **usted envio** esta volviendo → saldo aumenta)

Si su logica de reconciliacion filtra por `status` o dedup por `(e2e, event_type)`, asegurese de distinguir `event_type` primero - el payload es casi identico.
:::

```json
{
  "eventType": "pix.payout.returned",
  "status": "returned",
  "accountId": 10014,
  "amount": 500000,
  "originalAmount": 500000,
  "refundedAmount": 500000,
  "feeAmount": 0,
  "netAmount": 500000,
  "isPartial": false,
  "totalRefunded": 500000,
  "remainingRefundable": 0,
  "entityId": "26a48541-edce-4581-8c6e-564e7f2e6cd7",
  "returnE2eId": "D4602656220260410111500000001",
  "endToEndId": "E4602656220260402101500000001",
  "originalTransactionId": "PIXOUTa1b2c3d4e5f67890abcdef1234567890",
  "externalId": "payment-456",
  "returnReason": "MD06",
  "returnReasonDescription": "Refund requested by end customer",
  "counterpartyIspb": "60701190",
  "counterpartyName": "EMPRESA DESTINO LTDA",
  "counterpartyDocument": "12345678000199",
  "counterpartyInstitutionName": "Itau Unibanco S.A.",
  "returnedAt": "2026-04-10T11:15:00Z"
}
```

| Campo | Tipo | Descripcion |
|-------|------|-------------|
| `eventType` | string | Siempre `pix.payout.returned` |
| `status` | string | Siempre `returned` - devolucion liquidada y acreditada en su cuenta |
| `amount` | integer | Mismo valor que `refunded_amount` (mantenido para compatibilidad) |
| `originalAmount` | integer | Valor del PIX OUT original en subcentavos |
| `refundedAmount` | integer | Valor efectivamente devuelto en esta devolucion (puede ser parcial) |
| `feeAmount` | integer | Tarifa cobrada en esta devolucion (generalmente `0`) |
| `netAmount` | integer | `refunded_amount - fee_amount` |
| `isPartial` | boolean | `true` cuando `refunded_amount < original_amount` o aun reste saldo a devolver |
| `totalRefunded` | integer | Suma de **todas** las devoluciones ya recibidas para esta transaccion original (incluye esta) |
| `remainingRefundable` | integer | `max(original_amount - total_refunded, 0)` - saldo aun pasible de devolucion |
| `returnE2eId` | string | E2E de la devolucion (prefijo `D`) |
| `endToEndId` | string | E2E de la transaccion PIX OUT original (prefijo `E`) |
| `originalTransactionId` | string | `transaction_id` del PIX OUT original. Use para correlacion con su sistema |
| `externalId` | string o null | Su identificador externo de la transaccion original (si aplicable) |
| `returnReason` | string | Codigo BACEN de la devolucion: `MD06`, `BE08`, `FR01`, `SL02` |
| `returnReasonDescription` | string | Descripcion en ingles del `return_reason` |
| `counterpartyIspb` | string | ISPB de la institucion que inicio la devolucion |
| `counterpartyName` | string | Nombre de la contraparte (institucion destino del PIX original) |
| `counterpartyDocument` | string o null | CPF/CNPJ de la contraparte |
| `counterpartyInstitutionName` | string o null | Nombre de la institucion contraparte (cache BCB) |
| `returnedAt` | string (ISO 8601) | Momento del dispatch de este webhook (UTC) |

::: warning Tarifa no es reembolsada
La tarifa del cash-out original **no** es reembolsada en `pix.payout.returned`. La tarifa fue cobrada por el envio exitoso, que realmente ocurrio. Si la regla de negocio exige reembolso de la tarifa al cliente final, el merchant debe hacer eso por separado.
:::

---

## pix.refund.requested

Enviado cuando una devolucion PIX es solicitada via MED (Mecanismo Especial de Devolucion). Fondos fueron bloqueados cautelarmente en la cuenta del merchant que recibio el PIX original.

::: warning Solo PIX In
Este evento solo se aplica a PIX recibidos (cash-in). Si usted envio un PIX y fue devuelto, recibira `pix.return.received` en vez de `pix.refund.*`.
:::

```json
{
  "eventType": "pix.refund.requested",
  "status": "requested",
  "accountId": 10014,
  "requestedAmount": 300000,
  "entityId": "26a48541-edce-4581-8c6e-564e7f2e6cd7",
  "blockId": "b1c2d3e4-f5g6-7890-hijk-lm1234567890",
  "infractionReportId": "INF20260402001",
  "e2eId": "E9040088820260402095758709999671",
  "externalId": null,
  "blockedAmount": 300000,
  "feeAmount": 0,
  "fraudCategory": "OTHER",
  "deadline": "2026-04-09T14:30:00Z",
  "scenario": "cautelar",
  "createdAt": "2026-04-02T14:30:00Z"
}
```

| Campo | Tipo | Descripcion |
|-------|------|-------------|
| `eventType` | string | Siempre `pix.refund.requested` |
| `status` | string | Siempre `requested` - bloqueo cautelar activo |
| `requestedAmount` | integer | Valor solicitado para devolucion en subcentavos |
| `blockId` | string (UUID) | Identificador del bloqueo cautelar |
| `infractionReportId` | string | Identificador de la infraccion en el proveedor PIX |
| `e2eId` | string | E2E de la transaccion PIX original que esta siendo impugnada |
| `externalId` | string o null | Su identificador externo (si aplicable) |
| `blockedAmount` | integer | Valor efectivamente bloqueado en subcentavos |
| `feeAmount` | integer | Tarifa MED en subcentavos |
| `fraudCategory` | string | Categoria del fraude alegado. Valores posibles: `SCAM`, `ACCOUNT_TAKEOVER`, `COERCION`, `FRAUDULENT_ACCESS`, `OTHER`. Cuando la contraparte no envia un `FraudType` especifico, el valor es `OTHER` (estandar para `REFUND_REQUEST`). |
| `deadline` | string (ISO 8601) | Plazo para analisis/defensa (UTC) |
| `scenario` | string | Escenario MED: `cautelar` o `fraude` |
| `createdAt` | string (ISO 8601) | Fecha/hora del bloqueo (UTC) |

---

## pix.refund.completed

Disparado cuando una devolucion MED es efectivada con exito. Dispara via `med/processor.ex:915` durante el ciclo de MED aceptado.

Formato del payload (confirmado por el code path `med/processor.ex:900-920`):

```json
{
  "eventType": "pix.refund.completed",
  "status": "settled",
  "accountId": 10014,
  "amount": 300000,
  "entityId": "26a48541-edce-4581-8c6e-564e7f2e6cd7",
  "blockId": "b1c2d3e4-f5g6-7890-hijk-lm1234567890",
  "infractionReportId": "INF20260402001",
  "e2eId": "E9040088820260402095758709999671",
  "externalId": null,
  "reason": "analysis_unfounded",
  "completedAt": "2026-04-02T14:30:00Z"
}
```

| Campo | Tipo | Descripcion |
|-------|------|-------------|
| `eventType` | string | Siempre `pix.refund.completed` |
| `status` | string | Siempre `completed` - devolucion MED finalizada |
| `amount` | integer | Valor devuelto en subcentavos |
| `blockId` | string (UUID) | Identificador del bloqueo cautelar |
| `infractionReportId` | string | Identificador de la infraccion en el proveedor PIX |
| `e2eId` | string | E2E de la transaccion PIX original |
| `externalId` | string o null | Su identificador externo (si aplicable) |
| `reason` | string | Motivo de la liberacion (ej: `analysis_unfounded`, `manual_release`) |
| `completedAt` | string (ISO 8601) | Fecha/hora de la conclusion (UTC) |

---

## pix.return.received

Enviado cuando una devolucion PIX es recibida. Este evento es generado cuando un PIX que usted **recibio anteriormente** (cash-in) esta siendo devuelto al pagador original. El saldo del merchant **disminuye**.

::: danger Distincion de nomenclatura
- **`pix.return.received`** (este): un PIX que usted **recibio** esta siendo devuelto al pagador original. **Saldo DISMINUYE**.
- **`pix.payout.returned`**: un PIX que usted **envio** esta volviendo a usted. **Saldo AUMENTA**.

Los nombres son inversos a lo que el significado comun sugiere - preste atencion.
:::

```json
{
  "eventType": "pix.return.received",
  "status": "settled",
  "accountId": 10014,
  "amount": 300000,
  "originalAmount": 300000,
  "refundedAmount": 300000,
  "feeAmount": 0,
  "netAmount": 300000,
  "isPartial": false,
  "totalRefunded": 300000,
  "remainingRefundable": 0,
  "entityId": "26a48541-edce-4581-8c6e-564e7f2e6cd7",
  "returnE2eId": "D9040088820260402111500000001",
  "endToEndId": "E9040088820260402095758709999671",
  "originalTransactionId": "PIXINE9040088820260402095758709999671",
  "externalId": null,
  "returnReason": "MD06",
  "returnReasonDescription": "Refund requested by end customer",
  "counterpartyIspb": "60701190",
  "counterpartyName": "EMPRESA X LTDA",
  "counterpartyDocument": "98765432100",
  "counterpartyInstitutionName": "Itau Unibanco S.A.",
  "returnedAt": "2026-04-02T11:15:00Z"
}
```

| Campo | Tipo | Descripcion |
|-------|------|-------------|
| `eventType` | string | Siempre `pix.return.received` |
| `status` | string | Siempre `settled` - devolucion liquidada |
| `amount` | integer | Mismo valor que `refunded_amount` (mantenido para compatibilidad) |
| `originalAmount` | integer | Valor del PIX IN original en subcentavos |
| `refundedAmount` | integer | Valor efectivamente devuelto en esta devolucion (puede ser parcial) |
| `feeAmount` | integer | Tarifa cobrada en esta devolucion |
| `netAmount` | integer | `refunded_amount - fee_amount` |
| `isPartial` | boolean | `true` cuando la devolucion no cubre el valor total del PIX IN original |
| `totalRefunded` | integer | Suma de **todas** las devoluciones ya enviadas para esta transaccion (incluye esta) |
| `remainingRefundable` | integer | Saldo aun pasible de devolucion |
| `returnE2eId` | string | E2E de la devolucion (prefijo `D`) |
| `endToEndId` | string | E2E de la transaccion PIX IN original (prefijo `E`) |
| `originalTransactionId` | string | `transaction_id` del PIX IN original. Use para correlacion con su sistema |
| `externalId` | string o null | Su identificador externo de la transaccion original (si aplicable) |
| `returnReason` | string | Codigo BACEN de la devolucion: `MD06`, `BE08`, `FR01`, `SL02` |
| `returnReasonDescription` | string | Descripcion en ingles del `return_reason` |
| `counterpartyIspb` | string | ISPB de la institucion que esta recibiendo la devolucion |
| `counterpartyName` | string | Nombre de la contraparte (pagador original del PIX IN) |
| `counterpartyDocument` | string o null | CPF/CNPJ de la contraparte |
| `counterpartyInstitutionName` | string o null | Nombre de la institucion contraparte (cache BCB) |
| `returnedAt` | string (ISO 8601) | Momento del dispatch de este webhook (UTC) |

::: tip Deduplicacion
Para deduplicar retries de webhook, use el header `X-Monetarie-Event-Id` O la combinacion `(return_e2e_id, end_to_end_id)`. El `return_e2e_id` comienza con `D` (devolucion) y el `end_to_end_id` comienza con `E` (original).
:::

---

## webhook.test

Evento de prueba disparado manualmente para validar la configuracion del webhook.

```json
{
  "eventType": "webhook.test",
  "status": "test",
  "accountId": 10014,
  "entityId": "26a48541-edce-4581-8c6e-564e7f2e6cd7",
  "message": "Webhook test event"
}
```

---

## pix.infraction.created

Disparado cuando una infraccion PIX es reportada por la contraparte (via BACEN DICT). Use `defense_deadline` para acompanhar el plazo de respuesta.

```json
{
  "eventType": "pix.infraction.created",
  "infractionId": "e7f4d23a-6f2a-4d1e-a3e6-fe8b32bba95d",
  "e2eId": "E0416201020260404113012abcdef1234",
  "status": "ACKNOWLEDGED",
  "infractionType": "REFUND_REQUEST",
  "situation": "SCAM",
  "amount": 1500000,
  "analysisResult": null,
  "analysisDetails": null,
  "creationTime": "2026-04-14T18:00:00Z",
  "defenseDeadline": "2026-04-21T23:59:59Z",
  "counterpartIspb": "60701190",
  "accountId": 10011,
  "merchantId": "1b2db911-972f-4466-9be9-60a7c5450064",
  "entityId": "26a48541-edce-4581-8c6e-564e7f2e6cd7"
}
```

::: tip Disparado solo en la creacion
Este evento se emite **solo** cuando se inserta una infraccion nueva - actualizaciones y re-syncs de la misma infraccion **no** vuelven a emitir `pix.infraction.created`. Para la resolucion, suscribase a [`pix.infraction.resolved`](#pix-infraction-resolved).
:::

| Campo | Tipo | Descripcion |
|-------|------|-------------|
| `eventType` | string | Siempre `pix.infraction.created` |
| `infractionId` | string (UUID) | ID interno de la infraccion |
| `e2eId` | string | E2E de la transaccion impugnada |
| `status` | string | Status BACEN: `ACKNOWLEDGED`, `CLOSED`, `CANCELLED` |
| `infractionType` | string | Tipo BACEN: `REFUND_REQUEST`, `REFUND_CANCELLED`, `FRAUD` |
| `situation` | string \| null | Situacion/tipo de fraude: `SCAM`, `ACCOUNT_TAKEOVER`, `COERCION`, `FRAUDULENT_ACCESS`, `OTHER` |
| `amount` | integer | Valor en subcentavos |
| `creationTime` | string (ISO 8601) \| null | Fecha de apertura de la infraccion |
| `defenseDeadline` | string (ISO 8601) | Plazo para envio de defensa |
| `counterpartIspb` | string (8 digitos) | ISPB de la institucion contraparte |
| `accountId` | integer | Su cuenta afectada |
| `merchantId` | string (UUID) | Su merchant_id |
| `entityId` | string (UUID) | Su entidad |

::: warning Accion necesaria
Infracciones con status `ACKNOWLEDGED` pueden requerir analisis MED. Responda por el portal o por la API externa (`POST /api/external/med/:id/defense`) antes de `defense_deadline` cuando tenga defensa y evidencias.
:::

---

## pix.infraction.resolved

Disparado cuando una infraccion es resuelta. Informa el resultado final y libera el flujo financiero aplicable.

```json
{
  "eventType": "pix.infraction.resolved",
  "infractionId": "e7f4d23a-6f2a-4d1e-a3e6-fe8b32bba95d",
  "e2eId": "E0416201020260404113012abcdef1234",
  "status": "CLOSED",
  "infractionType": "REFUND_REQUEST",
  "amount": 1500000,
  "analysisResult": "DISAGREED",
  "analysisDetails": "Verificado pelo time de compliance e sem evidencias concretas nao temos como fazer devolucao",
  "defenseDeadline": "2026-04-21T23:59:59Z",
  "counterpartIspb": "60701190",
  "accountId": 10011,
  "merchantId": "1b2db911-972f-4466-9be9-60a7c5450064",
  "entityId": "26a48541-edce-4581-8c6e-564e7f2e6cd7"
}
```

| Campo | Tipo | Descripcion |
|-------|------|-------------|
| `analysisResult` | string | `AGREED` (devuelve), `DISAGREED` (niega) |
| `analysisDetails` | string | Justificativa de la decision |
| Demas campos | | Identicos a `pix.infraction.created` |

---

## pix.infraction.defense_submitted

Disparado cuando se registra una defensa contra infraccion/MED por los portales Monetarie o por la API externa.

```json
{
  "eventType": "pix.infraction.defense_submitted",
  "infractionId": "e7f4d23a-6f2a-4d1e-a3e6-fe8b32bba95d",
  "e2eId": "E0416201020260404113012abcdef1234",
  "status": "defense_submitted",
  "accountId": 10011,
  "merchantId": "1b2db911-972f-4466-9be9-60a7c5450064",
  "entityId": "26a48541-edce-4581-8c6e-564e7f2e6cd7"
}
```

| Campo | Tipo | Descripcion |
|-------|------|-------------|
| `eventType` | string | Siempre `pix.infraction.defense_submitted` |
| `status` | string | Siempre `defense_submitted` |
| `infractionId` | string (UUID) | ID de la infraccion siendo defendida |

::: tip Evidencias almacenadas
Los anexos de defensa quedan almacenados en Monetarie para analisis y auditoria. El resultado final llega via `pix.infraction.resolved`.
:::

---

## pix.payout.queued

Disparado cuando PIX OUT es colocado automaticamente en fila de nuevo intento. Motivos comunes: limite operacional por merchant o indisponibilidad temporal de capacidad DICT BACEN.



```json
{
  "eventType": "pix.payout.queued",
  "status": "queued",
  "accountId": 10011,
  "merchantId": "1b2db911-972f-4466-9be9-60a7c5450064",
  "transactionId": "PIXOUT0200806193e0984f830569",
  "endToEndId": "E4602656220260421133012abcdef1234",
  "amount": 200,
  "externalId": "payment-456",
  "reason": "dict_client_rate_limited",
  "reasonCode": "DICT_CLIENT_RATE_LIMITED",
  "reasonDescription": "Merchant exceeded per-minute DICT lookup quota",
  "queuedAt": "2026-04-21T13:30:12Z",
  "estimatedRetrySeconds": 3,
  "queueTtlSeconds": 7200
}
```

| Campo | Tipo | Descripcion |
|-------|------|-------------|
| `eventType` | string | Siempre `pix.payout.queued` |
| `status` | string | Siempre `queued` |
| `accountId` | integer | Cuenta que origino el PIX OUT |
| `merchantId` | string (UUID) | Su merchant_id |
| `transactionId` | string | Identificador de la transaccion Monetarie |
| `endToEndId` | string | E2E BACEN generado para el PIX OUT |
| `amount` | integer | Valor en subcentavos |
| `externalId` | string o null | Su identificador externo (si enviado en el request original) |
| `reason` | string | Motivo del encolamiento (snake_case). Valores conocidos: `dict_client_rate_limited` (limite por merchant), `dict_bucket_exhausted` (bucket DICT BACEN compartido agotado), `dict_rate_limited` (fallback generico) |
| `reasonCode` | string | **Codigo interno en UPPERCASE** correlato al `reason`. Valores: `DICT_CLIENT_RATE_LIMITED`, `DICT_BUCKET_EXHAUSTED`, `DICT_RATE_LIMITED`. No es un codigo BACEN SPI (como `AC03`, `AM02`) - el encolamiento sucede **antes** del envio al BACEN, por eso los codigos son internos de Monetarie |
| `reasonDescription` | string | Descripcion en ingles del motivo |
| `queuedAt` | string (ISO 8601) | Momento en que entro en la fila (UTC) |
| `estimatedRetrySeconds` | integer | Intervalo estimado de nuevo intento; la fila no garantiza ese tiempo y puede demorar si la capacidad externa demora en liberar |
| `queueTtlSeconds` | integer | TTL maximo en la fila en segundos (7200 = 2 h). Despues de expirar, request va para `failed` con motivo `queue_ttl_expired` |

::: warning `reason_code` aqui no es BACEN SPI
Note que en `pix.payout.queued` el `reason_code` es un codigo **interno Monetarie** en UPPERCASE (`DICT_CLIENT_RATE_LIMITED`, etc.). En `pix.payout.failed` el `reason_code` es **codigo BACEN SPI** (ej: `AC03`, `AM02`, `ED05`). Los dos campos tienen el mismo nombre pero vocabularios diferentes - trate cada evento por separado en su consumidor.
:::

::: tip Drenaje automatico
Requests encoladas son reintentadas automaticamente mientras haya TTL. En condiciones normales el procesamiento vuelve cuando el limite por merchant o el bucket DICT BACEN libera capacidad, pero esto **no es SLA de 3-10 min**. Proximo evento: `pix.payout.processing` (cuando salga de la fila y sea enviado al BACEN). En caso que el TTL de 2 h expire sin exito, usted recibe `pix.payout.failed` con `reason="queue_ttl_expired"`.
:::

---

## Como interpretar los webhooks

**Para confirmar que dinero entro en la cuenta**: Aguarde `pix.charge.paid` con `status: "paid"`. Este es el unico evento que garantiza que el valor fue acreditado y la tarifa cobrada.

**Para confirmar que dinero salio de la cuenta**: Aguarde `pix.payout.confirmed` con `status: "settled"`. El status `processing` es intermedio - el saldo esta reservado pero puede ser revertido si rechazado.

**Para devoluciones**: `pix.return.received` con `status: "settled"` confirma devolucion liquidada y acreditada en la cuenta.

**Deduplicacion**: Use el header `X-Monetarie-Event-Id` o el campo `end_to_end_id` como clave de idempotencia.
