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
| State | Terminal? | Webhook event |
|---|---|---|
created | No | outgoing_transfer.created |
processing | No | outgoing_transfer.processing |
target_resolved | No | outgoing_transfer.target_resolved |
held | No | outgoing_transfer.held |
sent_to_breb_provider | No | outgoing_transfer.sent_to_breb_provider |
successful | Yes | outgoing_transfer.successful |
failed | Yes | outgoing_transfer.failed |
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_resolvedif resolution succeeds (plain-key path) or the target was already resolved. - →
failedif resolution errors out — for example,key_not_found,key_suspended, orinvalid_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:
- →
heldonce funds are held on the source account. - →
failedwithstate_reason = target_creditor_mismatchif the resolved creditor's document doesn't match theexpected_creditoryou 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_provideronce we dispatch the payment instruction to the Bre-B provider (Credibanco). - →
failedif the hold itself fails (rare at this point — most funds errors surface earlier).state_reasonwill 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:
- →
successfulon a positive settlement callback. - →
failedon a negative settlement callback (e.g.,breb_timeout,provider_unavailable,risk_control, or anunknownfallback).
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_transferswhen you create a batch. They never become transfers in our system. - Failed transfers were accepted, persisted, moved through at leastcreated, and then hit an error. They have astate_reasonand emit a webhook.
Working with states in your integration
- Treat every non-terminal state as transient. A transfer in
heldorsent_to_breb_provideris still in flight — surface it as "processing" in your UI rather than "complete." - 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. - Handle out-of-order delivery. Webhook deliveries are at-least-once. If
you see
successfulbeforesent_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. - Only two states are final.
successfulandfailedare the only terminal states. Once a transfer reaches either, it will never move again.