Webhook Notifications

Use webhooks to get automatic, real-time updates on Colombia Payments activity. Register a callback URL with Bitso; when a resource status changes, Bitso sends an HTTP POST to your endpoint.

📘

Important Guidelines

  • We don't guarantee the order of event delivery. Your application should process events that may arrive out of sequence.
  • The same event might be delivered multiple times. Your application must be idempotent.
  • Your endpoint must respond in under five seconds to avoid a timeout. Store the notification and process it asynchronously.
  • For transport, signing, and subscription details, see Webhooks.

Colombia Event Types

Subscribe to these event types for Colombian payment resources:

Event typeResourceWhen it fires
payoutPayout (CO_BREB or CO_ACH)Any status change (PENDING → PROCESSING → COMPLETED / FAILED).
payinPay-in (CO_BREB, CO_PSE, or compensatory CO_ACH)A deposit lands on a Bre-B key, a PSE intent settles into a pay-in, or a failed payout is returned as a compensatory deposit.
payin_intentPay-in intent (CO_PSE)Any status change (PENDING → COMPLETED / FAILED / EXPIRED).

Branch on payload.type for rail-specific logic. A payin with type: CO_ACH is the refund of a CO_ACH payout. See Get payin for the fields that link it to that payout. Inbound COP is CO_BREB or CO_PSE.

Pay-in keys do not emit webhooks — creation returns ACTIVE synchronously. The payin webhook for funds received on a key carries sender identity and, when present, payload.co_breb.beneficiary_key (id is always set). Match beneficiary_key.id to the pay-in key. When that object is omitted, treat the pay-in as a first-party deposit recorded before this snapshot existed.

How to Register Your Webhook

Subscribe with Create webhook subscriptions. For Colombia payments, pass payout, payin, and payin_intent in events (any subset). See Managing Subscriptions. Fetch signing keys from List webhook public keys.

Webhook Payload Structure

Every webhook body has this envelope:

{
  "event": "payout|payin|payin_intent",
  "payload": { }
}

payload is byte-identical to the response of the matching GET for the same id:

eventGET endpoint
payoutGET /payments/v1/payouts/{id}
payinGET /payments/v1/payins/{id}
payin_intentGET /payments/v1/payin_intents/{id}

Reuse the same parsing code for webhooks and GET responses. Nested rail objects use lowercase keys (co_breb, co_pse, co_ach) that match type.

Payload Examples

Examples below wrap the GET response shapes from the API contract.

{
  "event": "payout",
  "payload": {
    "id": "89325a16-669e-4118-a8f0-f4236db92503",
    "status": "PROCESSING",
    "amount": "50000",
    "asset": "COP",
    "type": "CO_BREB",
    "co_breb": {
      "key_type": "PHONE",
      "key_value": "3120000000",
      "beneficiary": {
        "document_type": "CC",
        "document_id": "12345678",
        "display_name": "JAN*** SMI***",
        "bank_name": "Bancolombia"
      }
    },
    "external_ref": "client-op-def456",
    "created_at": "2026-02-26T14:00:00Z"
  }
}

A FAILED payout may carry the same error object as Get Payout. When present, branch on error.code. That field is one of the error classes on the payout. The table is the client action for each code, with the current error.message so you can match it. The message can be reworded later; do not parse it. An unrecognized code should be treated like payout_failed.

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. The same table is in Payment Flow.

{
  "event": "payin",
  "payload": {
    "id": "550238a2_2eed_440e_a6d0_a227a0520f00",
    "asset": "COP",
    "amount": "500000.00",
    "type": "CO_BREB",
    "status": "COMPLETED",
    "co_breb": {
      "sender_name": "Jane Smith",
      "sender_document_type": "CC",
      "sender_document_id": "123456789",
      "beneficiary_key": {
        "id": "f9a23c11-44b2-4e7a-bc13-2d5f6a7b8c9d",
        "key": "@KEYALIAS",
        "external_ref": "client-key-abc123"
      }
    },
    "created_at": "2026-02-26T14:00:00Z",
    "updated_at": "2026-02-26T14:05:00Z"
  }
}

When payload.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.

{
  "event": "payin",
  "payload": {
    "id": "fd857aec_fc24_4d93_907c_c0e2edeb7495",
    "status": "COMPLETED",
    "amount": "50000.00",
    "asset": "COP",
    "type": "CO_ACH",
    "trace_id": "5b04eff7-91e0-43ff-843e-773646646415",
    "refund": {
      "payout_id": "9ff0516e_3e14_4ce5_a847_843c333021a4",
      "reason": "DESTINATION_ACCOUNT_UNAVAILABLE",
      "payout_external_ref": "clien1t-op-abc123"
    },
    "created_at": "2026-08-04T20:10:24.637Z",
    "updated_at": "2026-08-04T20:10:24.654Z",
    "co_ach": {}
  }
}

When payload.refund is present, the pay-in is a compensatory deposit for a failed payout. A CO_ACH pay-in is that case for a refunded Colombian ACH payout. See Get payin to reconcile it with the original payout.

{
  "event": "payin_intent",
  "payload": {
    "id": "be6185ad-afdf-d682-2ca7-d447250f5e96",
    "status": "PENDING",
    "asset": "COP",
    "amount": "500000.00",
    "type": "CO_PSE",
    "co_pse": {
      "bank_code": "007",
      "success_redirect_url": "https://example.com/success",
      "error_redirect_url": "https://example.com/failure",
      "payment_link": "https://api.bitso.com/payments/pse/start?token=abc123"
    },
    "external_ref": "client-payin-abc123",
    "expires_at": "2026-02-26T23:59:59Z",
    "created_at": "2026-02-26T14:00:00Z"
  }
}

When a PSE intent reaches COMPLETED, the same GET/webhook shape includes payin_id pointing at the settled pay-in. Fulfil on the webhook (or a follow-up GET), not on the browser redirect alone.



Did this page help you?