---
title: "Convert USDT and USDC"
description: "Preview the price, then convert USDT to USDC or back between two wallets of the same customer."
---
Use this recipe to preview the price, then convert USDT to USDC, or USDC to USDT, between two wallets of the same customer.

**Who it is for.** A treasury app whose customer received USDT on Ethereum but needs USDC on Polygon to pay suppliers.

**Status.** Available between two wallets of the same customer. No conversion has completed in production yet, so **always preview the price first**. Gravv does not support Tron. You cannot convert and send to someone else in one call. Convert first, then [send](/recipes/remittance-and-crypto/send-stablecoins-to-an-outside-wallet).

## Money example

1. The customer converts 2.00 USDC in a Polygon wallet into USDT in their Ethereum wallet.
2. The preview shows a fee of 1.501737 USDC and a receive amount of 0.498263 USDT.
3. 2.00 USDC leaves the Polygon wallet, and about 0.498 USDT arrives. The fee comes out of the amount that arrives. Gravv never charges it on top.
4. Part of the fee works like a fixed cost, so preview the price first and convert larger amounts.

## Before you start

- Both wallets are Gravv wallets (`internal_crypto_wallet`) of the **same customer**, and that customer is the `customer_id` you send ([`GET /v1/wallets/{wallet_id}`](/api-reference/wallets/get-v1-wallets-wallet-id) shows `customer_id`).
- The source wallet holds the asset you send.
- The amount has at most 2 decimal places.

## Find out what is required

Get a price first with [`GET /v1/transfer/rates`](/api-reference/transfer/get-v1-transfer-rates) in preview mode. A preview reserves and charges nothing.

<CodeGroup>
```bash title="cURL"
curl --request GET \
     --url 'https://api.gravv.xyz/v1/transfer/rates?source_wallet_id=b6705e3a-3c6c-49b0-b24f-d55aafd03635&destination_wallet_id=5485cc5d-3d4b-4b17-9033-95e408405f03&send_asset=USDC&receive_asset=USDT&amount=2.00' \
     --header 'Api-Key: <API_KEY>'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v1/transfer/rates?source_wallet_id=b6705e3a-3c6c-49b0-b24f-d55aafd03635&destination_wallet_id=5485cc5d-3d4b-4b17-9033-95e408405f03&send_asset=USDC&receive_asset=USDT&amount=2.00", {
  method: "GET",
  headers: {
    "Api-Key": process.env.GRAVV_API_KEY,
  },
});
console.log(response.status, await response.json());
```
```python title="Python"
import os

import requests

response = requests.get(
    "https://api.gravv.xyz/v1/transfer/rates?source_wallet_id=b6705e3a-3c6c-49b0-b24f-d55aafd03635&destination_wallet_id=5485cc5d-3d4b-4b17-9033-95e408405f03&send_asset=USDC&receive_asset=USDT&amount=2.00",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

```json title="Response"
{
  "data": {
    "items": [
      {
        "currency_pair": "USDC_USDT",
        "from_currency": "USDC",
        "to_currency": "USDT",
        "rate": "0.249132",
        "direction": "USDC_USDT",
        "timestamp": "2026-10-12T13:40:00Z",
        "send_amount": "2",
        "fee": "1.501737",
        "fee_currency": "USDC",
        "receive_amount": "0.498263",
        "min_receive_amount": "0.498263",
        "source_network": "polygon",
        "destination_network": "ethereum",
        "expires_at": "2026-10-12T13:41:00Z"
      }
    ]
  },
  "error": null
}
```

`receive_amount` is the amount that should arrive, and `min_receive_amount` is the lowest amount that can arrive. `fee` is in the asset you send. The preview is not binding. Gravv fetches the price again when you send the transfer.

## Steps

### 1. Convert

Send a transfer whose `destination.crypto_asset` differs from the asset you send. That difference makes the transfer a conversion.

<CodeGroup>
```bash title="cURL"
curl --request POST \
     --url https://api.gravv.xyz/v1/transfer \
     --header 'Api-Key: <API_KEY>' \
     --header 'Content-Type: application/json' \
     --header 'Idempotency-Key: 335c34c9-1739-428f-9bcc-478645b1c8e5' \
     --data '
{
  "customer_id": "c7ae0522-6a4d-476f-a43c-5a9aac863c31",
  "source": {
    "source_type": "internal_crypto_wallet",
    "id": "b6705e3a-3c6c-49b0-b24f-d55aafd03635",
    "crypto_asset": "USDC"
  },
  "destination": {
    "destination_type": "internal_crypto_wallet",
    "id": "5485cc5d-3d4b-4b17-9033-95e408405f03",
    "crypto_asset": "USDT"
  },
  "amount": "2.00",
  "description": "Convert USDC to USDT"
}
'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v1/transfer", {
  method: "POST",
  headers: {
    "Api-Key": process.env.GRAVV_API_KEY,
    "Content-Type": "application/json",
    "Idempotency-Key": "335c34c9-1739-428f-9bcc-478645b1c8e5",
  },
  body: JSON.stringify({
    "customer_id": "c7ae0522-6a4d-476f-a43c-5a9aac863c31",
    "source": {
      "source_type": "internal_crypto_wallet",
      "id": "b6705e3a-3c6c-49b0-b24f-d55aafd03635",
      "crypto_asset": "USDC"
    },
    "destination": {
      "destination_type": "internal_crypto_wallet",
      "id": "5485cc5d-3d4b-4b17-9033-95e408405f03",
      "crypto_asset": "USDT"
    },
    "amount": "2.00",
    "description": "Convert USDC to USDT"
  }),
});
console.log(response.status, await response.json());
```
```python title="Python"
import os

import requests

response = requests.post(
    "https://api.gravv.xyz/v1/transfer",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
        "Content-Type": "application/json",
        "Idempotency-Key": "335c34c9-1739-428f-9bcc-478645b1c8e5",
    },
    json={
        "customer_id": "c7ae0522-6a4d-476f-a43c-5a9aac863c31",
        "source": {
            "source_type": "internal_crypto_wallet",
            "id": "b6705e3a-3c6c-49b0-b24f-d55aafd03635",
            "crypto_asset": "USDC",
        },
        "destination": {
            "destination_type": "internal_crypto_wallet",
            "id": "5485cc5d-3d4b-4b17-9033-95e408405f03",
            "crypto_asset": "USDT",
        },
        "amount": "2.00",
        "description": "Convert USDC to USDT",
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

```json title="Response"
{
  "data": {
    "amount": "2",
    "amount_currency": "USDC",
    "reference": "f3a9c1d7-2b4e-4c6a-9d8f-0e1b2c3d4e5f",
    "date_created": "2026-10-12T13:42:10.000Z",
    "transfer_status": "pending",
    "transaction_id": "a7c3e9f1-4d2b-4a8c-b6e0-1f2a3b4c5d6e",
    "local_amount": null,
    "local_currency": null,
    "fee": "1.501737",
    "fee_currency": "USDC",
    "requires_approval": false,
    "receive_amount": "0.498263",
    "min_receive_amount": "0.498263",
    "receive_asset": "USDT"
  },
  "error": null
}
```

Compare `receive_amount` with the preview, and check that it is still acceptable to you.

### 2. Wait for it

Wait for the `transfer.status.completed` webhook. It gives the amount that arrived (`receive_amount`) and the amount the converter kept (`provider_fee`). As a backup, poll [`GET /v1/transactions/a7c3e9f1-4d2b-4a8c-b6e0-1f2a3b4c5d6e`](/api-reference/transactions/get-v1-transactions-transaction-id).

The transaction shows no fee for a conversion. You find the fee in the transfer response and the webhooks.

### 3. Pay out

Send the converted funds from the receiving wallet as in [Send stablecoins to an outside wallet](/recipes/remittance-and-crypto/send-stablecoins-to-an-outside-wallet).

## What can go wrong

| Response | Why | Fix |
| :-- | :-- | :-- |
| 400 `Both wallets must belong to the same customer to convert USDT to USDC.` | The wallets belong to different customers, the destination is not a Gravv wallet, or `customer_id` is not the wallets' owner | Convert between one customer's wallets, then send to others |
| 400 `Only USDT and USDC can be converted.` | You sent another asset | Use USDT or USDC |
| 400 `Converting USDT on polygon to USDC on solana isn't available.` | Gravv does not offer that route right now | Try another pair of networks |
| 400 `This wallet can't be used for conversions yet.` | The wallet is not set up for conversions | Use another wallet, or contact support |
| 400 `The amount is too small to convert. Try a larger amount.` | The fee would take the whole amount | Convert a larger amount |
| 400 `The amount is too large to convert. Try a smaller amount.` | The amount is above the converter's limit | Split it into smaller conversions |
| 400 `This amount can't be converted. Try a different amount.` | The converter refused the amount | Change the amount |
| 400 `We couldn't get a price right now. Please try again shortly.` | No price is available | Retry later with a new idempotency key |
| 400 `Use at most 2 decimal places.` | The amount has more than 2 decimal places, such as `2.005` | Round to 2 decimal places |
| 400 `receive_asset must differ from the asset sent.` (preview) | Both sides use the same asset | Pick the other asset |
| 400 `source_wallet_id and destination_wallet_id are required to price a conversion.` (preview) | A wallet id is missing | Send both |
| `transfer.status.failed` with `refunded_amount` and `refund_asset` in `event_data` | The conversion did not go through. The asset came back, possibly minus the amount the converter kept (`provider_fee`) | Check the refund in the source wallet, then retry |

## Webhooks you will receive

1. `transfer.status.pending`, with `workflow_type` `stablecoin_conversion`. Its `event_data` carries `send_asset`, `amount_sent`, `receive_asset`, `receive_amount`, `min_receive_amount` and `provider_fee`.
2. Then one of these:
   - `transfer.status.completed`, with the actual `receive_amount` and `provider_fee`.
   - `transfer.status.failed`, with `refunded_amount` and `refund_asset`.

## Testing in sandbox

You cannot test a conversion in sandbox. There is no test converter, so both the preview and the conversion return `We couldn't get a price right now. Please try again shortly.`

The request checks still run first. So in sandbox you can still see `receive_asset must differ from the asset sent.`, `Use at most 2 decimal places.` and the same-customer error.

## Reference

- [Get rates](/api-reference/transfer/get-v1-transfer-rates)
- [Create a transfer](/api-reference/transfer/post-v1-transfer)
- [Crypto transfers](/platform/transfers/crypto-transfers)
