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

# Homologación de la integración

> Qué valida OnePay antes de habilitar tu integración de facturas en producción, endpoint por endpoint, y cómo es la corrida integral de cierre.

Antes de habilitar la salida a producción de tu compañía, OnePay revisa tu integración de facturas contra un conjunto de criterios conocidos. Esta página es ese conjunto: si tu integración los cumple, la homologación se cierra sin devoluciones.

El proceso tiene **dos etapas**: primero validamos cada endpoint por separado sobre tu tráfico de sandbox, y después corres la misma secuencia de punta a punta con tu llave de producción.

| Etapa                           | Entorno                 | Qué se valida                                                                                                           | Cómo se cierra                                     |
| ------------------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- |
| **1 · Validación por endpoint** | Sandbox (`sk_test_`)    | Cada endpoint y cada webhook contra sus criterios, de forma aislada                                                     | Sin requisitos incumplidos                         |
| **2 · Corrida integral**        | Producción (`sk_live_`) | La misma secuencia, completa y sobre facturas nuevas: creación → pago → actualización → cancelación con los dos motivos | Los 6 pasos verificados con los `id` que entregues |
| **Salida**                      | Producción              | Salida a producción habilitada, primera factura real acompañada                                                         | Solo con las etapas 1 y 2 cerradas                 |

<Note>
  **En ninguna de las dos etapas trabajas contra un guion nuestro.** La etapa 1 no te pide preparar nada: OnePay la hace sobre el tráfico que ya generaste en sandbox, revisando tus llamadas reales.

  La etapa 2 la corres **por tu cuenta**, con tu llave de producción, cuando estés listo. Al terminar nos entregas los `id` y nosotros analizamos el flujo de peticiones y el del pago del lado de OnePay. No hace falta agendar nada ni esperar a que estemos conectados.
</Note>

En las tablas de criterios distinguimos dos tipos:

* **Requisito** — sin esto no se homologa.
* **Recomendación** — no bloquea la homologación, pero es lo que hace que la integración aguante en producción.

***

## Prerrequisitos

| Tipo      | Criterio                                                                                                                                               |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Requisito | La etapa 1 se hace en sandbox, con una llave `sk_test_`; la etapa 2, en producción, con la `sk_live_`.                                                 |
| Requisito | Guardas el `id` que devuelve `POST /v1/invoices` como `external_id` (o equivalente) de tu factura. Es la **única** llave para `PUT`, `GET` y `DELETE`. |

<Note>
  **Tu llave `sk_live_` la generas tú desde el panel de administración.** La homologación no te la retiene: la necesitas para correr la etapa 2.

  Lo que sí depende de este proceso es la **salida a producción de la compañía**: OnePay la habilita cuando la homologación queda aprobada, así que conviene cerrarla antes de planear la fecha de salida.
</Note>

***

## Etapa 1 · Validación por endpoint

### Crear factura — `POST /v1/invoices`

| Tipo          | Criterio                                                                                                                                                  |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Requisito     | La creación responde `201` y tu sistema lee y persiste `id`, `payment_id` y `payment.payment_link`.                                                       |
| Requisito     | `provider_id` es el número de la factura: **único por documento y en el tiempo**, entre 5 y 100 caracteres.                                               |
| Requisito     | `reference` identifica al **cliente** (cédula o contrato), se repite entre ciclos y es lo que la persona escribe en el portal de pagos para ver su deuda. |
| Requisito     | Toda factura lleva `phone` en formato E.164 y/o un `email` válido.                                                                                        |
| Requisito     | `due_date` va en formato `YYYY-MM-DD` **completo**.                                                                                                       |
| Requisito     | `amount`, `name` y `description` dentro de sus rangos válidos. Los `422` recurrentes por datos de origen se corrigen **antes** de reenviar.               |
| Recomendación | Los reintentos se hacen con **el mismo cuerpo**.                                                                                                          |

<Warning>
  **`provider_id` debe ser único por documento y en el tiempo.** Cada factura lleva el suyo y no se reutiliza nunca: ni en el siguiente ciclo, ni para el mismo cliente, ni para el mismo contrato. Para identificar al cliente el campo es `reference`.

  Reenviar un `provider_id` ya usado no crea la factura nueva. Mientras la anterior siga en estado `CREATED`, OnePay la reconoce como el mismo documento y responde `201` con **esa** factura: el cobro nuevo no se genera y el cliente sigue viendo el monto anterior. La respuesta es exitosa, así que tu sistema no se entera.
</Warning>

<Warning>
  **Un `due_date` de año-mes nace vencido.** Un valor como `"2026-05"` es aceptado por la validación y se guarda como el **día 1** de ese mes: la factura nace vencida y las reglas de cobranza disparan mora desde el primer día.

  Si manejas una fecha de pago oportuno distinta del vencimiento, va en `timely_payment_date`, no en `due_date`.
</Warning>

<Warning>
  **Sin datos de contacto no hay cobro.** Una factura sin `phone` ni `email` se crea igual y responde `201`, pero el cobro no sale por ningún canal: ni WhatsApp ni correo. El cliente nunca se entera de que tiene algo que pagar.
</Warning>

<Info>
  **Por qué el reintento va con el mismo cuerpo:** si repites el `POST` con un cuerpo idéntico, la deduplicación te devuelve el mismo `id` y no se duplica el cobro. Un reintento con datos cambiados no es un reintento — crea un cobro nuevo.
</Info>

### Actualizar factura — `PUT /v1/invoices/{id}`

| Tipo      | Criterio                                                                                                                                          |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| Requisito | El `PUT` envía `reference`, `amount` y `name`, y tu sistema procesa el `200`: trae un `payment_id` **nuevo** y el cobro anterior queda cancelado. |

<Info>
  **El contacto se hereda.** Si el `PUT` no trae `phone` ni `email`, el cobro de reemplazo conserva los del cobro anterior. Enviarlos sirve para **actualizarlos**; no es obligatorio para que el cobro llegue.
</Info>

### Cancelar factura — `DELETE /v1/invoices/{id}`

Los dos motivos de cancelación no son intercambiables: cada uno corresponde a una situación real distinta.

| Tipo          | Criterio                                                                                                                |
| ------------- | ----------------------------------------------------------------------------------------------------------------------- |
| Requisito     | Factura pagada en la caja del comercio → `reason: PAID_FROM_PROVIDER` con `provider_payment_id` **y** `payment_method`. |
| Requisito     | Factura anulada en tu sistema → `reason: DELETE_FROM_PROVIDER` en el endpoint **individual**.                           |
| Recomendación | El `409` se trata como estado final ("ya fue pagada en OnePay"), sin reintentar.                                        |
| Recomendación | El `404` por desajuste de ambiente se trata como error de configuración, no como fallo transitorio.                     |

<Info>
  **Para qué sirve `payment_method`:** es el medio con el que el cliente pagó por fuera de OnePay. Texto libre de hasta 60 caracteres — `Efectivo`, `Nequi`, `Consignación Bancolombia`. Sin él, OnePay sabe que la factura se pagó afuera pero no sabe cómo.
</Info>

<Warning>
  **El endpoint masivo no es para el flujo diario.** [`DELETE /v1/invoices`](/client/invoices/bulk-delete) borra **todas** las facturas pendientes de la empresa. Es una herramienta de limpieza puntual y no debe quedar cableada al flujo de anulación de tu sistema de gestión.
</Warning>

<Note>
  El `404` de desajuste de ambiente aparece cuando consultas una factura de sandbox con la llave de producción o al revés. Es terminal: reintentarlo no cambia nada, hay que revisar qué llave está usando el proceso.
</Note>

### Webhooks

| Tipo          | Criterio                                                                                               |
| ------------- | ------------------------------------------------------------------------------------------------------ |
| Requisito     | Tu webhook está suscrito **como mínimo** a `invoice.paid`, con `secret` y token estático configurados. |
| Requisito     | Los eventos a los que estás suscrito reciben `2xx`. Ninguno puede quedar agotado.                      |
| Recomendación | Validas el header `x-webhook-token` y la firma `Signature`.                                            |
| Recomendación | Procesas de forma idempotente por `invoice.id` + tipo de evento.                                       |
| Recomendación | Al rotar el webhook, aceptas el token viejo y el nuevo durante la transición.                          |
| Recomendación | Usas `GET /v1/invoices/{id}` como respaldo de conciliación si se pierde un `invoice.paid`.             |

<Note>
  **Por qué `invoice.paid` es el mínimo:** es el evento que cierra el ciclo de cobro. Sin él no te enteras de que una factura se pagó en OnePay y tu sistema queda desincronizado.
</Note>

<Warning>
  **Un evento agotado se pierde.** OnePay entrega cada evento en **6 intentos como máximo**, con un backoff que se agota en poco menos de 10 minutos. Después del sexto intento fallido el evento deja de reintentarse por esa vía. Ver [Entrega y reintentos](/client/webhooks/index#entrega-y-reintentos) para el calendario completo.
</Warning>

<Info>
  **La firma se calcula sobre el cuerpo crudo.** `Signature` es un HMAC-SHA256 sobre los bytes tal como llegaron. Deserializar el JSON y volver a serializarlo antes de calcular el HMAC cambia el cuerpo y rompe la firma, aunque el contenido sea equivalente. Ver [Verificar la firma](/client/webhooks/index).
</Info>

<Info>
  **Por qué la idempotencia importa:** los reintentos pueden entregarte el mismo evento dos veces. Sin idempotencia por `invoice.id` + tipo de evento, un reintento se convierte en un pago registrado dos veces en tu sistema.
</Info>

***

## Etapa 2 · Corrida integral

Es la misma secuencia de la etapa 1, pero ejecutada de punta a punta **con tu llave `sk_live_`** y sobre facturas **nuevas**. Al correr en producción son facturas reales de tus clientes: el dinero se mueve y los mensajes salen normalmente. Al terminar nos entregas el `id` de OnePay de cada paso, y nosotros revisamos el flujo de peticiones y el del pago para confirmar que cada resultado esperado se dio.

<Steps>
  <Step title="Crear la factura, dos veces con el mismo cuerpo">
    `POST /v1/invoices` con el payload completo, enviado **dos veces con el mismo cuerpo**. Usa un monto bajo: esta factura se paga de verdad en el paso 2.

    **Esperado:** ambas respuestas `201` y con **el mismo** `id`. Si el segundo `201` trae un `id` distinto, hay un cobro duplicado.
  </Step>

  <Step title="Pagar la factura">
    Paga el `payment.payment_link`. Es un **pago real**: el dinero se mueve y se liquida como cualquier otro cobro, con su comisión.

    **Esperado:** llega `invoice.paid` con el pago aprobado, la factura queda en `CONCILIATED`, y en tu sistema queda marcada como pagada. Este último punto se verifica con evidencia de tu lado: es el paso que demuestra que el webhook no solo llega, sino que cierra el ciclo.
  </Step>

  <Step title="Actualizar otra factura">
    `PUT /v1/invoices/{id}` cambiando el monto de **otra** factura.

    **Esperado:** `200` con un `payment_id` nuevo. El cobro conserva el teléfono de la factura original y llega por WhatsApp.
  </Step>

  <Step title="Cancelar por pago externo">
    `DELETE /v1/invoices/{id}` con `reason: PAID_FROM_PROVIDER`, `provider_payment_id` y `payment_method`.

    **Esperado:** `204`. La factura **sobrevive** en estado `PAID_FROM_PROVIDER` con el método registrado — no se borra. Si la factura tiene teléfono, el cliente recibe el recibo en PDF por WhatsApp.
  </Step>

  <Step title="Cancelar por anulación">
    `DELETE /v1/invoices/{id}` con `reason: DELETE_FROM_PROVIDER` sobre **otra** factura.

    **Esperado:** `204`. La factura se elimina.
  </Step>

  <Step title="Intentar cancelar la factura ya pagada">
    `DELETE /v1/invoices/{id}` sobre la factura que pagaste en el paso 2.

    **Esperado:** `409`, registrado en tu sistema como "ya pagada en OnePay" y **sin reintentos**.
  </Step>
</Steps>

<Note>
  Los pasos 4 y 5 son los que más seguido revelan un problema: son el mismo endpoint con dos comportamientos distintos, y una integración que trata los dos motivos por igual falla ahí.
</Note>

***

## Salida a producción

Con las etapas 1 y 2 cerradas:

1. **Salida a producción habilitada** por OnePay, y tu webhook de producción configurado con `secret` y los mismos eventos que homologaste.
2. **Primera factura real acompañada** de punta a punta: creación, envío, pago y conciliación, con OnePay presente.
3. **Retiro de las facturas de prueba** que hayan quedado en sandbox.
4. **Monitoreo de las primeras 72 horas**: entregas de webhook, tasa de `422` y cobros duplicados.
