> ## Documentation Index
> Fetch the complete documentation index at: https://rain-sandbox-trial.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Offramps with Transfers

> Enable users to offramp crypto to international fiat currencies using quotes and transfers for cross-border payments.

## Overview

Offramps with transfers let your users convert stablecoin to international fiat currencies through a quote-based flow. Unlike [payment routes](/docs/offramps), 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:

1. **Create a payment account:** register the user's bank account details as a payment account.
2. **Create a quote:** get a quote with the exchange rate, fees, and destination amount.
3. **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](/docs/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 an `externalFiatAccount` 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](/docs/exchange-rates), 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 a `depositAddress` where the user sends their stablecoin. Once Rain receives the funds, it automatically converts and sends them to the destination bank account.

## API flow

<div className="wf-diagram">
  <div className="legend">
    <span className="lg"><span className="swatch action" />Action / API call</span>
    <span className="lg"><span className="swatch ext" />External / network</span>
    <span className="lg"><span className="swatch send" />Webhook Rain sends you</span>
  </div>

  <div className="diagram-shell borderless">
    <svg id="transfers-api-flow" role="img" aria-label="Offramp with transfers: the user provides bank details, you register a payment account, request a quote, and create a transfer, the user sends stablecoin to the deposit address, Rain converts it to fiat and transfers it to the user's bank, then sends a transactionTransfer webhook." viewBox="0 0 1080 1516" width="1080" height="1516" style={{width: "100%", height: "auto"}}><defs><marker id="arrow-transfers-api-flow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" className="marker-fill-default" /></marker><marker id="arrowSend-transfers-api-flow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" className="marker-fill-send" /></marker></defs><path d="M 540 164 L 540 204" fill="none" className="conn-default" stroke-width="1.7" marker-end="url(#arrow-transfers-api-flow)" opacity="0.92" /><path d="M 540 272 L 540 312" fill="none" className="conn-default" stroke-width="1.7" marker-end="url(#arrow-transfers-api-flow)" opacity="0.92" /><path d="M 540 380 L 540 420" fill="none" className="conn-default" stroke-width="1.7" marker-end="url(#arrow-transfers-api-flow)" opacity="0.92" /><path d="M 540 488 L 540 528" fill="none" className="conn-default" stroke-width="1.7" marker-end="url(#arrow-transfers-api-flow)" opacity="0.92" /><path d="M 540 596 L 540 636" fill="none" className="conn-default" stroke-width="1.7" marker-end="url(#arrow-transfers-api-flow)" opacity="0.92" /><path d="M 540 704 L 540 744" fill="none" className="conn-default" stroke-width="1.7" marker-end="url(#arrow-transfers-api-flow)" opacity="0.92" /><path d="M 540 812 L 540 852" fill="none" className="conn-default" stroke-width="1.7" marker-end="url(#arrow-transfers-api-flow)" opacity="0.92" /><path d="M 540 920 L 540 960" fill="none" className="conn-default" stroke-width="1.7" marker-end="url(#arrow-transfers-api-flow)" opacity="0.92" /><path d="M 540 1028 L 540 1068" fill="none" className="conn-default" stroke-width="1.7" marker-end="url(#arrow-transfers-api-flow)" opacity="0.92" /><path d="M 540 1136 L 540 1176" fill="none" className="conn-default" stroke-width="1.7" marker-end="url(#arrow-transfers-api-flow)" opacity="0.92" /><path d="M 540 1244 L 540 1284" fill="none" className="conn-default" stroke-width="1.7" marker-end="url(#arrow-transfers-api-flow)" opacity="0.92" /><path d="M 540 1352 L 540 1392" fill="none" className="conn-send" stroke-width="1.7" marker-end="url(#arrowSend-transfers-api-flow)" opacity="0.92" /><foreignObject x="320" y="96" width="440" height="68"><div className="card action"><span className="ct"><span className="tag">User</span><span className="lab">Provides bank details</span></span></div></foreignObject><foreignObject x="320" y="204" width="440" height="68"><div className="card action"><span className="bn">1</span><span className="ct"><span className="tag">You</span><span className="lab mono">POST /payment-accounts</span></span></div></foreignObject><foreignObject x="320" y="312" width="440" height="68"><div className="card action"><span className="bn">2</span><span className="ct"><span className="tag rain">Rain</span><span className="lab">Returns payment account</span></span></div></foreignObject><foreignObject x="320" y="420" width="440" height="68"><div className="card action"><span className="bn">3</span><span className="ct"><span className="tag">You</span><span className="lab mono">POST /quotes</span></span></div></foreignObject><foreignObject x="320" y="528" width="440" height="68"><div className="card action"><span className="bn">4</span><span className="ct"><span className="tag rain">Rain</span><span className="lab">Returns quote with rate & fees</span></span></div></foreignObject><foreignObject x="320" y="636" width="440" height="68"><div className="card action"><span className="bn">5</span><span className="ct"><span className="tag">You</span><span className="lab">Displays quote details</span></span></div></foreignObject><foreignObject x="320" y="744" width="440" height="68"><div className="card action"><span className="bn">6</span><span className="ct"><span className="tag">You</span><span className="lab mono">POST /transfers</span></span></div></foreignObject><foreignObject x="320" y="852" width="440" height="68"><div className="card action"><span className="bn">7</span><span className="ct"><span className="tag rain">Rain</span><span className="lab">Returns transfer with depositAddress</span></span></div></foreignObject><foreignObject x="320" y="960" width="440" height="68"><div className="card action"><span className="bn">8</span><span className="ct"><span className="tag">You</span><span className="lab">Provides deposit address</span></span></div></foreignObject><foreignObject x="320" y="1068" width="440" height="68"><div className="card ext"><span className="bn">9</span><span className="ct"><span className="tag">User</span><span className="lab">Sends stablecoin to deposit address</span></span></div></foreignObject><foreignObject x="320" y="1176" width="440" height="68"><div className="card action"><span className="bn">10</span><span className="ct"><span className="tag rain">Rain</span><span className="lab">Converts to fiat</span></span></div></foreignObject><foreignObject x="320" y="1284" width="440" height="68"><div className="card ext"><span className="bn">11</span><span className="ct"><span className="tag">Bank</span><span className="lab">Receives fiat transfer</span></span></div></foreignObject><foreignObject x="320" y="1392" width="440" height="68"><div className="card send"><span className="bn">12</span><span className="ct"><span className="tag">Webhook</span><span className="lab mono">transactionTransfer</span></span></div></foreignObject></svg>
  </div>
</div>

The steps above, in text:

| #  | Actor | Step                                                                                     |
| -- | ----- | ---------------------------------------------------------------------------------------- |
| –  | User  | Provides bank details to you                                                             |
| 1  | You   | Calls `POST /payment-accounts`                                                           |
| 2  | Rain  | Returns the payment account                                                              |
| 3  | You   | Calls `POST /quotes`                                                                     |
| 4  | Rain  | Returns the quote with exchange rate and fees                                            |
| 5  | You   | Displays the quote details to the user                                                   |
| 6  | You   | Calls `POST /transfers`                                                                  |
| 7  | Rain  | Returns the transfer with a `depositAddress`                                             |
| 8  | You   | Provides the deposit address to the user                                                 |
| 9  | User  | Sends stablecoin to the deposit address                                                  |
| 10 | Rain  | Converts the stablecoin to fiat                                                          |
| 11 | Bank  | Receives the fiat transfer                                                               |
| 12 | Rain  | Sends a [`transactionTransfer`](/docs/transaction#transactiontransfer-completed) webhook |

## Step 1: create a payment account

Before creating a quote, you must register the user's bank account as a payment account. The required fields vary by destination country.

### Request: Colombian peso (COP)

```bash theme={null}
curl -X POST https://api.rain.xyz/v1/payment-accounts \
  -H "Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "externalFiatAccount",
    "userId": "11111111-1111-1111-1111-111111111111",
    "nickname": "Maria Colombia Account",
    "externalFiatAccount": {
      "currency": "cop",
      "rail": "co_ach",
      "thirdParty": false,
      "beneficiaryType": "individual",
      "beneficiaryFirstName": "Maria",
      "beneficiaryLastName": "Garcia",
      "beneficiaryEmail": "maria@example.com",
      "beneficiaryAddress": {
        "line1": "Calle 100 #15-20",
        "city": "Bogota",
        "region": "Bogota D.C.",
        "postalCode": "110111",
        "countryCode": "CO"
      },
      "bankName": "Bancolombia",
      "accountNumber": "12345678901"
    }
  }'
```

<Info>
  Colombia supports multiple rails: `co_ach` for bank transfers, `co_bre_b` for
  Bancolombia transfers, and `co_mobile_wallet` for mobile wallet payments.
</Info>

### Request: Mexican peso (MXN)

```bash theme={null}
curl -X POST https://api.rain.xyz/v1/payment-accounts \
  -H "Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "externalFiatAccount",
    "userId": "11111111-1111-1111-1111-111111111111",
    "nickname": "Carlos Mexico Account",
    "externalFiatAccount": {
      "currency": "mxn",
      "rail": "mx_spei",
      "thirdParty": false,
      "beneficiaryType": "individual",
      "beneficiaryFirstName": "Carlos",
      "beneficiaryLastName": "Lopez",
      "beneficiaryEmail": "carlos@example.com",
      "beneficiaryAddress": {
        "line1": "Av. Reforma 222",
        "city": "Mexico City",
        "region": "CDMX",
        "postalCode": "06600",
        "countryCode": "MX"
      },
      "bankName": "BBVA Mexico",
      "clabeNumber": "012345678901234567"
    }
  }'
```

### Request: Brazilian real (BRL)

```bash theme={null}
curl -X POST https://api.rain.xyz/v1/payment-accounts \
  -H "Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "externalFiatAccount",
    "userId": "11111111-1111-1111-1111-111111111111",
    "nickname": "Ana Brazil Account",
    "externalFiatAccount": {
      "currency": "brl",
      "rail": "br_pix",
      "thirdParty": false,
      "beneficiaryType": "individual",
      "beneficiaryFirstName": "Ana",
      "beneficiaryLastName": "Silva",
      "beneficiaryEmail": "ana@example.com",
      "taxNumber": "123.456.789-00",
      "beneficiaryAddress": {
        "line1": "Rua Augusta 1500",
        "city": "Sao Paulo",
        "region": "SP",
        "postalCode": "01304-001",
        "countryCode": "BR"
      },
      "bankName": "Itau",
      "pixCode": "ana@example.com"
    }
  }'
```

<Info>
  Brazil supports `br_pix` for instant PIX payments and `br_ted` for traditional
  bank transfers.
</Info>

<Info>
  **First-party and third-party transfers**

  Rain supports both first-party and third-party transfer destinations:

  * **First-party** (`thirdParty: false`): the bank account belongs to the same user initiating the transfer.
  * **Third-party** (`thirdParty: true`): the bank account belongs to a different party, for example a vendor or another individual.
</Info>

### Response

```json theme={null}
{
  "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "type": "externalFiatAccount",
  "nickname": "Maria Colombia Account",
  "userId": "11111111-1111-1111-1111-111111111111",
  "externalFiatAccount": {
    "currency": "cop",
    "rail": "co_ach",
    "thirdParty": false,
    "beneficiaryType": "individual",
    "beneficiaryFirstName": "Maria",
    "beneficiaryLastName": "Garcia",
    "beneficiaryEmail": "maria@example.com",
    "beneficiaryAddress": {
      "line1": "Calle 100 #15-20",
      "city": "Bogota",
      "region": "Bogota D.C.",
      "postalCode": "110111",
      "countryCode": "CO"
    },
    "bankName": "Bancolombia",
    "accountNumber": "12345678901"
  },
  "createdAt": "2025-01-15T10:25:00Z"
}
```

### Required fields by rail

#### Colombia (COP)

| Field                  | `co_ach` | `co_bre_b` | `co_mobile_wallet` |
| ---------------------- | -------- | ---------- | ------------------ |
| `beneficiaryFirstName` | Required | Required   | Required           |
| `beneficiaryLastName`  | Required | Required   | Required           |
| `beneficiaryEmail`     | Required | Required   | Required           |
| `beneficiaryAddress`   | Required | Required   | -                  |
| `bankName`             | Required | Required   | -                  |
| `accountNumber`        | Required | Required   | -                  |
| `phoneNumber`          | -        | -          | Required           |

#### Mexico (MXN)

| Field                  | `mx_spei` |
| ---------------------- | --------- |
| `beneficiaryFirstName` | Required  |
| `beneficiaryLastName`  | Required  |
| `beneficiaryEmail`     | Required  |
| `beneficiaryAddress`   | Required  |
| `bankName`             | Required  |
| `clabeNumber`          | Required  |

#### Brazil (BRL)

| Field                  | `br_pix` | `br_ted` |
| ---------------------- | -------- | -------- |
| `beneficiaryFirstName` | Required | Required |
| `beneficiaryLastName`  | Required | Required |
| `beneficiaryEmail`     | Required | Required |
| `beneficiaryAddress`   | Required | Required |
| `taxNumber` (CPF/CNPJ) | Required | Required |
| `bankName`             | -        | Required |
| `bankCode`             | -        | Required |
| `branchCode`           | -        | Required |
| `accountNumber`        | -        | Required |
| `pixCode`              | Required | -        |

<Info>
  Additional fields may be required depending on the transfer amount or specific
  compliance requirements. Contact your Rain representative for detailed
  requirements.
</Info>

## Step 2: create a quote

Once you have a payment account, request a quote to see the exchange rate, fees, and destination amount.

### Request

```bash theme={null}
curl -X POST https://api.rain.xyz/v1/quotes \
  -H "Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "userId": "ee778224-43fd-4c27-ba26-aa5e8401d0dd",
    "source": {
      "amount": "100",
      "currency": "usdc",
      "rail": "base"
    },
    "destination": {
      "currency": "cop",
      "rail": "co_ach",
      "address": {
        "type": "paymentAccount",
        "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
      }
    }
  }'
```

<Info>
  You can optionally specify a source `address` if you know which wallet will be
  sending the funds. Some rails may require this information.
</Info>

### Response

```json theme={null}
{
  "id": "8a2b3c4d-5e6f-7890-abcd-ef1234567890",
  "source": {
    "amount": "100",
    "currency": "usdc",
    "rail": "base"
  },
  "destination": {
    "amount": "415000",
    "currency": "cop",
    "rail": "co_ach",
    "address": {
      "type": "paymentAccount",
      "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
    }
  },
  "exchangeRate": 4200,
  "fees": {
    "rain": {
      "currency": "usdc",
      "amount": "1",
      "amountUSD": "1"
    },
    "sender": {
      "currency": "usdc",
      "amount": "0.5",
      "amountUSD": "0.5"
    }
  },
  "expiresAt": "2025-01-15T10:45:00Z"
}
```

### Quote fields

| Field                | Description                                                                      |
| -------------------- | -------------------------------------------------------------------------------- |
| `id`                 | Unique identifier for the quote; pass this as `quoteId` when creating a transfer |
| `source.amount`      | The amount of crypto the user sends                                              |
| `destination.amount` | The amount of fiat the user receives                                             |
| `exchangeRate`       | The conversion rate (fiat per crypto unit), excluding fees                       |
| `fees.rain`          | The fee charged to your platform                                                 |
| `fees.sender`        | The fee you charge to the end user (configurable)                                |
| `expiresAt`          | When this quote expires; create the transfer before this time                    |

<Warning>
  Quotes expire after a short period (typically 15 minutes). If the quote
  expires, you must create a new quote before creating a transfer.
</Warning>

## Step 3: create a transfer

If the user is happy with the exchange rate and fees, create a transfer using the quote.

### Request

```bash theme={null}
curl -X POST https://api.rain.xyz/v1/transfers \
  -H "Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "idempotency-key: unique-request-id-12345" \
  -d '{
    "quoteId": "8a2b3c4d-5e6f-7890-abcd-ef1234567890",
    "refundAddress": "0x1234567890abcdef1234567890abcdef12345678",
    "transferMessage": "Payment for services"
  }'
```

### Request parameters

| Parameter         | Type   | Required    | Description                                                                                                                                                                |
| ----------------- | ------ | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `quoteId`         | string | Yes         | The ID of a valid, unexpired quote                                                                                                                                         |
| `refundAddress`   | string | Conditional | The onchain address Rain returns funds to if the transfer fails or is refunded. Required whenever the transfer's source is a crypto rail; not applicable to a fiat source. |
| `transferMessage` | string | No          | Optional message for the transfer. Must contain only ASCII alphanumeric characters and spaces. Maximum 50 characters.                                                      |

### Headers

| Header            | Required | Description                                                                                                                                  |
| ----------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `Api-Key`         | Yes      | Your API key                                                                                                                                 |
| `Content-Type`    | Yes      | Must be `application/json`                                                                                                                   |
| `idempotency-key` | Yes      | A unique identifier for the request to ensure idempotent processing. Use this to safely retry requests without creating duplicate transfers. |

<Info>
  Reusing an `idempotency-key` with a different request body returns a `409` conflict. Generate a new key for each distinct transfer.
</Info>

### Response

The response includes the transfer details along with a `depositAddress` where the user sends their stablecoin:

```json theme={null}
{
  "id": "1234abcd-5678-4901-9ef0-23456789abcd",
  "type": "transfer",
  "transfer": {
    "userId": "11111111-1111-1111-1111-111111111111",
    "source": {
      "amount": "100",
      "currency": "usdc",
      "rail": "base"
    },
    "destination": {
      "amount": "415000",
      "currency": "cop",
      "rail": "co_ach",
      "address": {
        "type": "paymentAccount",
        "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
      }
    },
    "exchangeRate": 4200,
    "fees": {
      "rain": {
        "currency": "usdc",
        "amount": "1",
        "amountUSD": "1"
      },
      "sender": {
        "currency": "usdc",
        "amount": "0.5",
        "amountUSD": "0.5"
      }
    },
    "quoteId": "8a2b3c4d-5e6f-7890-abcd-ef1234567890",
    "status": "pending",
    "createdAt": "2025-01-15T10:40:00Z",
    "updatedAt": "2025-01-15T10:40:00Z",
    "expiresAt": "2025-01-15T11:40:00Z",
    "depositAddress": {
      "type": "onchain",
      "address": "0xabcdef1234567890abcdef1234567890abcdef12"
    }
  }
}
```

### Response fields

| Field                                 | Description                                                                                                                   |
| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `userId` / `companyId`                | Identifies the owner of the transfer. `userId` for consumer accounts, `companyId` for corporate accounts. Mutually exclusive. |
| `source.amount`, `destination.amount` | The crypto amount sent and fiat amount received, same as on the quote                                                         |
| `exchangeRate`, `fees`                | Locked in from the quote and unchanged for the life of the transfer                                                           |
| `quoteId`                             | The quote this transfer was created from                                                                                      |
| `status`                              | Current state of the transfer. See [Transfer statuses](#transfer-statuses) below.                                             |
| `createdAt`, `updatedAt`              | When the transfer was created and last updated                                                                                |
| `expiresAt`                           | When the transfer expires if funds aren't deposited in time                                                                   |
| `depositAddress`                      | The onchain address the user sends stablecoin to                                                                              |

<Warning>
  The `depositAddress` is unique to this transfer. Only send the exact source
  amount of the specified currency (for example, 100 USDC on Base) to this
  address. Sending other tokens or incorrect amounts may result in failed
  transfers or loss of funds.
</Warning>

## Alternative: fund a transfer from fiat

Instead of funding a transfer with crypto, you can quote a fiat source such as `wire` or `ach`. The transfer then comes back with a fiat `depositAddress` containing bank deposit instructions, and you send the source amount there instead of to an onchain address.

### Quote request (fiat source)

```bash theme={null}
curl -X POST https://api.rain.xyz/v1/quotes \
  -H "Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "source": {
      "amount": "100",
      "currency": "usd",
      "rail": "wire"
    },
    "destination": {
      "currency": "cop",
      "rail": "co_ach",
      "address": {
        "type": "paymentAccount",
        "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
      }
    }
  }'
```

### Quote response (fiat source)

```json theme={null}
{
  "id": "9b3c4d5e-6f7a-8901-bcde-f23456789012",
  "source": {
    "amount": "100",
    "currency": "usd",
    "rail": "wire"
  },
  "destination": {
    "amount": "415000",
    "currency": "cop",
    "rail": "co_ach",
    "address": {
      "type": "paymentAccount",
      "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
    }
  },
  "exchangeRate": 4200,
  "fees": {
    "rain": {
      "currency": "usd",
      "amount": "1",
      "amountUSD": "1"
    },
    "sender": {
      "currency": "usd",
      "amount": "0.5",
      "amountUSD": "0.5"
    }
  },
  "expiresAt": "2025-01-15T10:45:00Z"
}
```

### Transfer request (fiat source)

```bash theme={null}
curl -X POST https://api.rain.xyz/v1/transfers \
  -H "Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "idempotency-key: unique-request-id-67890" \
  -d '{
    "quoteId": "9b3c4d5e-6f7a-8901-bcde-f23456789012",
    "transferMessage": "Payment for services"
  }'
```

### Transfer response (fiat source)

```json theme={null}
{
  "id": "5678efab-1234-46ab-8def-0123456789ab",
  "type": "transfer",
  "transfer": {
    "companyId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "source": {
      "amount": "100",
      "currency": "usd",
      "rail": "wire"
    },
    "destination": {
      "amount": "415000",
      "currency": "cop",
      "rail": "co_ach",
      "address": {
        "type": "paymentAccount",
        "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
      }
    },
    "exchangeRate": 4200,
    "fees": {
      "rain": {
        "currency": "usd",
        "amount": "1",
        "amountUSD": "1"
      },
      "sender": {
        "currency": "usd",
        "amount": "0.5",
        "amountUSD": "0.5"
      }
    },
    "quoteId": "9b3c4d5e-6f7a-8901-bcde-f23456789012",
    "status": "pending",
    "depositAddress": {
      "type": "fiat",
      "beneficiaryName": "Rain Liquidity LLC",
      "beneficiaryBankName": "SSB BANK",
      "accountNumber": "1234567890",
      "routingNumber": "043087080",
      "transferMessage": "RAIN-8A2B3C4D"
    },
    "createdAt": "2025-01-15T10:40:00Z",
    "updatedAt": "2025-01-15T10:40:00Z",
    "expiresAt": "2025-01-15T11:40:00Z"
  }
}
```

<Info>
  A fiat-source transfer returns a `depositAddress` with `type: "fiat"`: the
  bank deposit instructions to fund it. Send exactly the source amount, and
  include the `transferMessage` reference with your payment so Rain can match
  the funds to the transfer. A fiat source doesn't need a `refundAddress`.
</Info>

## What happens when crypto is deposited

When a user sends stablecoin to the deposit address:

1. **Crypto received:** Rain detects the incoming stablecoin transfer.
2. **Transfer created:** a transaction of type `transfer` is created, and a `transactionTransfer` webhook with action `created` is sent.
3. **Validation:** the amount and currency are validated against the transfer.
4. **Conversion:** stablecoin is converted to the destination fiat currency.
5. **Status updates:** as the transfer progresses, `transactionTransfer` webhooks with action `updated` are sent for status changes.
6. **Fiat transfer:** funds are sent to the user's bank account via the specified rail.
7. **Transfer completed:** the transfer settles, and a `transactionTransfer` webhook with action `completed` is sent.

### Webhooks

Rain sends webhooks during the transfer lifecycle to keep you informed of status changes:

| Resource              | Action      | Description                                                  |
| --------------------- | ----------- | ------------------------------------------------------------ |
| `transactionTransfer` | `created`   | Sent when the transfer is initiated after crypto is received |
| `transactionTransfer` | `updated`   | Sent when the transfer status changes during processing      |
| `transactionTransfer` | `completed` | Sent when the fiat transfer settles at the destination bank  |

See the [transfer object reference](/docs/transaction#transfer-object-reference) for the full webhook payload schema.

### Transfer statuses

| Status              | Description                                                            |
| ------------------- | ---------------------------------------------------------------------- |
| `pending`           | Transfer created, waiting for crypto deposit                           |
| `awaiting_transfer` | Transfer initialized, awaiting funds to be sent to the deposit address |
| `processing`        | Funds received, conversion and fiat transfer in progress               |
| `pending_review`    | Transfer is held for review before it continues processing             |
| `settled`           | Fiat successfully delivered to destination                             |
| `failed`            | Transfer failed (see error details)                                    |
| `cancelled`         | Transfer was cancelled before completion                               |
| `expired`           | Transfer expired before funds were deposited                           |
| `refunded`          | Funds were returned to the sender                                      |

### Query transfers

You can query all transfer transactions via the [Transactions API](/reference/transactions/get-all-transactions). Filter by `type=transfer` to see transfer transactions:

```bash theme={null}
curl -X GET "https://api.rain.xyz/v1/issuing/transactions?type=transfer" \
  -H "Api-Key: YOUR_API_KEY"
```

Or get a specific transaction by ID:

```bash theme={null}
curl -X GET "https://api.rain.xyz/v1/issuing/transactions/1234abcd-5678-4901-9ef0-23456789abcd" \
  -H "Api-Key: YOUR_API_KEY"
```

### Cancel a transfer

To cancel a transfer that hasn't completed, call `DELETE /v1/transfers/{transactionId}` with the transaction ID the create call returned:

```bash theme={null}
curl -X DELETE https://api.rain.xyz/v1/transfers/1234abcd-5678-4901-9ef0-23456789abcd \
  -H "Api-Key: YOUR_API_KEY"
```

A successful cancel returns `200` with the transfer, its `status` now `cancelled`. Once a transfer passes the point where it can be stopped, the call returns `409` with `Transfer can no longer be cancelled`.

## Supported configurations

### Source (crypto)

| Currency | Rails (Chains)                                                       |
| -------- | -------------------------------------------------------------------- |
| USDC     | Ethereum, Base, Polygon, Arbitrum, Optimism, Avalanche, Celo, Solana |
| RUSD     | Ethereum, Base, Polygon, Arbitrum, Optimism, Avalanche, Celo, Solana |

### Source (fiat)

| Currency | Rails         |
| -------- | ------------- |
| USD      | `ach`, `wire` |

### Destination (fiat)

| Currency | Country  | Rails                                    |
| -------- | -------- | ---------------------------------------- |
| COP      | Colombia | `co_ach`, `co_bre_b`, `co_mobile_wallet` |
| MXN      | Mexico   | `mx_spei`                                |
| BRL      | Brazil   | `br_pix`, `br_ted`                       |

<Info>
  Additional currencies and rails are being added regularly. Contact your Rain
  representative for the latest supported configurations.
</Info>

## Error handling

### Quote errors

| Error                       | Description                                                  | Resolution                             |
| --------------------------- | ------------------------------------------------------------ | -------------------------------------- |
| `quote_expired`             | The quote has expired                                        | Create a new quote                     |
| `unsupported_currency_pair` | The source/destination currency combination is not supported | Check supported configurations above   |
| `invalid_amount`            | The amount is outside allowed limits                         | Verify minimum/maximum transfer limits |
| `payment_account_not_found` | The specified payment account doesn't exist                  | Verify the payment account ID          |

### Transfer errors

| Error                      | HTTP Status | Description                                                                    | Resolution                                                                                  |
| -------------------------- | ----------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------- |
| `quote_expired`            | 400         | The quote used for this transfer has expired                                   | Create a new quote and transfer                                                             |
| `invalid_deposit`          | 400         | The deposited amount doesn't match the expected amount                         | Ensure the exact amount is sent                                                             |
| `transfer_expired`         | 400         | The transfer expired before funds were deposited                               | Create a new quote and transfer                                                             |
| `compliance_failed`        | 400         | The transfer failed compliance checks                                          | Contact support for details                                                                 |
| `TransferPersistenceError` | 503         | The transfer was initiated but could not be persisted due to a temporary issue | Don't retry immediately. The transfer processes asynchronously and completes automatically. |

<Info>
  If you receive a `503` response with `TransferPersistenceError`, your transfer has already been initiated with the payment provider. Rain automatically reconciles the transfer asynchronously. Don't retry the request, since this could result in duplicate transfers. Use the `idempotency-key` header to safely handle retries in case of network timeouts.
</Info>

## Best practices

1. **Use idempotency keys:** always include a unique `idempotency-key` header with each transfer request to prevent duplicate transfers on retries.
2. **Display quote details:** show users the exchange rate, fees, and destination amount before they confirm the transfer.
3. **Handle expiration:** implement logic to refresh quotes if the user takes too long to confirm.
4. **Monitor webhooks:** set up webhook handlers to track transfer status in real time.
5. **Validate bank details:** create payment accounts during user onboarding to catch errors early.
6. **Provide clear instructions:** make sure users understand they must send the exact amount to the deposit address.
7. **Handle 503 responses:** if you receive a 503 error, don't retry immediately. The transfer may have been initiated and is reconciled automatically.

## What's next

<Columns cols={2}>
  <Card title="Set up onramps" icon="arrow-down-to-line" href="/docs/onramps">
    Enable fiat deposits into crypto.
  </Card>

  <Card title="Set up offramps" icon="arrow-up-from-line" href="/docs/offramps">
    Enable fiat withdrawals from crypto via payment routes.
  </Card>

  <Card title="Payment accounts" icon="building-columns" href="/docs/payment-accounts">
    See the full request, response, and field reference for registering a bank account.
  </Card>

  <Card title="Set up webhooks" icon="bell" href="/docs/webhooks">
    Receive transfer notifications as they happen.
  </Card>
</Columns>
