Overview
Offramps with transfers let your users convert stablecoin to international fiat currencies through a quote-based flow. Unlike payment routes, which create persistent pipes for USD offramps, transfers are one-time operations with locked exchange rates and fees. Use transfers for international payouts to currencies like COP, MXN, and BRL. The process involves three steps:- Create a payment account: register the user’s bank account details as a payment account.
- Create a quote: get a quote with the exchange rate, fees, and destination amount.
- Create a transfer: execute the transfer using the quote before it expires.
Before you begin
Make sure you have:- API access to the payment accounts, quotes, and transfers endpoints.
- User bank account details: the destination bank account information (details vary by country and rail).
- A stablecoin source: users need stablecoin on a supported chain to transfer.
Key concepts
Payment accounts
A payment account is a stored representation of a user’s bank account or other fiat destination. See Payment Accounts for the general concept and the US-specific fields; this page covers the additional fields required for international destinations. For international transfers, you’ll create anexternalFiatAccount type payment account containing the user’s bank details for the destination country.
Quotes
A quote locks in the exact exchange rate, fees, and destination amount for a transfer, unlike an exchange rate preview, which is indicative only and doesn’t lock anything in. Quotes have an expiration time, so you must create the transfer before the quote expires. Key information in a quote includes:- Exchange rate: the rate used for currency conversion, excluding fees.
- Fees: breakdown of Rain fees and any sender fees.
- Destination amount: the exact amount the user receives in fiat.
- Expiration: when the quote expires and is no longer valid.
Transfers
A transfer executes the movement of funds from crypto to fiat using a locked quote. When you create a transfer, Rain generates adepositAddress where the user sends their stablecoin. Once Rain receives the funds, it automatically converts and sends them to the destination bank account.
API flow
Action / API callExternal / networkWebhook Rain sends you
Step 1: create a payment account
Before creating a quote, register the user’s bank account as a payment account with Create a payment account. SetexternalFiatAccount.currency and rail to the destination’s currency and rail. The other required fields vary by country and rail, so see Payment account details for the fields each rail requires, including the extra beneficiary identification some rails need for third-party payouts.
Set thirdParty to false when the bank account belongs to the user initiating the transfer, or to true when it belongs to a different party, such as a vendor. The response returns the payment account’s id, which you reference when you create a quote.
Additional fields may be required depending on the transfer amount or specific
compliance requirements. Contact your Rain representative for detailed
requirements.
Step 2: create a quote
Once you have a payment account, request a quote to see the exchange rate, fees, and destination amount.Request
Call Create a quote with theuserId, a source (amount, currency and rail), and a destination (currency, rail, and an address of type paymentAccount with its id).
You can optionally specify a source
address if you know which wallet will be
sending the funds. Some rails may require this information.Response
Quote fields
Step 3: create a transfer
If the user is happy with the exchange rate and fees, create a transfer using the quote.Request
Call Create a transfer with thequoteId from the quote. The parameters and headers are below.
Request parameters
Headers
Reusing an
idempotency-key with a different request body returns a 409 conflict. Generate a new key for each distinct transfer.Response
The response includes the transfer details along with adepositAddress where the user sends their stablecoin:
Response fields
Deposit window
The transfer’sexpiresAt is the deadline for sending funds to the deposit address: 24 hours after the transfer is created. This is separate from the quote’s expiresAt, which only limits when the transfer can be created. The exchange rate and fees from the quote hold for the whole deposit window.
If no deposit arrives by expiresAt, the transfer moves to expired. Funds sent after that are not converted at the original rate or a new one. Contact Rain support to have them returned to the sender.
What happens when a user deposits crypto
Rain creates the transfer when you callPOST /transfers, and sends a transactionTransfer webhook with action created right away. Its status is awaiting_transfer until funds arrive.
When a user then sends stablecoin to the deposit address:
- Crypto received: Rain detects the incoming stablecoin transfer.
- Validation: Rain validates the amount and currency against the transfer.
- Conversion: Rain converts the stablecoin to the destination fiat currency.
- Status updates: as the transfer progresses, Rain sends
transactionTransferwebhooks with actionupdatedfor status changes. - Fiat transfer: Rain sends the funds to the user’s bank account via the specified rail.
- Transfer completed: the transfer settles, and Rain sends a
transactionTransferwebhook with actioncompleted.
Webhooks
Rain sends webhooks during the transfer lifecycle to keep you informed of status changes:
See Money Movement Webhooks for how a transfer moves through these events, and the transfer object reference for the full webhook payload schema.
Transfer statuses
Rain checks a transfer against your program’s partner-managed limits when you create it. A transfer over the limit is rejected with
400 Bad Request.Query transfers
You can query all transfer transactions via the Transactions API. Filter bytype=transfer to see transfer transactions.
To get a specific transaction by ID, call Get a transaction by its ID.
Cancel a transfer
To cancel a transfer that hasn’t completed, callDELETE /v1/transfers/{transactionId} with the transaction ID the create call returned.
A successful cancel returns 200 with the transfer, its status now cancelled. Once a transfer passes the point where Rain can stop it, the call returns 409 with Transfer can no longer be cancelled.
Supported configurations
Source (crypto)
Destination (fiat)
See Money Movement Limits for per-corridor limits, supported use cases, settlement times and cutoffs, and Payment Account Details for the bank fields each rail requires.
✱ Supported currencies available upon request. Contact your Rain
representative to enable them for your organization.
Error handling
Errors return the HTTP status, its reason, and a message:refundReason instead. For example, a deposit window that runs out moves the transfer to expired, and a deposit that doesn’t match the transfer is refunded with the reason amount_mismatch. See Refund reasons.
Quote errors
Transfer errors
Best practices
- Use idempotency keys: always include a unique
idempotency-keyheader with each transfer request to prevent duplicate transfers on retries. - Display quote details: show users the exchange rate, fees, and destination amount before they confirm the transfer.
- Handle expiration: implement logic to refresh quotes if the user takes too long to confirm.
- Monitor webhooks: set up webhook handlers to track transfer status in real time.
- Validate bank details: create payment accounts during user onboarding to catch errors early.
- Provide clear instructions: make sure users understand they must send the exact amount to the deposit address.
- Handle 503 responses: retry a
503with the sameidempotency-key, never a new one.
What’s next
Set up onramps
Enable fiat deposits into crypto.
Set up offramps
Enable fiat withdrawals from crypto via payment routes.
Payment accounts
See the full request, response, and field reference for registering a bank account.
Partner-managed limits
Set limits on onramp and offramp volume and handle deposits that exceed them.
Set up webhooks
Receive transfer notifications as they happen.