Mono Colombia
Bre-B ParticipantArquitectura

Ciclo de vida de la transferencia saliente

Máquina de estados para transferencias salientes de Bre-B — transiciones, webhooks y estados terminales

Una transferencia saliente (un pago saliendo de tu cuenta por Bre-B) se mueve por una secuencia determinística de estados. Cada transición mapea a un evento de webhook, así que tu integración puede rastrear el progreso sin polling. Esta guía explica qué significa cada estado, qué hace que una transferencia se mueva entre ellos y cómo distinguir el éxito terminal de la falla terminal.

Diagrama de estados

Estados de un vistazo

Recorrido detallado

1. created

Estado inicial. Una transferencia entra a este estado tan pronto como create outgoing transfers la acepta en un batch. La fila de la transferencia existe en nuestro sistema y tiene un id que puedes usar para búsquedas.

Las transferencias rechazadas nunca llegan a este estado

Si el request falla la validación previa al insert — por ejemplo, el monto supera el límite máximo, o el target es inválido — la transferencia se devuelve en el arreglo rejected_transfers de la respuesta del batch y no se persiste. Nunca pasará por esta máquina de estados y no se disparará ningún webhook. Ver Códigos de error de transferencia saliente para la lista de motivos de rechazo.

Qué pasa después: un worker en background toma la transferencia y la mueve a processing. Esto típicamente ocurre en segundos.

2. processing

El sistema está procesando activamente la transferencia. El camino que toma depende de cómo se proveyó el target:

  • Transferencias por plain-key (proveíste una llave Bre-B en query): el sistema primero debe resolver la llave a un destinatario antes de poder retener fondos.
  • Target pre-resuelto (proveíste un target_id existente): la resolución se salta y la transferencia va directo al hold de fondos.

Siguientes estados:

  • target_resolved si la resolución tiene éxito (camino plain-key) o el target ya estaba resuelto.
  • failed si la resolución da error — por ejemplo, key_not_found, key_suspended o invalid_key_format.

3. target_resolved

La llave Bre-B del destinatario fue resuelta a un creditor concreto y una cuenta bancaria. Si proveíste un expected_creditor, ahí es donde validamos que el creditor resuelto coincida. Esta es la última oportunidad de rechazar la transferencia antes de que los fondos se muevan.

Siguientes estados:

  • held una vez que los fondos están retenidos en la cuenta origen.
  • failed con state_reason = target_creditor_mismatch si el documento del creditor resuelto no coincide con el expected_creditor que enviaste. Este es el resguardo que evita pagar a la persona equivocada cuando una llave se reasigna.

4. held

Fondos equivalentes al monto de la transferencia están reservados en tu cuenta origen. El dinero salió de tu saldo disponible pero todavía no fue enviado al banco del destinatario. En este punto la transferencia está totalmente comprometida desde tu lado.

Siguientes estados:

  • sent_to_breb_provider una vez que despachamos la instrucción de pago al proveedor Bre-B (Credibanco).
  • failed si el hold mismo falla (raro en este punto — la mayoría de errores de fondos emergen antes). state_reason llevará la causa (e.g., insufficient_funds).

5. sent_to_breb_provider

El pago fue entregado al proveedor Bre-B para settlement. Ahora está en vuelo a través de la red Bre-B. La transferencia está esperando el callback final de settlement del proveedor.

Este estado puede mantenerse por un rato

La mayoría de transferencias settlea en segundos, pero timeouts del proveedor y la disponibilidad de los bancos aguas abajo pueden estirar esto a minutos. No asumas que una transferencia falló solo porque no salió rápido de este estado — espera el webhook terminal.

Siguientes estados:

  • successful con un callback de settlement positivo.
  • failed con un callback de settlement negativo (e.g., breb_timeout, provider_unavailable, risk_control o un fallback unknown).

6. successful (terminal)

Los fondos fueron acreditados al destinatario. No hay más transiciones; no se disparan más webhooks para esta transferencia.

7. failed (terminal)

La transferencia no se completó. El campo state_reason en la transferencia (y en el webhook outgoing_transfer.failed) te dice por qué. Dependiendo del motivo, el error puede ser reintentable enviando una nueva transferencia — ver la guía de reintentos en Códigos de error de transferencia saliente.

Failed no es lo mismo que rejected

  • Las transferencias rechazadas se devuelven de forma síncrona en rejected_transfers cuando creas un batch. Nunca se convierten en transferencias en nuestro sistema. - Las transferencias fallidas fueron aceptadas, persistidas, pasaron por al menos created y luego golpearon un error. Tienen un state_reason y emiten un webhook.

Trabajando con estados en tu integración

  1. Trata cada estado no terminal como transitorio. Una transferencia en held o sent_to_breb_provider sigue en vuelo — muéstrala como "en proceso" en tu UI en vez de "completa".
  2. Trabaja con webhooks, no con polling. Cada transición de estado emite un webhook con el nuevo estado y, en caso de falla, un state_reason. Verifica cada webhook (ver Verificación de firma de webhook) y actualiza tu registro local de forma idempotente — el mismo estado puede ser reintentado por nuestros workers.
  3. Maneja entrega fuera de orden. Las entregas de webhook son at-least-once. Si ves successful antes que sent_to_breb_provider, conserva el estado más reciente e ignora el anterior. Una regla simple: si la transferencia ya está en un estado terminal localmente, ignora los webhooks no terminales.
  4. Solo dos estados son finales. successful y failed son los únicos estados terminales. Una vez que una transferencia alcanza cualquiera, nunca volverá a moverse.

En esta página