This guide documents the upcoming unified Refund API (POST /v1/refunds), currently in development. Endpoint links, exact response shape, and rail coverage are still being finalized and will be updated as the implementation progresses.
The Refund API lets clients return funds to a payer against an original payin through a single endpoint, instead of building custom reversal logic per payment rail. Clients reference the original payin by money_movement_id or transaction_id, and Cobre resolves the correct payout rail and counterparty automatically.This API complements the existing Money Movement APIs: a refund is created and tracked as a payout Money Movement linked back to the original payin.
Requesting a Refund#
A client requests a refund by calling POST /v1/refunds with a reference to the original payin (money_movement_id or transaction_id) and, for a partial refund, an amount strictly less than the original payin amount. Cobre validates that the original payin has not already been fully refunded, resolves the rail the payin was received on, and creates a payout Money Movement debiting the Cobre Balance that received the original funds.Refunds are supported only against payin Money Movements/Transactions. Transfer-in transactions are not eligible for refund for now.Supported rails#
| Country | Rail | Availability |
|---|
| Colombia | direct_debit_nequi | Available |
| Colombia | r2p_breb | Available |
| Colombia | r2p_pse | In development |
| Colombia | r2p_bancolombia | In development |
| Colombia | breb_credit(Breb static keys transactions) | In development |
| Mexico | spei_credit | In development |
| Mexico | r2p_spei | In development |
Key Terms#
Refund: a payout Money Movement created by Cobre that returns funds to the payer of an original payin.
Original payin: the payin Money Movement or Transaction a refund is requested against.
money_movement_id: Cobre's internal identifier for a Money Movement; one of two accepted ways to reference the original payin.
transaction_id: the transaction identifier for the original payin; the other accepted reference.
Partial refund: a refund for less than the full original payin amount. Multiple partial refunds are allowed against the same original payin, up to a hundred or its original amount.
Refund Lifecycle#
The exact set and shape of refund statuses is still under architecture review. The states below reflect the current design draft and may change before release.
| State | Description |
|---|
processing | The refund has been accepted and its underlying payout is being executed. |
completed | The refund's payout has settled to the payer. |
rejected | The refund request did not pass validation (for example, amount exceeds what remains refundable). |
failed | The refund's payout could not be completed. |
waiting_for_information | We are waiting for the payer to share their bank account and identification data for us to initiate the refund. This status only applies for the payment methods in which we don't receive the payer's data from the provider (PSE, Bancolombia) to start the refund without payer's intervention |
Refund Summary#
Each original payin has at most one refund summary — a running-total accumulator across every refund attempt requested against it. It complements the individual refund attempt: a single attempt (rfe_ prefix) represents one refund request and its own lifecycle, while the refund summary (rf_ prefix) tracks the payin as a whole.The refund summary reports:original_amount — the amount of the original payin.
refunded_amount — the amount already refunded and settled across all completed attempts.
reserved_amount — the amount currently held by in-flight (not yet settled) attempts.
refundable_amount — the amount still available to refund (original_amount minus refunded_amount and reserved_amount).
status — the summary's overall derived state (for example partially_refunded or fully_refunded).
refunds[] — the list of individual attempts against this payin.
Why use it#
Retrieving the refund summary saves you from fetching every individual attempt and adding the amounts up yourself to know how much of a payin has been refunded and how much remains refundable. It is especially useful to:Check refundable_amount before requesting a new partial refund, without tracking prior refunds on your own side.
Reconcile a payin's refund activity in a single call instead of paginating through attempts.
Show a payer or support agent the full refund picture for a payin — total refunded, pending, and remaining — at a glance.
The refund summary is read-only: it is derived from the underlying refund attempts, not created directly. There is exactly one summary per original payin, shared by every attempt requested against it.
Refund Idempotency#
Refund creation requires an idempotency key so retries do not double-refund the same payin.We highly recommend you to use a deterministic key derived from the original payin's identifier (for example rf_{original_money_movement_id}_{uuid}) so a retried request is recognized as the same refund attempt.
Allowed Actions on Refunds#
Description: Request a full or partial refund against an existing payin by calling
POST /v1/refunds with
money_movement_id or
transaction_id,
amount and an optional
description.
What to expect after the action: A refund is created in
processing status. Cobre resolves the original payin data or sends a link to capture it and initiates the corresponding payout automatically.
📘
Learn more: How to Get Started#
Before you begin, we recommend reviewing the following considerations.1
Confirm the original payin is eligible
The refund must reference a payin Money Movement/Transaction that has not already been fully refunded. Transfer-in transactions cannot be refunded. For partial refunds, the requested amount must be strictly less than the amount still available to refund.
2
Ensure the originating Cobre Balance is funded
A refund debits the same Cobre Balance that received the original payin. That balance must hold sufficient funds for the refund to process — Cobre does not source refund funds from any other balance.
3
Prepare an idempotency key
Derive an idempotency key from the original payin's identifier or use your own defined unique key so retried refund requests are not applied twice.
4
Implement Webhooks to track status
Since a refund settles asynchronously as a payout, we strongly recommend implementing Webhooks to track its progress.
What to expect after using this API#
1
Refund accepted
Once a refund request passes validation, it is created in processing status and Cobre begins executing the underlying payout to the original payer.
2
Refund completion
When the underlying payout settles, the refund moves to completed and the debit is reflected on the originating Cobre Balance.
3
Refund rejection or failure
If the underlying payout is rejected by the financial institution or by Cobre, the refund is rejected. If the underlying payout cannot be completed after acceptance, the refund moves to failed.