API Reference
Overview
The Virtual Accounts API allows partners to retrieve virtual account transaction data through a secure, signed request mechanism. All requests must be authenticated using RSA-SHA256 digital signatures.
Base URL
https://api.moonpay.com
Authentication
RSA-SHA256 Digital Signatures
All API requests must be signed using RSA-SHA256 digital signatures. The signature is generated by signing the complete request payload (including path and query parameters) with your private key.
Required Headers
Header | Description |
---|---|
x-signature | Base64-encoded RSA-SHA256 signature of the request payload |
Content-Type | application/json |
Signature Generation Process
-
Create the payload: Concatenate the request path with query parameters
Format: {path}?{queryString} Example: /v1/virtual-accounts/transactions?apiKey=pk_test_xxx×tamp=1234567890
-
Generate signature: Use RSA-SHA256 to sign the payload with your private key
const sign = crypto.createSign('SHA256'); sign.update(payload); const signature = sign.sign(privateKey, 'base64');
-
Include signature: Add the signature to the
x-signature
header
Get Virtual Account Details
Retrieves virtual account details. Can be filtered by customer ID and/or wallet address.
Request
GET /v1/virtual-accounts?apiKey={publishableApiKey}×tamp={timestamp}&customerId={customerId}&walletAddress={walletAddress}
Query Parameters
Parameter | Type | Required | Description |
---|---|---|---|
apiKey | string | Yes | Your publishable API key (starts with pk_test_ for sandbox or pk_live_ for production) |
timestamp | number | Yes | Unix timestamp in milliseconds for request validation |
customerId | string | No | Optional filter by customer ID |
walletAddress | string | No | Optional filter by wallet address |
virtualAccountId | string | No | Optional filter by virtual account id |
Headers
Header | Type | Required | Description |
---|---|---|---|
x-signature | string | Yes | RSA-SHA256 signature of the request payload |
Content-Type | string | Yes | Must be application/json |
Example Request
async function getVirtualAccountDetails(customerId = null, walletAddress = null) {
// Generate timestamp
const timestamp = Date.now();
// Build query string with optional parameters
let queryParams = [`apiKey=${PUBLISHABLE_API_KEY}`, `timestamp=${timestamp}`];
if (customerId) queryParams.push(`customerId=${customerId}`);
if (walletAddress) queryParams.push(`walletAddress=${walletAddress}`);
// Build payload for signature
const path = '/v1/virtual-accounts';
const queryString = queryParams.join('&');
const payload = `${path}?${queryString}`;
// Create RSA-SHA256 signature
const sign = crypto.createSign('SHA256');
sign.update(payload);
const signature = sign.sign(PRIVATE_KEY, 'base64');
// Make the request
const url = `${SERVER_URL}${payload}`;
const response = await fetch(url, {
method: 'GET',
headers: {
'x-signature': signature,
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
return await response.json();
}
// Usage examples:
// Get all virtual accounts
await getVirtualAccountDetails();
// Filter by customer ID only
await getVirtualAccountDetails('customerId123');
// Filter by wallet address only
await getVirtualAccountDetails(null, '0x1234567890123456789012345678901234567890');
// Filter by both customer ID and wallet address
await getVirtualAccountDetails('customerId123', '0x1234567890123456789012345678901234567890');
Success Response (200 OK)
[
{
"id": "1234567890",
"type": "auto_on_ramp",
"destination": {
"wallet": {
"walletAddress": "0x1234576867890123456789012345678901234567",
"walletAddressTag": "usdc_sol"
},
"network": {
"code": "solana",
"name": "Solana",
"icon": "https://static.moonpay.com/widget/currencies/usdc.svg",
"isAddressCaseSensitive": false,
"isEvmCompatible": true,
"addressRegex": "0x[a-fA-F0-9]{40}",
"supportsAddressTag": true,
"testnetWalletExplorerLink": "https://solana.com/testnet",
"mainnetWalletExplorerLink": "https://solana.com/mainnet",
"testnetTxnExplorerLink": "https://solana.com/testnet",
"mainnetTxnExplorerLink": "https://solana.com/mainnet",
"addressTagRegex": "0x[a-fA-F0-9]{40}",
"createdAt": "2025-06-10T11:45:25.202Z",
"updatedAt": "2025-06-10T11:45:25.202Z"
}
},
"sourceCurrency": {
"id": "123",
"code": "USD",
"name": "US Dollar",
"precision": 2,
"icon": "",
"maxAmount": 1000000,
"minAmount": 1,
"maxBuyAmount": 1000000,
"minBuyAmount": 1,
"accountBuyMinAmount": 1,
"isSellSupported": true,
"deletedAt": null,
"type": "fiat",
"decimals": 2,
"spreadPercentage": 0,
"isUtxoCompatible": false,
"supportedAcquirers": [],
"createdAt": "2025-06-10T11:45:25.202Z",
"updatedAt": "2025-06-10T11:45:25.202Z"
},
"destinationCurrency": {
"id": "123",
"code": "usdc_sol",
"name": "USD Coin (Solana)",
"type": "crypto",
"precision": 2,
"icon": "",
"maxAmount": 1000000,
"minAmount": 1,
"maxBuyAmount": 1000000,
"minBuyAmount": 1
},
"customerId": "123",
"organizationId": "123",
"createdAt": "2025-06-10T11:45:25.202Z",
"updatedAt": "2025-06-10T11:45:25.202Z",
"source": {
"iban": "DE89370400440532013000",
"bic": "DEUTDEDB123",
"accountNumber": "1234567890",
"sortCode": "1234567890",
"bankName": "Deutsche Bank",
"bankAddress": "123 Main St, Anytown, USA",
"accountHolderName": "John Doe",
"accountHolderAddress": "123 Main St, Anytown, USA"
}
},
{
"id": "1234567890",
"type": "auto_on_ramp",
"destination": {
"wallet": {
"walletAddress": "0x1234576867890123456789012345678901234567",
"walletAddressTag": "usdc_sol"
},
"network": {
"code": "solana",
"name": "Solana",
"icon": "https://static.moonpay.com/widget/currencies/usdc.svg",
"isAddressCaseSensitive": false,
"isEvmCompatible": true,
"addressRegex": "0x[a-fA-F0-9]{40}",
"supportsAddressTag": true,
"testnetWalletExplorerLink": "https://solana.com/testnet",
"mainnetWalletExplorerLink": "https://solana.com/mainnet",
"testnetTxnExplorerLink": "https://solana.com/testnet",
"mainnetTxnExplorerLink": "https://solana.com/mainnet",
"addressTagRegex": "0x[a-fA-F0-9]{40}",
"createdAt": "2025-06-10T11:45:25.202Z",
"updatedAt": "2025-06-10T11:45:25.202Z"
}
},
"sourceCurrency": {
"id": "123",
"code": "USD",
"name": "US Dollar",
"precision": 2,
"icon": "",
"maxAmount": 1000000,
"minAmount": 1,
"maxBuyAmount": 1000000,
"minBuyAmount": 1,
"accountBuyMinAmount": 1,
"isSellSupported": true,
"deletedAt": null,
"type": "fiat",
"decimals": 2,
"spreadPercentage": 0,
"isUtxoCompatible": false,
"supportedAcquirers": [],
"createdAt": "2025-06-10T11:45:25.202Z",
"updatedAt": "2025-06-10T11:45:25.202Z"
},
"destinationCurrency": {
"id": "123",
"code": "usdc_sol",
"name": "USD Coin (Solana)",
"type": "crypto",
"precision": 2,
"icon": "",
"maxAmount": 1000000,
"minAmount": 1,
"maxBuyAmount": 1000000,
"minBuyAmount": 1
},
"customerId": "123",
"organizationId": "123",
"createdAt": "2025-06-10T11:45:25.202Z",
"updatedAt": "2025-06-10T11:45:25.202Z",
"source": {
"iban": "DE89370400440532013000",
"bic": "DEUTDEDB123",
"accountNumber": "1234567890",
"sortCode": "1234567890",
"bankName": "Deutsche Bank",
"bankAddress": "123 Main St, Anytown, USA",
"accountName": "John Doe",
"accountAddress": "123 Main St, Anytown, USA"
}
}
]
Response Field Descriptions
Field | Type | Description |
---|---|---|
id | string | Unique virtual account identifier |
type | string | Current virtual account type (auto_on_ramp, auto_off_ramp) |
state | string | Current virtual account state (pending, authorized, depositAccountAdded, approved, rejected, cancelled) |
sourceCurrency | object | Source currency details including code, name, precision, and limits |
destinationCurrency | object | Destination currency details |
source | object | Source account information (name, IBAN, address) |
destination | object | Destination wallet and network information (wallet address, wallet address tag, network code, network name, network icon, network is address case sensitive, network is EVM compatible, network address regex, network supports address tag, network testnet wallet explorer link, network mainnet wallet explorer link, network testnet txn explorer link, network mainnet txn explorer link, network address tag regex, network created at, network updated at) |
createdAt | string | ISO 8601 timestamp of virtual account creation |
Virtual Account Types
Type | Description |
---|---|
auto_on_ramp | Virtual account is receiving FIAT and sending Crypto |
auto_off_ramp | Virtual account is receiving Crypto and sending FIAT |
Virtual Account States
State | Description |
---|---|
pending | Virtual account is being created |
authorized | Virtual account has been authorized by the customer |
depositAccountAdded | Virtual account has been added to the deposit account |
approved | Virtual account has been approved |
rejected | Virtual account has been rejected |
cancelled | Virtual account has been cancelled |
Get Virtual Account Transactions
Retrieves virtual account transaction data for the authenticated partner.
Request
GET /v1/virtual-accounts/transactions?apiKey={publishableApiKey}×tamp={timestamp}&virtualAccountId={virtualAccountId}&customerId={customerId}
Query Parameters
Parameter | Type | Required | Description |
---|---|---|---|
apiKey | string | Yes | Your publishable API key (starts with pk_test_ for sandbox or pk_live_ for production) |
timestamp | number | Yes | Unix timestamp in milliseconds for request validation |
virtualAccountId | string | No | Optional filter the transaction list by virtual account id |
customerId | string | No | Optional filter the transaction list by customer id |
Headers
Header | Type | Required | Description |
---|---|---|---|
x-signature | string | Yes | RSA-SHA256 signature of the request payload |
Content-Type | string | Yes | Must be application/json |
Example Request
const crypto = require('crypto');
const fetch = require('node-fetch');
const SERVER_URL = 'https://api.moonpay.com';
const PUBLISHABLE_API_KEY = '[Your Publishable Api Key]';
const PRIVATE_KEY = `-----BEGIN PRIVATE KEY-----
[Your Private Key Here]
-----END PRIVATE KEY-----`;
async function getVirtualAccountTransactions(virtualAccountId) {
// Generate timestamp
const timestamp = Date.now();
// Build payload for signature
const path = '/v1/virtual-accounts/transactions';
const queryString = `apiKey=${PUBLISHABLE_API_KEY}×tamp=${timestamp}&virtualAccountId=${virtualAccountId}`;
const payload = `${path}?${queryString}`;
// Create RSA-SHA256 signature
const sign = crypto.createSign('SHA256');
sign.update(payload);
const signature = sign.sign(PRIVATE_KEY, 'base64');
// Make the request
const url = `${SERVER_URL}${payload}`;
const response = await fetch(url, {
method: 'GET',
headers: {
'x-signature': signature,
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
return await response.json();
}
Success Response (200 OK)
{
"transactions": [
{
"id": "1234567890",
"state": "completed",
"sourceAmount": "100",
"sourceCurrency": {
"id": "123",
"code": "USD",
"name": "US Dollar",
"precision": 2,
"icon": "",
"maxBuyAmount": 1000000,
"minBuyAmount": 1,
"accountBuyMinAmount": 1,
"isSellSupported": true,
"maxAmount": 1000000,
"minAmount": 1,
"createdAt": "2025-06-10T11:47:27.582Z",
"updatedAt": "2025-06-10T11:47:27.582Z"
},
"destinationAmount": "100",
"destinationCurrency": {
"id": "123",
"code": "usdc_sol",
"name": "USD Coin (Solana)",
"precision": 2,
"icon": "",
"maxBuyAmount": 1000000,
"minBuyAmount": 1,
"accountBuyMinAmount": 1,
"isSellSupported": true,
"maxAmount": 1000000,
"minAmount": 1,
"createdAt": "2025-06-10T11:47:27.582Z",
"updatedAt": "2025-06-10T11:47:27.582Z"
},
"source": {
"accountName": "John Doe",
"iban": "DE89370400440532013000",
"accountAddress": "123 Main St, Anytown, USA"
},
"destination": {
"wallet": {
"walletAddress": "0x1234576867890123456789012345678901234567",
"walletAddressTag": "Solana"
},
"network": {
"code": "solana",
"name": "Solana",
"icon": "https://static.moonpay.com/widget/currencies/usdc.svg",
"isAddressCaseSensitive": false,
"isEvmCompatible": true,
"addressRegex": "0x[a-fA-F0-9]{40}",
"supportsAddressTag": true,
"testnetWalletExplorerLink": "https://solana.com/testnet",
"mainnetWalletExplorerLink": "https://solana.com/mainnet",
"testnetTxnExplorerLink": "https://solana.com/testnet",
"mainnetTxnExplorerLink": "https://solana.com/mainnet",
"addressTagRegex": "0x[a-fA-F0-9]{40}",
"createdAt": "2025-06-10T11:47:27.582Z",
"updatedAt": "2025-06-10T11:47:27.582Z"
}
},
"createdAt": "2025-06-10T11:47:27.582Z"
}
]
}
Response Field Descriptions
Field | Type | Description |
---|---|---|
id | string | Unique transaction identifier |
state | string | Current transaction state (pending, completed, failed) |
sourceAmount | string | Amount in source currency |
sourceCurrency | object | Source currency details including code, name, precision, and limits |
destinationAmount | string | Amount in destination currency |
destinationCurrency | object | Destination currency details |
source | object | Source account information (name, IBAN, address) |
destination | object | Destination wallet and network information |
createdAt | string | ISO 8601 timestamp of transaction creation |
Transaction States
State | Description |
---|---|
pending | Transaction is being processed |
completed | Transaction has been successfully completed |
failed | Transaction has failed |
Errors
Error responses
400 Bad Request
{
"success": false,
"error": {
"code": "INVALID_REQUEST",
"message": "Missing required parameter: apiKey"
}
}
401 Unauthorized
{
"success": false,
"error": {
"code": "INVALID_SIGNATURE",
"message": "Request signature verification failed"
}
}
403 Forbidden
{
"success": false,
"error": {
"code": "INVALID_API_KEY",
"message": "The provided API key is invalid or inactive"
}
}
429 Too Many Requests
{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Please try again later."
}
}
500 Internal Server Error
{
"success": false,
"error": {
"code": "INTERNAL_ERROR",
"message": "An internal server error occurred"
}
}
Error Handling Best Practices
- Always check response status codes before processing response data
- Implement retry logic for transient errors (5xx status codes)
- Log signature generation details for debugging authentication issues
- Handle rate limiting by implementing exponential backoff
Security Considerations
Private Key Management
- Never expose your private key in client-side code or public repositories
- Store private keys securely using environment variables or secure key management systems
- Rotate private keys regularly for enhanced security
Request Validation
- Timestamps are validated to prevent replay attacks
- Signatures are verified against your registered public key
- All requests must be made over HTTPS in production
Rate Limiting
- API requests are rate-limited per API key
- Current limit: 100 requests per minute per API key
- Exceeding rate limits will result in HTTP 429 responses
Testing
Sandbox Environment
Use the following for testing:
- API Key: Test keys start with
pk_test_
Sample Test Data
The sandbox environment includes sample virtual account transactions for testing integration.
Updated 2 days ago