Mono Colombia

Outgoing Transfer Lifecycle

State machine for outgoing Bre-B transfers — transitions, webhooks, and terminal states

An outgoing transfer (a payout leaving your account over Bre-B) moves through a deterministic sequence of states. Each transition maps to a webhook event, so your integration can track progress without polling. This guide explains what each state means, what makes a transfer move between them, and how to tell terminal success from terminal failure.

State diagram

States at a glance

Detailed walkthrough

1. created

Initial state. A transfer enters this state as soon as create outgoing transfers accepts it into a batch. The transfer row exists in our system and has an id you can use for lookups.

Rejected transfers never reach this state

If the request fails pre-insert validation — for example, the amount exceeds the max limit, or the target is invalid — the transfer is returned in the rejected_transfers array of the batch response and is not persisted. It will never move through this state machine and no webhook will fire. See Outgoing Transfer Error Codes for the list of rejection reasons.

What happens next: a background worker picks up the transfer and moves it to processing. This typically happens within seconds.

2. processing

The system is actively working on the transfer. The path it takes depends on how the target was supplied:

  • Plain-key transfers (you provided a Bre-B key in query): the system must first resolve the key to a recipient before it can hold funds.
  • Pre-resolved target (you supplied an existing target_id): resolution is skipped and the transfer goes straight to funds-holding.

Next states:

  • target_resolved if resolution succeeds (plain-key path) or the target was already resolved.
  • failed if resolution errors out — for example, key_not_found, key_suspended, or invalid_key_format.

3. target_resolved

The recipient's Bre-B key has been resolved to a concrete creditor and bank account. If you supplied an expected_creditor, that's where we check the resolved creditor matches. This is the last chance to reject the transfer before funds move.

Next states:

  • held once funds are held on the source account.
  • failed with state_reason = target_creditor_mismatch if the resolved creditor's document doesn't match the expected_creditor you sent. This is the safeguard that prevents paying the wrong person when a key is reassigned.

4. held

Funds equal to the transfer amount are reserved on your source account. The money has left your available balance but has not yet been sent to the recipient's bank. At this point the transfer is fully committed from your side.

Next states:

  • sent_to_breb_provider once we dispatch the payment instruction to the Bre-B provider (Credibanco).
  • failed if the hold itself fails (rare at this point — most funds errors surface earlier). state_reason will carry the cause (e.g., insufficient_funds).

5. sent_to_breb_provider

The payment has been handed off to the Bre-B provider for settlement. It is now in flight across the Bre-B network. The transfer is awaiting the final settlement callback from the provider.

This state can sit for a while

Most transfers settle within a few seconds, but provider timeouts and downstream bank availability can stretch this to minutes. Don't assume a transfer has failed just because it hasn't left this state quickly — wait for the terminal webhook.

Next states:

  • successful on a positive settlement callback.
  • failed on a negative settlement callback (e.g., breb_timeout, provider_unavailable, risk_control, or an unknown fallback).

6. successful (terminal)

The funds have been credited to the recipient. No further transitions; no more webhooks will fire for this transfer.

7. failed (terminal)

The transfer did not complete. The state_reason field on the transfer (and on the outgoing_transfer.failed webhook) tells you why. Depending on the reason, the error may be retryable by submitting a new transfer — see the retry guidance in Outgoing Transfer Error Codes.

Failed is not the same as rejected

  • Rejected transfers are returned synchronously in rejected_transfers when you create a batch. They never become transfers in our system. - Failed transfers were accepted, persisted, moved through at least created, and then hit an error. They have a state_reason and emit a webhook.

Working with states in your integration

  1. Treat every non-terminal state as transient. A transfer in held or sent_to_breb_provider is still in flight — surface it as "processing" in your UI rather than "complete."
  2. Key off webhooks, not polling. Every state transition emits a webhook with the new state and, on failure, a state_reason. Verify each webhook (see Webhook signature verification) and update your local record idempotently — the same state can be retried by our workers.
  3. Handle out-of-order delivery. Webhook deliveries are at-least-once. If you see successful before sent_to_breb_provider, keep the later state and ignore the earlier one. A simple rule: if the transfer is already in a terminal state locally, ignore non-terminal webhooks.
  4. Only two states are final. successful and failed are the only terminal states. Once a transfer reaches either, it will never move again.

On this page