- A liquidation address that turns incoming USDC into EUR and pays out to a Deutsche Bank account
- A liquidation address that turns incoming USDC into GBP and pays out to a Revolut account
How it works
1
Max registers his bank account and gets a liquidation address owned by MoonPay Enterprise
2
An
autoramp ties that address to his bank account: USDC in, EUR out3
Max sends 1000 USDC to the liquidation address
4
As soon as funds hit the address, MoonPay Enterprise converts them to EUR and pays out to Max’s bank account
5
Max can transfer at any time. The address is persistent.
6
Non-supported assets sent to the address are returned to sender
Two ways to offramp
Most offramp integrations start with an exact-out quote, because the payer usually owes a precise fiat figure. Both are shown below, and a single quote-source autoramp can keep accepting new quotes over time.
Prerequisites
Every step must complete before moving to the next. Sandbox-only steps are marked. Set these in your shell first. Every command on this page reuses them.1
Customer is Active
Your customer must have
Active status: terms signed and identification (KYC/KYB) approved, with nothing outstanding in required-signings. Active alone is not always enough: check that the specific rail you need is Active in the customer’s abilities too, and subscribe to the customer_fiat_abilities webhook to track rail changes instead of polling. See Onramp for a full onboarding walkthrough and Tracking ability changes for the webhook.2
Register a bank account
Register the destination bank account via
POST /api/addresses/fiat. See Fiat Addresses. The account starts in RegistrationPending status.Save the returned fiat address ID. Quotes reference it as recipient_account_id.3
Sandbox: approve the fiat address
In Sandbox, the fiat address stays in In production, registration is handled automatically.
RegistrationPending until you manually advance it:4
Register crypto addresses
Register your customer’s wallet addresses via Crypto Addresses for Travel Rule compliance. Funds from unregistered wallets may be delayed for manual review.
5
Create the offramp
Either request an exact-out quote and create a quote-source autoramp, or create a standing autoramp directly. Both are shown below.
6
Sandbox: approve the autoramp
An autoramp is created in In production, an autoramp reaches
Authorized status. In Sandbox you advance it yourself, which also provisions the mock deposit account that a simulated deposit needs:Approved on its own once its deposit account is provisioned and verified.Offramp with an exact-out quote
Use this when your customer owes a precise fiat amount. You ask for the output, MoonPay Enterprise tells you the required stablecoin input.1
Request the quote
GET /api/autoramps/quoteSet amount_out to the fiat figure you need to land in the bank. Set amount_in instead if you want to price a known stablecoin amount. Never set both.Example response
Example response
amount_in is what your customer must send: 111.19863 USDC to receive exactly 100 EUR, with the fee breakdown itemised.2
Create the autoramp from the quote
POST /api/autorampsSubmit the signed quote payload verbatim.3
Share the deposit address
Once the autoramp is
Approved, deposit_rails contains the liquidation address. Show your customer the address and the exact amount_in figure from the quote.A deposit only matches a quote when its amount_in value and source currency match. Deposits that match no active quote, or only an expired one, are returned to sender.Rate locks and expiry
rate_lock_duration_minutes is optional and clamped to the cap for the pair. Stablecoin to fiat cross-currency pairs (e.g. USDC to EUR) cap at 10 minutes; pegged pairs (a USD-stablecoin to USD, a EUR-stablecoin to EUR) cap at 2 days.
rate_expiry_policy decides what happens when funds arrive after the lock expires:
Return: validity equals the rate lock. Late funds are refunded.Slippage: validity extends beyond the lock. Late funds execute if the market rate is withinslippage_tolerance_in_bips, otherwise they are refunded or requoted.
Offramp with a standing autoramp
Use this when you want a persistent address that converts whatever arrives at the current mid-market rate, with no amount agreed up front.POST /api/autoramps
The
Idempotency-Key header is required. Send the same key to safely retry a request without creating a duplicate autoramp.Request fields
The
account_identifier inside recipient_account uses the same payment rail discriminator as Fiat Addresses: SEPA (requires iban), ACH / Wire / FEDNOW (require routing_number + account_number), SWIFT (requires bic + account_number), CHAPS / FPS (require sort_code + account_number).
Request
$AUTORAMP_ID lets you paste the remaining commands on this page straight into the same shell.
The
account_identifier you provide in recipient_account must match a bank account you’ve already registered via the Fiat Addresses API.Response
The example above is trimmed to the fields most integrations need. The full
Autoramp object also returns kind, source, recipient, customer_id, source_currencies, destination_currency, is_third_party, fee_profile_id, batch_payout, and quotes. The legacy deposit_account field is deprecated.Shared EVM deposit address. An offramp gets one deposit address per source chain, and every EVM chain on the same autoramp shares one address, so the EVM entries in
deposit_rails repeat the same string. Only the token and chain combinations listed in source_currencies are converted: a deposit arriving on an EVM chain you did not declare is not swept, even though the address matches. Declare every chain you want to accept when you create the autoramp. Non-EVM chains such as Solana still get their own distinct deposit address.Error response
Validation failures return the error as a plain string body, not a structured object. Anaccount_identifier that does not match a bank account already registered and verified for this customer returns 400:
Registered status returns 422 with "recipient account has not yet been activated".
Show the user their liquidation address along with the supported assets for it (e.g. EURC, USDC). Non-supported assets are returned to sender.
Test the full flow in Sandbox
Creating the is half the integration. Simulate a deposit to confirm your webhook handler and reconciliation logic work before you take real money.1
Simulate an incoming deposit
POST /api/sandbox/transaction builds a transaction directly from the route, reusing its currencies, customer, and recipient. amount_in, amount_out, currency_in, currency_out, and state. See Sandbox for the optional fee, fx_rate, and initial_state fields.2
Drive the transaction to Completed
The simulated transaction starts in
Pending. Advance it to trigger the rest of the status webhooks.3
Check the webhooks you received
Your endpoint should have received a
transaction event when the deposit landed, followed by transaction_status events as it moved through FundsReviewInProgress, ConversionInProgress, PayoutInProgress, and Completed.Read transaction_status, not the deprecated status field, for the current state. Payload shapes and the full status table are in Monitoring payments; signature verification is in Webhooks.On an offramp,
Completed means the payout was sent to the bank. Arrival depends on the rail: SEPA and ACH settle on their own schedules. See Transaction Status.Simulate it in the Dashboard
Register and approve the bank account first: My Address → Linked Bank Accounts in the App view, then Developer → Sandbox → Fiat Accounts and set the entry under Unverified Fiat Accounts to Registered. Until you do, the offramp has nowhere to pay out.
1
Create the route
Switch the sidebar to the App view and pick your customer in the customer selector at the top. Open Operations → Routes, then click on the card and choose .
2
Authorize the route
Go to Developer → Sandbox and open the Autoramps tab. Your new route appears under Unverified Autoramps. Set its status to Authorized using the dropdown on the right.
The dropdown offers Created, Authorized, and Rejected. Pick Authorized: that provisions the mock deposit account, which is what the simulated deposit needs. Sending
"Approved" to PUT /api/sandbox/autoramp/{id} provisions the same account and also moves the route to Approved, so use the API call if you want to see the Approved status your integration waits for.3
Create the simulated deposit
Still under Developer → Sandbox, click New Transaction in the top right. On the Create New Sandbox Transaction screen:
- Customer: search by name, email, or ID and select the customer who owns the route.
- Ramp: pick it from the dropdown. This field still uses the older wording for a route. Entries are labelled by currency pair, for example .
- Amount: enter the deposit amount in the route’s input currency.
4
Settle the transaction
Back on the Sandbox page, open the Transactions tab. Your transaction is listed under Pending Transactions with Accept and Reject buttons. Choose Accept to complete it, or use the Change state dropdown to move it to a specific state and test how your integration reacts.
5
Check the result
Open the transaction from Operations → Transactions to see amounts, fees, and . Webhooks fire exactly as they do on the API path, so this is a good way to exercise your endpoint before you write any integration code.
Move to production
Remove every
/api/sandbox/* call from your integration. Those endpoints do not exist in production.
What to read next
Quotes
Full quote reference: expiry policies, multi-currency input, attaching quotes over time
Fiat Addresses
Register bank accounts per payment rail and track registration status
Onramp
The reverse route: a named virtual account that settles into stablecoins
Transaction Status
Status mapping, settlement times, and the polling API
Webhooks
Signature verification, payload schemas, and retry behaviour
End of Day Settlement
How batched payouts and daily settlement work