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:
The destination rule depends on the payment provider and, for crypto, on your account’s configuration:
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
The off-ramp order refund also has a customer-scoped form,
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.
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. Refunding and reprocessing never apply to the same order.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:
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:
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.
refundDestinationRequiredistrueand there is no order that could already carry an address, so a request withoutrefundAddressis 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
409with error code20077(CryptoRefundDepositPendingMatchException) — the one refusal that resolves by itself, so retry later. Every other refusal is final.
Following the outcome
ThelatestRefund block on the order (and on each refundable deposit) carries the state of its most recent refund, or null if there never was one:
rejectionReason is empty unless the refund was REJECTED:
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.
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.
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 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.