---
updatedAt: 2026-08-17T19:00:17.000Z
agentTools:
  projectIndex: https://docs.moderntreasury.com/payments/llms.txt
---

# Stablecoin Off-ramp

Stablecoin payments use the same primitives as fiat:

* Legal Entities to onboard end users
* Internal Accounts to hold USD and stablecoin balances
* Counterparties to represent external wallets and bank accounts
* Payment Orders to move funds across rails.

Stablecoin-to-USD conversions are triggered via Payment Order type: `book transfers` .  Use webhooks to track lifecycle events.

***

## Recommended implementation order

Build in this sequence:

1. Onboard end users by creating Legal Entities and waiting for KYC/KYB approval.
2. Open USD and stablecoin Internal Accounts for each approved entity.
3. Register counterparties with external bank accounts for fiat and wallet addresses for stablecoin.
4. Build the off-ramp: receive stablecoins, convert to USD, and pay out via a fiat rail.
5. Subscribe to webhooks for Payment Order and Legal Entity lifecycle events.

***

## 1. Onboard end users

Every entity that holds funds on your platform needs a verified Legal Entity. For required fields and documents, see:

* [Onboard a Business](https://docs.moderntreasury.com/payments/docs/onboard-a-business)
* [Onboard an Individual](https://docs.moderntreasury.com/payments/docs/onboard-an-individual)

### Create a business or individual Legal Entity

The example below creates a business Legal Entity with a control person. In production, also include beneficial owners, required documents (articles of incorporation and proof of address), and any additional identifications. See the onboarding guide above for the complete field list.

```bash Create a business legal entity
curl --request POST \
  -u ORGANIZATION_ID:API_KEY \
  --url https://app.moderntreasury.com/api/legal_entities \
  -H 'content-type: application/json' \
  -d '{
    "legal_entity_type": "business",
    "business_name": "Acme Payments Inc.",
    "legal_structure": "corporation",
    "business_description": "Online marketplace for handmade goods",
    "addresses": [
      {
        "address_types": ["business"],
        "primary": true,
        "line1": "100 Market Street",
        "locality": "San Francisco",
        "region": "CA",
        "postal_code": "94105",
        "country": "US"
      }
    ],
    "date_formed": "2020-06-15",
    "expected_activity_volume": 100000000,
    "intended_use": "Accepting payments from international customers",
    "identifications": [
      {
        "id_type": "us_ein",
        "issuing_country": "US",
        "id_number": "123456789"
      }
    ],
    "documents": [
      {
        "document_type": "proof_of_address",
        "file_data": "<base64 encoded bytes>",
        "filename": "proof_of_address.pdf"
      }
    ]
  }'
```

### Wait for approval

Wait for the `activated` webhook before you open accounts. In production, Modern Treasury may manually review legal entities, so ask your account manager for expected SLAs. In sandbox, legal entities activate automatically.

```json
{
  "event": "activated",
  "data": {
    "id": "<LEGAL_ENTITY_ID>",
    "status": "active",
    "legal_entity_type": "business"
  }
}
```

***

## 2. Open accounts

After the Legal Entity is approved, create Internal Accounts for USD and stablecoin balances. Each Internal Account is a programmable sub-account with its own account and routing number (for USD) or blockchain address (for stablecoins).

You will provision one USD Internal Account and one stablecoin Internal Account for the end user. The USD account will be created using your customer's `legal_entity_id` and the stablecoin account (USDC, USDG, etc.) will be created using your own organization's `legal_entity_id`

### Create a C2 USD Internal Account

```bash Create USD Internal Account
curl --request POST \
  -u ORGANIZATION_ID:API_KEY \
  --url https://app.moderntreasury.com/api/internal_accounts \
  -H 'content-type: application/json' \
  -d '{
    "name": "Acme Payments USD Account",
    "party_name": "Acme Payments Inc.",
    "currency": "USD",
    "legal_entity_id": "<CUSTOMER_LEGAL_ENTITY_ID>"
  }'
```

### Retrieved Organization (C1) Legal Entity ID

Use your Organization Legal Entity ID to provision the stablecoin Internal Account:

```bash Retrieve Org Legal Entity ID
curl --request GET \
  -u ORGANIZATION_ID:API_KEY \
  --url 'https://app.moderntreasury.com/api/legal_entities?business_name=<ORGANIZATION_NAME>' \
  -H 'content-type: application/json'
```

These details are also available within the legal entities UI in the Modern Treasury app.

### Create a stablecoin Internal Account

```bash Create stablecoin Internal Account
curl --request POST \
  -u ORGANIZATION_ID:API_KEY \
  --url https://app.moderntreasury.com/api/internal_accounts \
  -H 'content-type: application/json' \
  -d '{
    "name": "Acme Payments USDC Account",
    "party_name": "Acme Payments Inc.",
    "currency": "USDC",
    "legal_entity_id": "<ORGANIZATION_ENTITY_ID>",
    "requested_account_number_types": ["ethereum_address"]
  }'
```

The stablecoin Internal Account is created in `pending_activation` while the requested address is provisioned.

## Blockchain Network Selection

The `requested_account_number_types` value determines the blockchain network for the provisioned address. For example, `ethereum_address` provisions an EVM-compatible address. To use another network, request the matching `account_number_type`.

See [Supported stablecoins & blockchain networks](https://docs.moderntreasury.com/payments/docs/stablecoins#supported-stablecoins--blockchain-networks) before you create the counterparty.

<br />

```bash
curl --request GET \
  -u ORGANIZATION_ID:API_KEY \
  --url https://app.moderntreasury.com/api/internal_accounts/<ORG_USDC_INTERNAL_ACCOUNT_ID>
```

```json Stablecoin IA Response (Pending Activation)
{
    "id": "<ORG_USDC_INTERNAL_ACCOUNT_ID>",
    "object": "internal_account",
    "party_name": "Acme Payments Inc.",
    "name": "Acme Payments USDC Account",
    "currency": "USDC",
    "legal_entity_id": "5d95643d-1127-4a7c-9ef5-ad21a1d007c6",
    "account_details": [],
    "status": "pending_activation"
}
```

After the `internal_account.activated` webhook, the requested Ethereum address appears in `account_details`:

```json Stablecoin IA Response (After Activation)
{
    "id": "<ORG_USDC_INTERNAL_ACCOUNT_ID>",
    "object": "internal_account",
    "party_name": "Acme Payments Inc.",
    "name": "Acme Payments USDC Account",
    "currency": "USDC",
    "legal_entity_id": "5d95643d-1127-4a7c-9ef5-ad21a1d007c6",
    "account_details": [
        {
            "account_number": "<ETHEREUM_ADDRESS>",
            "account_number_type": "ethereum_address"
        }
    ],
    "status": "active"
}
```

***

## 3. Set up counterparties

For the off-ramp flow, create a fiat counterparty for the customer:

### Create a bank counterparty

```bash Create fiat counterparty
curl --request POST \
  -u ORGANIZATION_ID:API_KEY \
  --url https://app.moderntreasury.com/api/counterparties \
  -H 'content-type: application/json' \
  -d '{
    "name": "Acme Payments",
    "legal_entity_id": "<CUSTOMER_ENTITY_ID>",
    "accounts": [
      {
        "account_type": "checking",
        "routing_details": [
          {
            "routing_number_type": "aba",
            "routing_number": "<ROUTING_NUMBER>"
          }
        ],
        "account_details": [
          { "account_number": "<ACCOUNT_NUMBER>" }
        ]
      }
    ]
  }'
```

***

## 4. Off-ramp: receive stablecoins, convert to USD, and pay out

![](https://files.readme.io/4169aa3c614d12bbb8e535c0ebcc2433a2590840a7c287ed37467983d908bd08-image.png)

Complete the off-ramp in four steps: receive stablecoins, convert them to USD, wait for settlement, and send the fiat payout.

### Step A: Receive stablecoins

Your stablecoin Internal Account has a unique blockchain address for the requested network. External parties can send stablecoins directly to that address.

To retrieve the blockchain address:

```bash
curl --request GET \
  -u ORGANIZATION_ID:API_KEY \
  --url https://app.moderntreasury.com/api/internal_accounts/<ORG_USDC_INTERNAL_ACCOUNT_ID>
```

Share the relevant address with your counterparty. Inbound transfers appear as Incoming Payment Detail to your stablecoin Internal Account.

```json Example Incoming stablecoin webhook
{
  "event": "completed",
  "data": {
    "id": "<INCOMING_PAYMENT_DETAIL_ID>",
    "object": "incoming_payment_detail",
    "type": "stablecoin",
    "amount": 1000000,
    "currency": "USDC",
    "direction": "credit",
    "status": "completed",
    "internal_account_id": "<ORG_USDC_INTERNAL_ACCOUNT_ID>",
    "originating_account_number": "0x1234567890abcdef1234567890abcdef12343f9a",
    "originating_account_number_safe": "3f9a",
    "originating_account_number_type": "ethereum_address"
  }
}
```

### Step B: Convert stablecoins to USD (book transfer)

Represent stablecoin-to-USD conversions as book transfers between Internal Accounts.

```bash
curl --request POST \
  -u ORGANIZATION_ID:API_KEY \
  --url https://app.moderntreasury.com/api/payment_orders \
  -H 'content-type: application/json' \
  -d '{
    "type": "book",
    "amount": 100000,
    "direction": "credit",
    "currency": "USD",
    "originating_account_id": "<ORG_USDC_INTERNAL_ACCOUNT_ID>",
    "receiving_account_id": "<ORG_USD_INTERNAL_ACCOUNT_ID>"
  }'
```

### Step C: Wait for settlement

Wait for the `payment_order.completed` webhook before you initiate the fiat payout:

```json
{
  "event": "completed",
  "data": {
    "id": "<PAYMENT_ORDER_ID>",
    "object": "payment_order",
    "type": "book",
    "status": "completed"
  }
}
```

### Step D: Pay out via fiat rail

After the payment order completes, send USD to the counterparty's external bank account:

```bash
curl --request POST \
  -u ORGANIZATION_ID:API_KEY \
  --url https://app.moderntreasury.com/api/payment_orders \
  -H 'content-type: application/json' \
  -d '{
    "type": "ach",
    "amount": 100000,
    "direction": "credit",
    "currency": "USD",
    "originating_account_id": "<CUST_USD_INTERNAL_ACCOUNT_ID>",
    "receiving_account_id": "<BANK_EXTERNAL_ACCOUNT_ID>"
  }'
```

Use `wire` or `rtp` instead of `ach` if you need a different speed or cost profile. `rtp` payments route automatically over RTP or FedNow. See [Originating Payments](https://docs.moderntreasury.com/payments/docs/making-payments) for settlement timing and pre-funding requirements.