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.

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.

View account details through the MoonPay widget.  
**Prototype provided for illustrative purposes only.**

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.

ParameterDescription
apiKey requiredYour publishable API key. This is used to assign customers and transactions to your MoonPay account.
customerId requiredThe MoonPay identifier for the customer. This identifier will be present whenever we pass you customer data.
walletAddress requiredThe 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.
themeLight 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.

Confirm virtual account details in the MoonPay widget.  
**Prototype provided for illustrative purposes only.**

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
Transfer in progress toast
Transfer pending toast message.
Prototype provided for illustrative purposes only.
Transfer complete toast
Transfer complete toast message.
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

  1. 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
  1. Save your private key and don't share it with anyone; we will never ask you to share your private key.
  2. Login to MoonPay Dashboard
  3. Go to Developers Tab and click on API Keys
  4. Find the Public Key field on the page, copy the Public Key content that you generated in Step 1, and paste it in.
  5. 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&timestamp=${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.