Deposits
Let users fund your app with any crypto, ideal for gaming deposits, embedded wallets & trading apps.
IMPORTANT: Deposits is currently available for private beta testing. Login here: mainnet or devnet and use the following query parameter: ?permission=deposit.
How It Works
- Set up a Deposit ID in the MoonPay Commerce dashboard
- Generate unique Deposit Customers via API and associated deposit addresses across SVM, EVM & BTC.
- Serve the deposit widget/addresses in your app via embed/API
- User copies/scans the wallet QR code to deposit their crypto of choice
- Optionally enable the "Deposit with Card" function
- MoonPay Commerce detects balance and auto-bridges/swaps the crypto to your preferred currency & recipient wallet
- Use webhooks/APIs to confirm deposits on your backend and credit your customer's account
1. Set Up a Deposit ID
Login to the MoonPay Commerce dashboard, create a payment and select Deposits as the payment type.
- Deposit name - Set a name for the deposit. You can also enable options such as “Deposit with card” and email notifications for successful deposits.
- Recipient currency – The currency in which you want to receive the payment
- Test customer deposit session - Use the prebuilt UI in step 2 to generate a test customer deposit session by defining a customer ID and recipient wallet
- Theming/whitelabel - Go to Settings -> Merchant Settings to theme your deposit widget. Theming disables the footer including the light/dark mode toggle and MooonPay logo
2. Create a Deposit Customer via the API
- Once you’ve created a Deposit ID, you can generate your API key in the dashboard.
- Create Deposit Customers and associated deposit addresses across SVM, EVM & BTC via the API. You need to specify your unique customerId, recipient wallet, and (optionally) an additionalJson payload.
- Store the Deposit Customer token to reuse it for repeat deposits for the same customerId.
Example Request:
curl --request POST \
--url "https://api.hel.io/v1/deposit-customers/api-key?apiKey=YOUR_PUBLIC_KEY" \
--header 'Authorization: Bearer YOUR_PRIVATE_KEY' \
--header 'Content-Type: application/json' \
--data '{
"customerId": "<customer-identifier>", // The ID you set for the customer
"depositId": "68f23471cff6f1f4c16cb7c8", // ID of the deposit
"recipientPublicKeys": [ // Recipient public key
"Gvga5AysVCokomFstCDJRmNjLFnJt7VkmqxXJVgfKLGj"
],
"defaultOnrampAmount": "30" // USD amount that the Deposit with card amount defaults to
"additionalJSON": "{\"createdFromTempDemoUi\": true}" // Optional metadata
}'Example Response:
{
"id": "68f2348ecff6f1f4c16cb7e1", // Unique identifier for this transaction or operation
"deposit": "68f23471cff6f1f4c16cb7c8", // Reference ID of the related deposit
"token": "acac3ec9-f8f6-4128-8159-a91e418e2581", // API or session token used for authentication
"customerId": "<customer-identifier>", // Unique identifier of the customer
"recipientPublicKeys": [ // Array of recipient public keys
"Gvga5AysVCokomFstCDJRmNjLFnJt7VkmqxXJVgfKLGj"
]
}Store the Deposit Customer token to reuse it for repeat deposits for the same customerId. You can also use the Update Deposit Customer and Get Deposit Customer endpoints as needed. Please note you can only each CustomerId once to generate a Deposit Customer.
3. Serve the Deposit UI in Your App
Configuration
The below configuration options are available for customizing your deposit widget:
network
Specifies the environment for the deposit flow.
- Type:
'test' | 'main' - Default:
'main' - Required: No
Values:
'test'- Use the test/sandbox environment for development and testing'main'- Use the production environment for live transactions
display
Controls how the deposit flow is rendered on your page.
- Type:
'inline' | 'button' | 'new-tab' - Default:
'inline' - Required: No
Values:
'inline'- Embeds the deposit flow directly in the container element'button'- Renders a button that opens the deposit flow in a modal'new-tab'- Renders a button that opens the deposit flow in a new browser tab
If you enable Deposit with Card and use the deposit widget in inline or button mode, your domain(s) must be whitelisted by MoonPay Ramps. In some cases, this may require additional KYB. You can always use new-tab mode without domain whitelisting, or disable the Deposit with Card option.
Embedding the widget inside a React app:
Embed the deposit widget in your app using the following NPM package - [Deposit widget - react](https://www.npmjs.com/package/@heliofi/deposit-react
import { MoonpayCommerceDeposit } from '@heliofi/deposit-react';
interface MoonpayCommerceDepositConfig {
// Required: Your unique deposit customer token
// Create it from https://commerce.moonpay.com
depositCustomerToken: string;
// Optional: Network to use
// 'main' = Mainnet
// 'test' = Testnet
network?: 'test' | 'main';
// Optional: Controls how the deposit flow is rendered on your page.
// 'inline' = Embeds the deposit flow directly in the container element
// 'button' = Renders a button that opens the deposit flow in a modal
// 'new-tab' = Renders a button that opens the deposit flow in a new browser tab
display?: 'inline' | 'button' | 'new-tab'
// Optional: Called when deposit completes successfully
// Note: this might not be working in the current beta version
onSuccess?: (data: {
transaction?: string;
depositCustomer?: unknown;
}) => void;
// Optional: Called if an error occurs
// Note: this might not be working in the current beta version
onError?: (error: { errorMessage?: string; transaction?: string }) => void;
}
function App() {
return (
<MoonpayCommerceDeposit
config={{
depositCustomerToken: 'your-deposit-customer-token',
onSuccess: (data) => {
console.log('Deposit completed:', data);
},
onError: (error) => {
console.error('Deposit failed:', error);
},
}}
/>
);
}Vanilla JS
If you are not using React.js, you can alternatively embed using the pure JS approach like below:
<script type="module" src="https://embed.deposits.hel.io/assets/index-v1.js" id="deposit-script"></script>
<div id="deposit-container"></div>
<script>
document.getElementById('deposit-script').addEventListener('load', () => {
window.moonpayCommerceDeposit(
document.getElementById('deposit-container'),
{
depositCustomerToken: 'your-token-here',
network: 'test',
display: 'inline',
onSuccess: () => {
console.log('Deposit completed!');
},
onError: (error) => {
console.error('Deposit error:', error.message);
},
}
);
});
</script>Your own UI
You can also explore a headless setup and simply serve deposit addresses in your own UI using the deposit API endpoint. Use the Record Wallet Activity endpoint whenever you display a deposit wallet to a customer. This tells us that the wallet is actively being used, allowing us to temporarily increase balance polling and detect incoming payments more quickly. This improves the payment experience for your customers.
Iframe
Alternatively you can embed an iframe using the HTML snippet below. It lets users deposit crypto or pay by card directly on your site without leaving your app.
<iframe
allow="clipboard-write"
style="width:420px; height:260px;"
src="https://moonpay.hel.io/embed/deposit/acac3ec9-f8f6-4128-8159-a91e418e2581"
/>Replace the token at the end of the src URL with your own token returned when creating the customer deposit via the API (e.g. acac3ec9-f8f6-4128-8159-a91e418e2581) to render the correct deposit widget.
Iframe Post Message Events
Our deposit widget emits structured postMessage events that let your application react instantly to lifecycle changes such as completion, errors, or height updates, keeping your interface responsive and in sync without requiring a page reload.
| Event | Payload | When it fires |
|---|---|---|
onSuccess | {} (empty object) | Deposit flow completed and the merchant has been successfully paid. |
onError | { message: string } | Something went wrong |
onHeightChanged | { height: number } (pixels) | The widget needs a new height (e.g. after a step change) |
The snippet below illustrates how to integrate and respond to our postMessage events within a React component.
import { useEffect, useState } from "react";
export const Deposit = () => {
// Dynamic height set by the deposit widget via postMessage
const [iframeHeight, setIframeHeight] = useState(0);
useEffect(() => {
// Handle events sent from the deposit widget iframe
const handleMessage = (event: MessageEvent) => {
// Only process messages coming from the Helio/MoonPay widget
if (event.origin !== "https://moonpay.hel.io") return;
const data = event.data;
switch (data.type) {
// Widget requests the parent to resize the iframe
case "onHeightChanged":
setIframeHeight(data.height || 0);
break;
// Deposit flow completed successfully
case "onSuccess":
alert("Deposit completed");
break;
// An error occurred inside the widget
case "onError":
alert(`Deposit error: ${data.message}`);
break;
default:
break;
}
};
// Subscribe to postMessage events
window.addEventListener("message", handleMessage);
// Clean up on unmount
return () => window.removeEventListener("message", handleMessage);
}, []);
return (
<iframe
allow="clipboard-write"
className="rounded-3xl shadow-2xl"
// Apply dynamic height returned by the widget
style={{ width: 420, minHeight: 260, height: iframeHeight }}
src="https://moonpay.hel.io/embed/deposit/ad1ecf2d-f535-41f8-96df-fd6a45a27cdd"
/>
);
};Always validate the
event.originbefore handling messages from the widget. This ensures you only process events coming from our domain and ignore anything unexpected.
4. Use Webhooks to Confirm Deposits on Your Backend
You can create a webhook to listen for customer deposit events in the Dashboard -> Developer or via the API using the curl request below.
curl --request POST \
--url "https://api.hel.io/v1/webhook/deposit/api-key?apiKey=YOUR_PUBLIC_KEY" \
--header 'Authorization: Bearer YOUR_PRIVATE_KEY' \
--header 'Content-Type: application/json' \
--data '{
"name": "string",
"depositId": "string",
"targetUrl": "string"
}'Listen to the webhook payload, which includes details such as customerId, amount received, fees, and amount sent (if you choose to credit the full amount and absorb the fees). Use the Get Deposit functions to query this data via the API for all transactions or by customer token.
Supported Chains and Currencies
These are the supported currencies for each blockchain network. Users can choose their preferred network and currency when making a deposit. Deposited funds will be bridged and swapped into your specified recipient currency.
| Chain | Supported Currencies |
|---|---|
| Solana | SOL, USDC, USDT |
| Ethereum (EVM) | ETH, USDC, USDT |
| Polygon (EVM) | POL, USDC, USDT |
| Base (EVM) | ETH, USDC, USDT |
| Binance Smart Chain (EVM) | BNB, USDT |
| Bitcoin | BTC |
| Arbitrum (EVM) | ETH, USDC, USDT |
*You can request support for any currency on EVM & Solana. Additional chains coming soon including Tron, Ton, SUI and more.
User FAQs
What is a deposit address?
It's your unique crypto wallet address where you can send funds. Deposits are swapped/bridged to the correct currency, and then credited to your account balance.
How do I use crypto deposits?
- Select your token and network.
- Copy the address or scan the QR code.
- Paste it into your wallet or exchange withdrawal page.
- Enter your amount (must meet the minimum) and select the correct currency.
- Double-check the wallet address and currency, then confirm.
- Track your transfer in the widget, most transfers arrive within 30 seconds (except BTC which takes 20-30 mins).
What is price impact and slippage?
Price impact and slippage represent the estimated difference between the market price when you initiate the deposit and the actual conversion rate when your transaction is processed. This typically ranges from 0.1% to 1% and varies based on market volatility and network congestion. You might also pay a small gas fee.
Do I always need to send tokens to cover gas?
No, MoonPay covers gas fees for SPL & ERC-20 tokens. For example, you can send USDC on Solana or Ethereum without needing to send SOL or ETH.
Are there deposit limits?
Yes. Minimums are $5 on Solana and EVM L2s, and $25 on Ethereum and Bitcoin. No maximums. If you send less, you can top up to reach the minimum.
Can I reuse the same deposit address?
It depends on the app you're funding, addresses are customer ID specific. Always use the one shown in your current widget.
What should I do if my deposit hasn't arrived?
First, you can always verify the transaction on the blockchain explorer using your deposit wallet address. Check that you sent to the correct address and used the right network. If the transaction is confirmed on the blockchain but hasn't appeared in your account after the expected processing time, wait a short time or contact support with your wallet address.
Updated 7 days ago
