Deposits

Create deposit workflows for receiving any crypto in your app

⚠️

IMPORTANT: Deposits is currently available for private beta testing. Login here: https://moonpay.dev.hel.io/ (devnet) or https://moonpay.hel.io/ (mainnet) and use the following query parameter: ?permission=deposit.

Let users fund your app with any crypto, ideal for gaming deposits, embedded wallets and trading apps. This works via unique, self-custodial deposit addresses that are securely generated and identifiable per customer and session.

How It Works

  • Set up a Deposit in the MoonPay Commerce dashboard
  • Generate unique Deposit Customer IDs via API
  • Serve the deposit widget or deposit addresses in your app via iframe/embed/API
    • User copies/scans the wallet QR code to deposit their crypto of choice
  • MoonPay Commerce detects balance and auto-bridges/swaps the crypto to your preferred currency & recipient wallet
  • Use webhooks to confirm deposits on your backend and credit the deposit to 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 - Go to Settings -> Merchant Settings to theme your deposit widget
  1. Create Deposit Customer via the API

Once you’ve created a Deposit ID, you can generate your API key in the dashboard.
Use it to programmatically create Deposit Customers via the API endpoint. You need to specify a unique customer identifier, recipient wallet, and (optionally) an additionalJson payload.

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"
    ],
    "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"
    ]
}
  1. Serve the Deposit UI in Your App via embed/Iframe/API

Embed the deposit widget in your app using the following NPM package - Deposit widget - 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: 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);
        },
      }}
    />
  );
}

You can 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.

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.

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.

EventPayloadWhen 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.origin before handling messages from the widget. This ensures you only process events coming from our domain and ignore anything unexpected.

  1. 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.

ChainSupported Currencies
SolanaSOL, USDC, USDT
Ethereum (EVM)ETH, USDC, USDT
Polygon (EVM)POL, USDC, USDT
Base (EVM)ETH, USDC, USDT
Binance Smart Chain (EVM)BNB, USDT
BitcoinBTC
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.