Payment Flow

Overview

This guide shows how to integrate Colombia COP flows. You create payouts, pay-in intents, or pay-in keys with a Colombia type, Bitso settles against the rail, and you track progress with webhooks or GET by id.

Core Resources

ResourceRole
PayoutOutbound COP payment (CO_BREB or CO_ACH).
Pay-inRead-only record of funds already received (CO_BREB or CO_PSE), or the refund of a CO_ACH payout (CO_ACH).
Pay-in IntentExpected inbound payment for a single charge (CO_PSE).
Pay-in KeyStanding Bre-B alias so payers can send you COP (CO_BREB).

Every resource uses a top-level type discriminator and a matching nested object whose key is the lowercase form of type (for example type: CO_PSE with a co_pse block).

Step-by-Step

1. Send a Bre-B payout

  1. Call Preview Payout with type: CO_BREB to resolve the alias.
  2. Call Create Payout with the preview result (co_breb.preview_payout_id) to commit the transfer. For Bre-B, do not rely on the Idempotency-Key header — preview_payout_id is the idempotency key (one preview commits at most one payout).
  3. Track PENDING → PROCESSING → COMPLETED or FAILED via webhooks or Get Payout.

2. Send an ACH payout

  1. Call Create Payout with type: CO_ACH and the beneficiary bank details.
  2. Track status the same way as Bre-B payouts.

Third-party ACH payouts (co_ach.beneficiary_type: THIRD_PARTY) send the bank account and the beneficiary document in co_ach.third_party. First-party and saved-beneficiary payloads have no document fields. Invalid document_id values return 400 invalid_field.

Use document_type: NIT only for a legal person. Every other type is a natural person.

document_typedocument_id
CCDigits only, length 3–10.
CEDigits only, length 6–10.
TIDigits only, length 10–11.
NIT (legal person)Digits only, length 9 or 10. Prefer 9 digits without the verifier. 10 digits are accepted and the last digit is omitted.
PAAlphanumeric [A-Za-z0-9], length 3–15.

When using NIT, set given_name and family_name to the same company name. Both fields remain required.

3. Charge a payer with PSE

  1. Call Create Payin Intent with type: CO_PSE and a co_pse.bank_code.
  2. Redirect the payer to the payment_link from the response.
  3. Fulfill on the payin_intent webhook (COMPLETED, FAILED, or EXPIRED) — not on the browser redirect alone.
  4. When completed, use payin_id (or Get Payin) to reconcile the settled deposit.

PSE bank_code:

4. Receive COP on a Bre-B alias

  1. Call Create Payin Key with type: CO_BREB and co_breb.key_type: ALIAS. See Pay-in Keys for first-party omit, natural-person, and legal-person examples. Creation is synchronous and returns status: ACTIVE.
  2. Share the registered alias with payers. When a payer sends funds to the alias, you receive a payin webhook and can list or get the pay-in. When co_breb.beneficiary_key is present, id is always set; match it to the pay-in key. When omitted, treat the pay-in as a first-party deposit recorded before this snapshot existed.

5. Match a refunded ACH payout

A returned CO_ACH payout appears as a CO_ACH pay-in. See List payins and Get payin for the refund fields and response shape.

Inbound COP arrives as CO_BREB or CO_PSE. A COP pay-in listing can also contain returned CO_ACH payouts, so branch on type before treating a row as new inbound funds.

PSE Pay-in Diagram

sequenceDiagram
    participant M as Merchant
    participant B as Bitso
    participant P as Payer
    participant Bank as Payer bank

    M->>B: POST /payments/v1/payin_intents (CO_PSE)
    B-->>M: payment_link + Intent ID (PENDING)
    M->>P: Redirect to payment_link
    P->>Bank: Authenticate and authorize
    Bank-->>B: Settlement notification
    B-->>M: Webhook payin_intent (COMPLETED)

Status Lifecycle

Payouts (CO_BREB, CO_ACH)

  • PENDING: Accepted, not yet dispatched.
  • PROCESSING: Sent to the rail.
  • COMPLETED: Funds confirmed. Terminal.
  • FAILED: Terminal failure. The resource may include an error block. When present, branch on error.code. Each code has one error.message. The message can be reworded later; do not parse it. An unrecognized code should be treated like payout_failed.

A limit rejected when you create the payout comes back as an HTTP error (payout_amount_above_maximum, payout_amount_below_minimum, or payout_limits_exceeded) and no payout is created. A limit rejected after the payout was accepted is a FAILED payout with amount_above_maximum, amount_below_minimum, or monthly_amount_limit_exceeded.

The same error block may be present on Get Payout, List Payouts, and the payout webhook. That field is one of the error classes on the payout. Branch on error.code. The table is the client action for each code, with the current error.message so you can match it. Do not parse the message.

CodeMessageWhat to do
destination_account_invalidThe destination account number is invalid. Verify it and retry.Correct the account number and retry.
destination_account_blockedThe destination account is inactive or blocked. Verify the account status with the beneficiary and retry.Check the account status with the beneficiary and retry.
destination_account_credit_limit_reachedThe destination account has reached its credit limit. Use another account or contact the beneficiary.Use another account, or contact the beneficiary.
destination_account_closedThe destination account is closed or cancelled. Use another account.Use another account.
destination_account_not_openThe destination account is not open. Verify the account number and retry.Correct the account number and retry.
destination_account_unavailableThe destination account is unavailable. Please verify the account details and try again.Correct the account details and retry.
amount_above_maximumThe amount exceeds the maximum allowed. Lower it and retry.Lower the amount and retry.
amount_below_minimumThe amount is below the minimum allowed. Raise it and retry.Raise the amount and retry.
monthly_amount_limit_exceededThe monthly deposit limit has been exceeded. Retry with a lower amount or wait until next month.Retry with a lower amount, or wait until next month.
beneficiary_phone_not_foundNo beneficiary was found for that phone number. Verify it and retry.Correct the phone number and retry.
beneficiary_details_mismatchThe beneficiary details do not match. Verify the document type, document number, account type, and account number, then retry.Correct the beneficiary details and retry.
beneficiary_identification_mismatchThe identification does not match the destination account. Verify it and retry.Correct the identification and retry.
invalid_account_typeThe account type is not valid for the destination bank. Correct it and retry.Correct the account type and retry.
invalid_transaction_typeThe transaction type is invalid. Correct it and retry.Correct the transaction type and retry.
invalid_requestThe request could not be processed. Please verify the payout details and try again.Correct the payout details and retry.
duplicate_transactionThis payout may be a duplicate. Confirm the previous attempt, then retry in a few minutes with a new reference.Confirm the previous attempt, then retry later with a new reference.
transaction_declinedThe recipient's bank returned the payout. Retry, and contact that bank if it fails again.Retry. If it fails again, contact the recipient's bank.
security_rejectionThe payout was cancelled for security reasons. Retry it.Retry.
technical_error_retryA technical error occurred. Please retry the payout in a few minutes.Retry in a few minutes.
transaction_timed_outThe receiving bank timed out. Please retry the payout.Retry.
service_temporarily_unavailableThe service is temporarily unavailable. Please retry the payout in a few minutes.Retry in a few minutes.
insufficient_fundsThe payout could not be completed due to insufficient funds.Add funds and retry.
security_review_requiredThe transaction requires additional security review.Contact Bitso support.
service_unavailableThe payout could not be completed. Please contact support.Contact Bitso support and include this code.
fund_reservation_failedThe payout could not be completed. Please contact support.Contact Bitso support and include this code.
request_not_acceptedThe payout request was not accepted. Please contact support.Contact Bitso support.
payout_rejectedThe payout was rejected by the provider.Contact Bitso support.
payout_failedThe payout has failed.Contact Bitso support.

service_unavailable and fund_reservation_failed share one message. The code is what distinguishes them.

Pay-in Intents (CO_PSE)

  • PENDING: Intent created; payment_link is usable.
  • COMPLETED: Deposit created (payin_id set). Terminal.
  • FAILED / EXPIRED: Terminal; no deposit is created on expiry.

Pay-in Keys (CO_BREB)

  • ACTIVE: Registered and can receive funds. Returned on successful create.
  • INACTIVE: Cannot receive funds.

See Pay-in Keys.

Best Practices

  • Idempotency: For Bre-B payouts, the Idempotency-Key header is not supported — reuse the same preview_payout_id from Preview Payout; that id is the idempotency key for Create Payout.
  • Reconciliation: Store external_ref for your own order id; it is searchable on list endpoints for payouts and pay-in intents.
  • Webhooks first: Treat webhooks as the source of truth for terminal states; use GET polling as fallback.
  • Validation: Always verify webhook signatures before updating orders. See Webhook Signatures.
  • Testing: Exercise the full status lifecycle in stage before going live. For PSE in stage, set co_pse.bank_code to 1022; in production, pick a code from https://api.bitso.com/v3/banks/co.

Did this page help you?