Off-Ramp (Stablecoin to Fiat)

Off-ramp (stablecoin to fiat)

The off-ramp flow turns an inbound stablecoin payment into a fiat payout. You register one or more remote bank accounts with account_details for the bank that receives the converted fiat. The user or corporate always sends supported stablecoin to the receiving_address for that account (returned when you create it). Bakkt converts and settles fiat to the same account_details.

Prerequisites (individuals and corporates)

  • Onboarding: POST /user or POST /corporate (see the Onboarding API).
  • KYC / KYB: The user or corporate must be allowed to transact—for individuals, that includes status FULL_USER. No off-ramp completes until then.
  • Remote bank account: account_details for a bank on the right corridor; Supported assets & currencies has rails and field context by region.
  • Where to send stablecoins: receiving_address from the Receiving address section (returned on Create where applicable).

Stablecoin-to-fiat (off-ramp) flow

The same logical steps run in production and Sandbox; use the right base URL (Quick start, Sandbox setup).

  1. Create a remote bank account (fiat destination)
    Configure the external bank account (destination) where the end user or corporate will receive the converted fiat using one of these endpoints:

    • POST /stablecoin/user/bank-account/remote
    • POST /stablecoin/corporate/{corporate_uuid}/bank-account/remote
      The request body carries account_details (for example IBAN, or U.S. account and routing, depending on the rail) for the payout. The currency in account_details must match the fiat you intend to withdraw. Set recipient_type to SELF or THIRD_PARTY to identify who the payout beneficiary is, and include any third_party_details the create request requires for third-party payees.
  2. Get the deposit address
    Use receiving_address; it is the address to send stablecoin for that remote bank account.

  1. Send stablecoin

  2. Conversion and fiat transfer (automatic)
    When stablecoin is credited, Bakkt converts and sends fiat to the remote account linked to that deposit path (including Sandbox paths described in the testing section).

  3. Status
    Webhooks use type cryptoToFiat. Example subType values include IN_PROGRESS, FIAT_TRANSFER_ISSUED, SUCCESS, ON_HOLD, FAILED, REFUNDED, and LIMIT_BREACHED—the full set is in the Webhooks guide.

Sandbox: off-ramp testing

Step 3. Send stablecoin in Sandbox does not use mainnet. Either fund receiving_address on a testnet (below) or use the simulate API (further down).

On-chain test funding (testnets)

Use a faucet or a contract’s public mint() where the contract allows it, then send the test tokens to receiving_address. The table lists test token contracts by network (many expose mint(); small native-coin top-ups and other call rules for mint() differ by chain—check the explorer for the contract you are calling).

TokenAmoy (Polygon testnet)Sepolia (Optimism testnet)Sepolia (Arbitrum testnet)Alfajores (Celo testnet)Sepolia (Ethereum testnet)Sepolia (Base testnet)Shasta (Tron testnet)Solana DevnetAvalanche testnet
USDc Native0xDc1d50x7E6FD0x7E6FD0x196FF0x7E6FD8T3Km0x7E6FD
USDc.e0xA6B680x0b6b0x0b6bb
USDT0x5A960x0ccC70x0ccC70x2F220TMbsACwdGG0x0ccC7
cUSD0x2D91e
cEUR0xa248F90x58129
EURa0x512c5

Each of these token contracts has a public mint() function you can use to mint test tokens to an address you control, then move them to receiving_address to exercise a cryptoToFiat off-ramp in Sandbox.

On Avalanche testnet, calling mint() requires sending ceil(amount) * 1 gwei in AVAX.

Simulate (API, Sandbox)

Sandbox only: call the wallet simulate routes below on the Sandbox host to fund the off-ramp without the on-chain test-token path in the previous subsection. Create a remote bank account first, then set beneficiary_uuid to the account uuid from POSTbank-account/remote.

  • Users: POST /stablecoin/user/wallet/{chain}/simulate
  • Corporates: POST /stablecoin/corporate/{corporate_uuid}/wallet/{chain}/simulate
curl --request POST \
  --url 'https://sandbox.api.bakkt.com/stablecoin/user/wallet/polygon/simulate' \
  --header 'Authorization: API-Key YOUR_SANDBOX_API_KEY' \
  --header 'accept: application/json' \
  --header 'bakkt-session-id: YOUR_SESSION_ID' \
  --header 'Content-Type: application/json' \
  --data '{
    "value": 100,
    "token": "usdc",
    "beneficiary_uuid": "REMOTE_BANK_ACCOUNT_UUID_FROM_CREATE_RESPONSE"
  }'

Examples

Use `API-Key ${apiKey}` in Authorization (same as other guides).

User: create remote account and read receiving_address

const auth = `API-Key ${apiKey}`;

const res = await fetch('https://api.bakkt.com/stablecoin/user/bank-account/remote', {
  method: 'POST',
  headers: {
    'Authorization': auth,
    'bakkt-session-id': sessionId,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    account_name: 'My EUR payout',
    main_recipient: true,
    recipient_type: 'SELF',
    account_details: { currency: 'EUR', iban: 'DE89370400440532013000' }
  })
});
if (!res.ok) return;
const { uuid, receiving_address: depositAddress } = await res.json();
// User sends supported stablecoin to `depositAddress`. Webhook type: cryptoToFiat — /guides/webhooks

Corporate: same pattern

POST /stablecoin/corporate/{corporate_uuid}/bank-account/remote, then corporate wallet endpoints as needed; account_details for USD and other rails are covered in Supported assets & currencies.

Next steps