Mono Colombia
BankingArquitectura

Ciclo de vida de transferencia bancaria

Máquina de estados para transferencias bancarias y lotes — transiciones, webhooks y estados terminales.

Una transferencia bancaria es una dispersión: el dinero sale de tu cuenta Mono y llega a la cuenta bancaria colombiana de un beneficiario por uno de tres rails — ACH, Transfiya o Mono Turbo. Cada transferencia que creas vía POST /transfers se agrupa automáticamente en un lote, y tanto la transferencia como el lote se mueven por sus propias máquinas de estados en paralelo. Esta página explica qué significa cada estado, qué causa cada transición y qué evento de webhook se dispara en cada paso.

Entender ambas máquinas de estados importa porque tu handler de webhooks recibirá eventos del lote (¿fue autorizado? ¿enviado?) y eventos de la transferencia (¿fue aprobada o rechazada la transferencia individual?) en un flujo intercalado.

Máquina de estados de la transferencia

Estados de la transferencia de un vistazo

Estado¿Terminal?Evento de webhook
createdNo— (respuesta síncrona de la API)
in_progressNobank_transfer_fallback_routing se dispara si ocurre un reintento de routing
approvedbank_transfer_approved
declinedbank_transfer_rejected
cancelled— (iniciado por el usuario antes del despacho; sin evento asíncrono)
duplicated— (se expone vía batch_duplicated a nivel del lote)

Los estados finales pueden cambiar

El evento bank_transfer_change_final_state se dispara en el caso raro en que una transferencia ya en approved o declined es movida a un estado final distinto por el rail. Maneja siempre este evento de forma idempotente — actualiza tu registro local al nuevo estado incluso si ya lo considerabas settled.

Máquina de estados del lote

Cada llamada a POST /transfers crea un lote, incluso cuando envías una sola transferencia. El lote es la unidad que pasa por la autorización del admin (OTP) si tu cuenta lo requiere.

Estados del lote de un vistazo

Estado¿Terminal?Evento de webhook
createdNo— (respuesta síncrona de la API)
pending_otpNobatch_authorization_requested
verified_otpNo— (interno; transiciona inmediatamente a processing_transactions)
processing_transactionsNobatch_sent
approved— (las transferencias individuales emiten bank_transfer_approved)
partially_approved— (mezcla de bank_transfer_approved y bank_transfer_rejected por transferencia)
declined— (las transferencias individuales emiten bank_transfer_rejected)
duplicatedbatch_duplicated
canceledbatch_canceled

Recorrido detallado

Transferencia: created

Estado inicial. La fila de la transferencia existe en el sistema con un id que puedes usar para consultas. Mono aceptó la solicitud pero aún no la ha enviado al rail. Cada transferencia del lote entra a este estado al mismo tiempo.

Qué pasa después: una vez que el lote pasa cualquier gate de OTP y llega a processing_transactions, cada transferencia pasa a in_progress.

Transferencia: in_progress

La transferencia fue despachada al rail seleccionado — ACH, Mono Turbo o Transfiya. Los fondos están en tránsito; el saldo de la cuenta origen fue debitado pero el destino aún no ha confirmado el crédito.

Si el rail primario rechaza el intento y configuraste un fallback_routing, Mono reintenta automáticamente. Esto dispara bank_transfer_fallback_routing sin cambiar el estado de la transferencia — la transferencia se queda en in_progress durante el reintento.

Estados siguientes:

  • approved cuando el rail confirma el crédito.
  • declined cuando el rail rechaza y ningún fallback tiene éxito.

Transferencia: approved (terminal)

El banco destino confirmó el crédito. Los fondos están en la cuenta del beneficiario. El webhook bank_transfer_approved se dispara con el routing final y el monto. No ocurren más transiciones bajo circunstancias normales.

Transferencia: declined (terminal)

La transferencia fue rechazada. El campo declination_reason en el payload del webhook lleva la razón del rechazo (por ejemplo, insufficient_funds o invalid_account_information). El webhook bank_transfer_rejected se dispara. No ocurren más transiciones bajo circunstancias normales.

Transferencia: cancelled (terminal)

La transferencia fue cancelada por un usuario antes de que el lote llegara a processing_transactions. Cancelar un lote cancela todas sus transferencias. No se dispara un webhook asíncrono para las transferencias individuales — el lote emite batch_canceled.

Transferencia: duplicated (terminal)

El entity_id de esta transferencia ya se usó en un lote anterior. La transferencia no se procesa. El lote emite batch_duplicated.


Lote: created

La fila del lote existe en el sistema. Todas sus transferencias están en created. Este estado es síncrono — obtienes el id del lote en la respuesta de la API.

Qué pasa después: Mono chequea si el lote requiere autorización OTP.

Lote: pending_otp

El lote requiere autorización explícita de un usuario Administrador antes de poder ser procesado. El webhook batch_authorization_requested se dispara. Ninguna transferencia se mueve hasta que un admin autorice.

Estados siguientes:

  • verified_otp cuando un admin completa la autorización.
  • canceled cuando un admin cancela.

Lote: verified_otp

El admin autorizó. Mono prepara el lote para el despacho. Este estado es transitorio — pasa a processing_transactions en segundos y no se dispara webhook para verified_otp.

Lote: processing_transactions

El lote está siendo despachado. Cada transferencia pasa a in_progress. El webhook batch_sent se dispara. Desde este punto, los webhooks individuales de transferencia (bank_transfer_approved / bank_transfer_rejected) guían la conciliación.

Lote: approved (terminal)

Todas las transferencias del lote fueron aprobadas. Los eventos individuales bank_transfer_approved ya se dispararon para cada transferencia.

Lote: partially_approved (terminal)

Algunas transferencias fueron aprobadas y otras rechazadas. Cada transferencia ya emitió su propio evento bank_transfer_approved o bank_transfer_rejected. Concilia transferencia por transferencia usando los eventos individuales.

Lote: declined (terminal)

Todas las transferencias del lote fueron rechazadas. Los eventos individuales bank_transfer_rejected ya se dispararon.

Lote: duplicated (terminal)

Cada transferencia del lote tenía un entity_id que ya existía. Ninguna transferencia se procesa. batch_duplicated se dispara. Las transferencias individuales pasan a duplicated.

Lote: canceled (terminal)

Un admin canceló el lote antes de que llegara a processing_transactions. Todas las transferencias pasan a cancelled. batch_canceled se dispara.

Cómo trabajar con estas máquinas de estados en tu integración

  1. Apóyate en webhooks, no en polling. Cada transición significativa emite un webhook. Suscríbete a eventos de transferencia y de lote — llevan información distinta y llegan en orden intercalado.

  2. Maneja entregas fuera de orden. Las entregas de webhooks son at-least-once y pueden llegar fuera de orden. Si recibes bank_transfer_approved antes que batch_sent, conserva el estado más reciente e ignora el anterior. Regla segura: si una transferencia ya está en estado terminal localmente, ignora los webhooks no terminales para ella.

  3. Diseña para el gate de OTP. Si tu cuenta Mono requiere autorización de lote, tu lote se quedará en pending_otp hasta que un admin actúe. Planea tu UX alrededor de esto: expón el estado del lote a los operadores para que sepan cuándo se requiere acción.

  4. Distingue el estado de la transferencia del estado del lote. Un lote en partially_approved no te dice qué transferencias tuvieron éxito — debes inspeccionar los eventos individuales de transferencia. Concilia transferencia por transferencia, no lote por lote.

  5. Maneja bank_transfer_change_final_state. Es raro, pero una transferencia ya en approved o declined puede pasar a un estado final distinto. Actualiza tu registro local cuando llegue este evento; no trates un estado terminal como inmutable hasta que dejes de recibir eventos para esa transferencia.

  6. Usa entity_id para idempotencia. Enviar el mismo entity_id dos veces resulta en duplicated. Esta es tu red de seguridad contra el doble envío — pero también significa que debes generar un entity_id nuevo para cada transferencia genuinamente nueva.

Siguientes pasos

En esta página