Integration Guide
A step by step guide on how to enable Virtual Accounts in your app
Step One: Complete integration prerequisites
If you haven't already, please complete these prerequisite steps. If you're having any challenges, or have questions on this step please reach out to [email protected] or your Account Director.
Step Two: Build your entry points
To create a full experience, you'll need to build two native entry points within your app, the Create account
and Top up
entry points.
Create account
You'll need to build an entry point within your native app so users can open their virtual accounts. This should be a button and optional banner on your home page prompting users to open a Virtual Account. This button and banner should be displayed for users in supported regions.

Entry point for account creation.
Clicking on your Create account
entry point should generate a URL which will open in an iframe or mobile in-app browser. Use the base URL http://buy.moonpay.com/virtual-account and refer to our Mobile Integrations guide for the best way to render this URL in your app.
To create their virtual account, the end-customer will log in to the MoonPay widget and if required, complete KYC. If the user has already passed KYC, they will not need to KYC again and will proceed directly to account creation.

Currency selection and account creation.
This screen only displays if depositCurrencyCode
is not passed.
The table below lists the URL parameters supported by MoonPay Virtual Accounts. We highly recommend that you collect the user's depositCurrencyCode
and token
natively before opening the MoonPay widget in order to skip screens in the journey and reduce friction.
Parameter | Description |
---|---|
apiKey required | Your publishable API key. This is used to assign customers and transactions to your MoonPay account. |
customerId required | The MoonPay identifier for the customer. This identifier will be present whenever we pass you customer data. |
walletAddress required | The customer’s wallet address where they will receive EURC, USDC or USDT. |
depositCurrencyCode (recommended) | The fiat currency code of the virtual account. Pre-select the deposit currency code to skip the currency selection screen. Possible values are: gbp, eur, usd. |
token (recommended) | The code of the cryptocurrency the customer will receive. Possible values: usdc, usdt, eurc. |
skipAccountDetails (optional) | Optional. Defaults to false. Set to true if your app natively shows the user their virtual account details after account creation. Possible values: true, false. |
email (optional) | Optional. The user's email address to be pre-filled in the MoonPay widget. |
theme | Light and dark modes according to the themes built in your MoonPay dashboard. Possible values: light, dark. |
Top up
After a user has created their MoonPay Virtual Account, you will need a final entry point to take them directly into the top up experience. Next up, you'll learn how to build a native account details screen.
Step Three: Build account detail screen
Next, you'll build a screen in your native app to show the user's Virtual Account details so they know how to top up. Note, MoonPay can provide this screen to end-customers via the widget, but viewing details will require users to re-login which could create a higher friction experience.
View account details
To display the end-customers virtual account details natively, you can use account_created webhook notification or API in Step Seven:
- Bank routing number / IBAN
- Bank account number
- Bank name
- Bank beneficiary name
- Bank address
If your app has a native account details screen, be sure to pass ?skipAccountDetails=true
to the MoonPay widget.

Option 1: Confirm virtual account details in the MoonPay widget.
Option 2 (preferred): Confirm virtual account details natively in your app.
{
id: "123",
walletAddress: "0x0",
chainCode: "solana",
token: "usdc",
bankDetails: {
currencyCode: "usd",
routingNumber: "123456",
accountNumber: "654321",
iban: null,
bic: null,
bankName: "",
bankAddress: "",
beneficiaryName: "",
}
}
Step Four: Build transaction tracking
To ensure end-customers have clarity on the status of their transaction, we recommend you build basic transaction tracking capabilities. If your app has an Activity
section, you may want to populate these transactions there too.
Here are three states our product supports today and their corresponding recommended use cases:
- Transaction created / pending → toast message
- Transaction completed → toast message
- Transaction failed → toast message with tap to retry

Prototype provided for illustrative purposes only.

Prototype provided for illustrative purposes only.
Transaction created
Show a loading state in a full screen or toast message that confirms the transaction was created successfully.
{
type: "account_transaction_created",
body: {
id: "123",
state: "pending",
depositAmount: "100.00",
depositCurrencyCode: "EUR",
receivedAmount: "110.30",
token: "USDC",
}
}
Transaction completed
Send the user a push notification confirming that the top up has completed once you receive this webhook.
{
type: "account_transaction_completed",
body: {
id: "123",
state: "completed",
depositAmount: "100.00",
depositCurrencyCode: "EUR",
receivedAmount: "110.30",
token: "USDC",
}
}
Transaction failed
Use this webhook to show that the transaction failed and nudge the user to try another top-up.
{
type: "account_transaction_failed",
body: {
id: "123",
state: "failed",
depositAmount: "100.00",
depositCurrencyCode: "EUR",
receivedAmount: "110.30",
token: "USDC",
failureReason: "some_reason"
}
}
Step Five: Generate private and public key pair
- Generate your key pair using these commands:
openssl genrsa -out private_key.pem 2048
openssl rsa -in private_key.pem -pubout -out public_key.pem
- Save your private key and don't share it with anyone; we will never ask you to share your private key.
- Login to MoonPay Dashboard
- Go to Developers Tab and click on API Keys
- Find the
Public Key
field on the page, copy the Public Key content that you generated in Step 1, and paste it in. - Press the
Upload
button to upload your Public Key.
Step Six: Sign your request payloads
Before calling the Virtual Accounts API, you must sign your request payload using the private key you generated at the previous step.
Here's an example of how we expect you to sign your payloads and send a request.
const fs = require('fs');
const crypto = require('crypto');
const axios = require('axios');
const { DateTime } = require('luxon');
const privateKey = fs.readFileSync('./private_key.pem', 'utf8');
const publishableKey = 'pk_live_key'
const timestamp = DateTime.now().toMillis();
const queryString = `/v1/autoramp/transactions?apiKey=pk_live_key&transactionId=123×tamp=${timestamp}`;
const sign = crypto.createSign('SHA256');
sign.update(queryString);
const signature = sign.sign(privateKey, 'base64');
axios
.get(`https://api.moonpay.com${queryString}`, {
headers: {
'x-signature': signature,
},
})
.then((res) => {
console.log(res.data);
});
We expect you to pass your Publishable Key and a Timestamp in milliseconds as query parameters.
The signed request is only valid for 5 minutes; after that, it will expire to prevent replay attacks.
Step Seven: Get account and transaction details via API
Use our API to get account, transaction, and quote details.
Some endpoints require an idempotency key to ensure that certain operations are processed only once. Refer to our Idempotency guide to understand how it works and considerations.
Updated 1 day ago