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

# Pago de una orden

> El resultado del cargo de una orden de pago: confirmado, rechazado o revertido.

Cuando creas una [orden de pago](/es/recursos/ordenes-de-pago.md), el cargo a la cuenta del usuario se procesa después, en el lote que TapiPay envía al banco. El resultado te llega con este webhook: uno por cada movimiento del cargo.

| `type` | `paymentStatus` | Cuándo llega | Estado de la orden |
| - | - | - | - |
| `ORDER_PAYMENT_CONFIRMED` | `CONFIRMED` | El banco confirmó el cargo: el dinero ingresó. | `PAID` |
| `ORDER_PAYMENT_FAILED` | `FAILED` | El banco rechazó el cargo o el cargo falló. No hubo cobro. | `FAILED` |
| `ORDER_PAYMENT_CHARGEBACK` | `CHARGED_BACK` | Un cargo cobrado se revirtió. Puede llegar aunque no hayas recibido la confirmación. | `CHARGED_BACK` |

> [!NOTE]
>
> Este webhook tiene **su propio contrato**, distinto del de los eventos de débito automático: no trae `operationId` ni `companyName`, y su llave de idempotencia es `id`. Lo que se explica en [Campos comunes](/es/webhooks.md#campos-comunes) no aplica acá, salvo el `hash`.

## Dos avisos por una orden confirmada

`ORDER_PAYMENT_CONFIRMED` informa el **cobro**, no el pago de la deuda. Después de confirmarlo, TapiPay aplica el pago a la deuda de la orden y te envía además la [notificación de pago](/es/webhooks/notificacion-de-pago.md) (`type: SERVICE`, `status: confirmed`). Así, una orden confirmada te genera dos avisos, que pueden llegar a URLs distintas.

## Configuración

La URL de estos eventos se define en el onboarding y puede ser distinta de la de los demás webhooks. Si no configuraste una propia para órdenes, llegan a tu URL general. Los mecanismos de seguridad son los mismos que en el resto de los webhooks: ver [Validar que la petición viene de TapiPay](/es/webhooks/notificacion-de-pago.md#validar-que-la-petición-viene-de-tapipay).

`POST` con `Content-Type: application/json`. El header `notificationType` trae el mismo valor que `type`, para que puedas enrutar sin leer el cuerpo.

## El payload

| Campo | Tipo | Presente | Descripción |
| - | - | - | - |
| `type` | String | Siempre | `ORDER_PAYMENT_CONFIRMED`, `ORDER_PAYMENT_FAILED` u `ORDER_PAYMENT_CHARGEBACK`. Úsalo para enrutar. |
| `hash` | String \| null | Siempre | Hash de verificación. `null` si no tienes llave pública configurada. |
| `id` | String (uuid) | Siempre | Identificador único de la notificación. Si te llega repetida, trae el mismo `id`. **Úsalo como llave de idempotencia.** |
| `orderId` | String (uuid) | Siempre | El `orderId` que te devolvió la creación de la orden. |
| `externalOrderId` | String | Siempre | El `externalRequestId` que enviaste al crear la orden. |
| `paymentStatus` | String | Siempre | `CONFIRMED`, `FAILED` o `CHARGED_BACK`. |
| `companyCode` | String | Siempre | Código de la empresa de la orden. |
| `externalClientId` | String | Siempre | Tu identificador del usuario. |
| `amount` | Number | Siempre | Monto de la orden, en pesos con hasta 2 decimales. |
| `currency` | String | Siempre | Moneda, en ISO 4217. Por ahora `MXN`. |
| `paymentProcessor` | String | Siempre | Por ahora `BANK_ACCOUNT`. |
| `paymentMethod` | Object | Siempre | La cuenta con la que se cobró: `{ type, externalPaymentMethodId }`. Nunca trae la cuenta ni la CLABE. |
| `paymentProviderError` | Object | Solo `ORDER_PAYMENT_FAILED` | Motivo del rechazo: `{ code, description }`. |
| `chargeback` | Object | Solo `ORDER_PAYMENT_CHARGEBACK` | Motivo del contracargo: `{ code, description }`. |
| `additionalData` | Object | Siempre | Por ahora siempre llega vacío (`{}`). |
| `paymentAt` | String | Siempre | Cuándo TapiPay registró el resultado que informó el banco, en ISO 8601 UTC. No es la hora exacta del movimiento en el banco. |

> [!WARNING]
>
> **En `paymentMethod`, el tipo de cuenta va en `type`.** En la respuesta de la orden y en [`GET /payment-methods`](/es/recursos/debito-automatico#consultar-los-medios-de-pago-de-un-usuario) el mismo dato se llama `paymentMethodType`. `externalPaymentMethodId` es el mismo valor en los tres lugares.

> [!NOTE]
>
> Por ahora `code` y `description` llegan siempre en `null` dentro de `paymentProviderError` y `chargeback`. El objeto sí viene siempre en su evento: léelo sin verificar que exista, pero contempla el `null`.

## El hash

Si entregaste una llave pública RSA en el onboarding, `hash` viene cifrado con ella. Descífralo con tu llave privada: contiene tu usuario de integración o, en la versión `v2` de la seguridad, `{ "clientUsername", "operationId" }`. En `v2`, comprueba que `operationId` coincida con el `id` del cuerpo.

## Ejemplos

Una orden que se cobra y después se revierte, y otra cuyo cargo se rechaza. Los ejemplos corresponden a una integración sin llave pública, por eso `hash` viene en `null`.

```json Confirmado theme={null}
{
  "type": "ORDER_PAYMENT_CONFIRMED",
  "hash": null,
  "id": "5c0f7a3d-2b8e-5d14-9a6c-8e1f3b7d2a90",
  "orderId": "6f1c2b9e-4d3a-4b8e-9a71-2c5d8e0f1a23",
  "externalOrderId": "CARGO-2026-00042",
  "paymentStatus": "CONFIRMED",
  "companyCode": "MX-S-12345",
  "externalClientId": "CLI-00042",
  "amount": 350.00,
  "currency": "MXN",
  "paymentProcessor": "BANK_ACCOUNT",
  "paymentMethod": { "type": "BANK_ACCOUNT", "externalPaymentMethodId": "b7e4a0c2-9f13-4e6d-8a25-71c3d9e0b4f8" },
  "additionalData": {},
  "paymentAt": "2026-10-23T15:02:17.000Z"
}
```

```json Rechazado theme={null}
{
  "type": "ORDER_PAYMENT_FAILED",
  "hash": null,
  "id": "e7a2c94b-1d3f-5b68-8c2e-4f9a6d1b3c75",
  "orderId": "4e7b1a90-2c6d-4f3e-8a15-6b9d0c2e7f48",
  "externalOrderId": "CARGO-2026-00043",
  "paymentStatus": "FAILED",
  "companyCode": "MX-S-12345",
  "externalClientId": "CLI-00043",
  "amount": 980.00,
  "currency": "MXN",
  "paymentProcessor": "BANK_ACCOUNT",
  "paymentMethod": { "type": "BANK_ACCOUNT", "externalPaymentMethodId": "7d2c9e14-5a3b-4f86-b1e0-9c4a8f6d2b53" },
  "paymentProviderError": { "code": null, "description": null },
  "additionalData": {},
  "paymentAt": "2026-10-23T15:05:42.000Z"
}
```

```json Contracargo theme={null}
{
  "type": "ORDER_PAYMENT_CHARGEBACK",
  "hash": null,
  "id": "a41d8e6f-3c2b-5e97-b0d4-7c5f2e8a1b36",
  "orderId": "6f1c2b9e-4d3a-4b8e-9a71-2c5d8e0f1a23",
  "externalOrderId": "CARGO-2026-00042",
  "paymentStatus": "CHARGED_BACK",
  "companyCode": "MX-S-12345",
  "externalClientId": "CLI-00042",
  "amount": 350.00,
  "currency": "MXN",
  "paymentProcessor": "BANK_ACCOUNT",
  "paymentMethod": { "type": "BANK_ACCOUNT", "externalPaymentMethodId": "b7e4a0c2-9f13-4e6d-8a25-71c3d9e0b4f8" },
  "chargeback": { "code": null, "description": null },
  "additionalData": {},
  "paymentAt": "2026-11-04T16:00:00.000Z"
}
```

## Recomendaciones

* **Procesa cada notificación una sola vez.** TapiPay reintenta si tu endpoint no responde 2xx a tiempo, así que la misma notificación puede llegar más de una vez, siempre con el mismo `id`. Guarda los `id` procesados en almacenamiento persistente.
* **No asumas un orden de llegada.** Usa `paymentAt` para ordenar los movimientos de una misma orden.
* **Correlaciona por `orderId` o `externalOrderId`.**
* **Ignora los campos que no conozcas**: se pueden agregar campos nuevos.
* **Si no te llega un webhook**, consulta la orden con [`GET /v2/debts/orders`](/es/recursos/ordenes-de-pago#consultar-una-orden).

Los requisitos de tu endpoint (responder 2xx en menos de 5 segundos y ser alcanzable desde las IPs de TapiPay) son los mismos que en el resto de los webhooks: ver [Requisitos de tu endpoint](/es/webhooks/notificacion-de-pago.md#requisitos-de-tu-endpoint).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.