> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ripio.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Refunds

> How to return the money of an on-ramp or off-ramp operation that could not be completed: when a refund can be requested, where the money goes, and how to follow its outcome.

When an operation cannot be completed — a buy that could not be executed, a deposit that never matched an order — the money the customer already sent has to go back. A refund returns **the deposit**, in the currency it arrived in: fiat on an on-ramp, crypto on an off-ramp. It is requested per operation, or per deposit when there is no operation to name.

<Warning>
  Refunds are an **opt-in feature that must be enabled for your account by the Ripio team**, separately for each ramp. While a ramp is disabled, its refund endpoints return `403 Forbidden` — error code `20056` (`FiatRefundNotEnabledException`) for on-ramp, `20072` (`CryptoRefundNotEnabledException`) for off-ramp — and `refundable` is always `false`. The listings of deposits with no order are affected too — [fiat](/ramps-api/refunds/list-refundable-deposits) and [crypto](/ramps-api/refunds/list-refundable-crypto-deposits): while a ramp is disabled, its listing returns no claimable deposits, so an empty page there does not mean the customer has none.
</Warning>

## Deciding whether to offer a refund

Every on-ramp and off-ramp order carries two read-only fields that answer the question before you build the request:

| Field | Meaning |
| - | - |
| `refundable` | Whether a refund can be requested for this operation **right now**. Computed per request, so use it to decide whether to call the endpoint instead of discovering it through a `400`. |
| `refundDestinationRequired` | Whether the request must name a destination. `true` → send `fiatAccountId` (on-ramp) or `refundAddress` (off-ramp). `false` → the money returns to where it came from and the field is rejected. `null` → the operation is not in refund territory, so the question does not apply. |

The destination rule depends on the payment provider and, for crypto, on your account's configuration:

| Ramp | Country | Where the money goes | `refundDestinationRequired` |
| - | - | - | - |
| On-ramp | Argentina, Mexico, Brazil | The account the customer paid from | `false` |
| On-ramp | Colombia | A destination account you name | `true` |
| Off-ramp | All | The deposit's on-chain origin address, or an address you name | Depends on your account's refund mode |

`refundDestinationRequired` is the field to branch on — it is answered per operation, so you never have to keep your own table of countries and providers.

## Requesting a refund

| What you are refunding | Endpoint |
| - | - |
| A cancelled on-ramp order | [`POST /customers/{customerId}/transactions/onramp/{orderId}/refund/`](/ramps-api/refunds/request-fiat-refund) |
| A fiat deposit with no order | [`POST /customers/{customerId}/refundableDeposits/{depositUuid}/refund/`](/ramps-api/refunds/refund-deposit) |
| A failed off-ramp order | [`POST /offramp/{orderId}/refund/`](/ramps-api/refunds/request-crypto-refund) |
| A crypto deposit with no order | [`POST /customers/{customerId}/refundableCryptoDeposits/{depositId}/refund/`](/ramps-api/refunds/refund-crypto-deposit) |

The off-ramp order refund also has a [customer-scoped form](/ramps-api/refunds/request-customer-crypto-refund), `POST /customers/{customerId}/transactions/offramp/{orderId}/refund/`. Same operation, same result — use it when your integration is organised per end customer rather than per order.

<Note>
  A cancelled off-ramp order whose **trade already executed** is not one of these: the crypto was sold, so there is no deposit left to send back and `refundable` is `false`. What failed there is the fiat payout, and the way out is to retry it against another destination account — see [Change Fiat Account and Reprocess](/ramps-api/off-ramp/change-fiat-account). Refunding and reprocessing never apply to the same order.
</Note>

Requests are idempotent: `202` means the refund was registered, `200` means one already existed for that deposit and nothing new was created.

## Deposits with no order

Money can also arrive with no operation behind it: a fiat payment that never matched an on-ramp order, or crypto sent to a deposit address that matched no off-ramp order. There is no order to hang the refund off, and — because every transaction listing is rooted at orders — no other endpoint shows the payment at all. These two listings are the only place it appears, one per ramp:

| Deposit | Listing | Refunded by |
| - | - | - |
| Fiat, on-ramp | [List Refundable Deposits](/ramps-api/refunds/list-refundable-deposits) | `depositUuid` |
| Crypto, off-ramp | [List Refundable Crypto Deposits](/ramps-api/refunds/list-refundable-crypto-deposits) | `depositId` |

Each rail returns what it received: a fiat deposit is refunded in fiat, a crypto one on-chain. There is no crossing between them.

Both listings answer two different questions, and `includeRequested` picks which one:

| Call | Answers | What comes back |
| - | - | - |
| The listing, plain | "What can this customer claim?" | Only the deposits a new refund can be requested for: no refund yet, or a rejected one. |
| The listing with `?includeRequested=true` | "Where is this customer's money?" | The same deposits, plus the ones whose refund was already requested and has not been paid out yet. |

Ask for the wider list when you show the customer their own payments. Approval is not instant, so with the default list a deposit disappears from the only place it was visible right after the customer asks for it back; with `includeRequested=true` it stays, and `latestRefund` says what is happening to it. A deposit leaves that list once its refund reaches `COMPLETED` — the money is back with the customer.

Neither listing has a `refundable` field, so in the wider mode a deposit being there no longer means it can be claimed. `latestRefund` is what tells them apart: `null` or `REJECTED` when a refund can still be requested, any other state when one is already under way.

The reverse also holds while that ramp's refunds are not enabled for your account: a deposit **not** being there does not mean it does not exist. Neither call offers a deposit you could not refund anyway, so the claimable half is left out of both. What `?includeRequested=true` still returns is any deposit whose refund Ripio support already started for the customer — that money is moving, and the customer's screen has to be able to say so.

Two things are specific to the crypto rail:

* **The address is mandatory in explicit mode.** `refundDestinationRequired` is `true` and there is no order that could already carry an address, so a request without `refundAddress` is rejected. On the order-rooted endpoint the field is optional because the order may hold one from its creation; here it never can.
* **A deposit that just arrived is held back.** Only deposits whose matching has concluded are listed, because refunding one that is about to find its order would return crypto that is about to be sold. Requesting it anyway answers `409` with error code `20077` (`CryptoRefundDepositPendingMatchException`) — the one refusal that resolves by itself, so retry later. Every other refusal is final.

Crypto deposits with no order only occur on API integrations that create one-off off-ramp orders. A deposit made through an off-ramp **session** always gets an order, so an integration that only uses the widget will never see one.

## Following the outcome

The `latestRefund` block on the order (and on each refundable deposit) carries the state of its most recent refund, or `null` if there never was one:

| Status | Meaning |
| - | - |
| `PENDING` | Registered, not yet sent to the payment provider or the blockchain. |
| `PROCESSING` | Sent, awaiting confirmation. |
| `COMPLETED` | The money reached its destination. |
| `REJECTED` | The request was declined. Nothing was sent — see `rejectionReason`. |
| `FAILED` / `CANCELLED` | The attempt did not go through. Contact Ripio support. |

`rejectionReason` is empty unless the refund was `REJECTED`:

| Code | Meaning |
| - | - |
| `DUPLICATE_REQUEST` | The same refund was already requested. |
| `INVALID_DESTINATION` | The destination account or address cannot receive the money. |
| `ALREADY_RESOLVED` | The operation was already resolved by other means. |
| `NOT_ELIGIBLE` | The operation does not qualify for a refund. |
| `CONTACT_SUPPORT` | Contact Ripio's support team for the detail. |

A rejection is not final: nothing was paid out, so `refundable` stays `true` and the refund can be requested again.

`initiatedBy` tells you who originated it, which matters because **a refund is not necessarily one you asked for** — Ripio may return a deposit on its own after a failed order, or register one on the customer's behalf.

| Value | Who | Ramp |
| - | - | - |
| `PARTNER` | Your own API call | both |
| `WIDGET` | The end user, from the widget | both |
| `SYSTEM` | Returned automatically by Ripio after a failed order | on-ramp only |
| `OPS` | Registered by Ripio's support team on the customer's behalf | both |

There is no `SYSTEM` on the off-ramp: nothing returns crypto automatically, so a crypto refund was always asked for by someone.

The **response to an on-ramp refund request** carries `initiatedBy` too. Requests are idempotent, so a `200` hands back whatever refund already existed for that deposit — which may be one you never asked for. Read the field instead of assuming the refund is your own.

<Note>
  A refund emits an event only when it **completes**, and which event it is depends on whether there was an order behind the money: `ON-RAMP.ORDER.REFUNDED` and `ON-RAMP.DEPOSIT.REFUNDED` on the on-ramp, [`OFF-RAMP.ORDER.REFUNDED` and `OFF-RAMP.DEPOSIT.REFUNDED`](/ramps-api/webhooks/off-ramp-events) on the off-ramp. Every other transition is silent — in particular a **rejection is never an event on either ramp**, so `latestRefund` is the only way to learn about one. Poll it if you need to know that a refund was declined.

  Neither off-ramp event carries the hash of the returned crypto: read `refundedTxnHash` for that, on the order or on the deposit.
</Note>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.