Fiat-to-crypto on-ramps used to mean weeks of compliance paperwork, banking relationships, and KYC vendors bolted together with duct tape. The MoonPay API collapses that stack into a single integration: you generate a signed URL, drop a widget into your app, and MoonPay handles card processing, bank transfers, identity verification, and payouts to a user’s wallet.
If you are still comparing providers, start with our roundup of the best fiat on-ramp and off-ramp APIs to see where MoonPay fits against Transak, Ramp, and Stripe Crypto. Developers evaluating custodial infrastructure should also read how to use the Circle API for a view of the USDC side of the stack.
TL;DR
- MoonPay is a regulated fiat-to-crypto on-ramp and off-ramp used by wallets, NFT marketplaces, and exchanges in 160+ countries.
- Two integration paths: the Ramps SDK/widget (fastest, MoonPay-hosted UI) or direct REST API (full control, you build the UI).
- All widget URLs must be signed with HMAC-SHA256 using your secret key; unsigned URLs are rejected in production.
- KYC, card processing, and bank rails are handled server-side by MoonPay; you receive status via webhooks signed with the same HMAC pattern.
- Pricing is a processing fee (3.5%-4.5% card, lower for bank transfer) plus the underlying network fee, passed transparently to the user.
- Off-ramp (sell) mirrors the buy flow: signed URL, user sends crypto to a deposit address, MoonPay settles fiat to the user’s bank.
What is MoonPay?
MoonPay is a licensed payments company that lets your users buy and sell crypto with cards, bank transfers, Apple Pay, Google Pay, SEPA, and local rails. It operates as a money services business in the US, has an EMI license in the EU, and holds registrations across the UK, Canada, and Australia. You do not have to become a money transmitter to accept a card and deliver ETH to a user’s wallet.
The platform covers 110+ cryptocurrencies across 40+ networks (Ethereum, Solana, Bitcoin, Polygon, Base, Arbitrum), plus NFT checkout. MoonPay powers on-ramp flows for MetaMask, Trust Wallet, and OpenSea.
Authentication and setup
- Sign up for a partner account at moonpay.com/business.
- After approval, get sandbox and production keys:
- Publishable:
pk_test_... - Secret:
sk_test_...
- Publishable:
-
Store your keys securely:
export MOONPAY_API_KEY="pk_test_123..." export MOONPAY_SECRET_KEY="sk_test_abc..." export MOONPAY_BASE_URL="https://api.moonpay.com" Use the sandbox for end-to-end testing. It mimics production, but all transactions are test data.
Core endpoints
You will most often use these endpoint groups: currencies, quotes, transactions, and webhooks. See the full REST reference for all resources.
List supported currencies
Always fetch the latest currency list before building a picker. Filter by user’s IP or declared location for compliance.
curl -X GET "https://api.moonpay.com/v3/currencies" \
-H "Authorization: Api-Key $MOONPAY_API_KEY"
Response includes code, name, type (crypto or fiat), minBuyAmount, maxBuyAmount, and per-network metadata.
Get a real-time quote
Show users an exact crypto amount (including fees) before they proceed.
curl -X GET "https://api.moonpay.com/v3/currencies/eth/buy_quote?apiKey=$MOONPAY_API_KEY&baseCurrencyAmount=100&baseCurrencyCode=usd" \
-H "Content-Type: application/json"
Response: quoteCurrencyAmount, feeAmount, networkFeeAmount, totalAmount. Cache quote for ~60 seconds.
Build a signed buy widget URL (Node.js)
The buy widget is the fastest integration. Generate a signed URL with your parameters:
import crypto from "node:crypto";
function buildMoonPayBuyUrl({ walletAddress, currencyCode, baseAmount, email }) {
const params = new URLSearchParams({
apiKey: process.env.MOONPAY_API_KEY,
currencyCode,
walletAddress,
baseCurrencyCode: "usd",
baseCurrencyAmount: String(baseAmount),
email,
redirectURL: "https://yourapp.com/moonpay/complete",
});
const originalUrl = `https://buy.moonpay.com?${params.toString()}`;
const signature = crypto
.createHmac("sha256", process.env.MOONPAY_SECRET_KEY)
.update(new URL(originalUrl).search)
.digest("base64");
return `${originalUrl}&signature=${encodeURIComponent(signature)}`;
}
Pass this URL to your front end or load it in an iframe. The signature prevents tampering. See buy widget quickstart for all parameters.
Verify webhook signatures
MoonPay webhooks (e.g., transaction_created, transaction_updated) include a Moonpay-Signature-V2 header. Always verify before processing.
import crypto from "node:crypto";
export function verifyMoonPayWebhook(rawBody, header, secret) {
const [tPart, sPart] = header.split(",");
const timestamp = tPart.split("=")[1];
const signature = sPart.split("=")[1];
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected, "hex"),
Buffer.from(signature, "hex"),
);
}
Reject requests older than five minutes to prevent replay attacks. See the webhook reference for event types and payloads.
Sell (off-ramp) flow
The sell flow uses a signed URL for sell.moonpay.com. User picks crypto/amount, MoonPay generates a deposit address, and settles fiat to the user’s bank after confirmation.
const sellParams = new URLSearchParams({
apiKey: process.env.MOONPAY_API_KEY,
baseCurrencyCode: "eth",
baseCurrencyAmount: "0.5",
quoteCurrencyCode: "usd",
refundWalletAddress: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbc",
});
const sellUrl = `https://sell.moonpay.com?${sellParams.toString()}`;
// Sign with the same HMAC process as buy
Set refundWalletAddress to return funds if the transaction fails or the user sends the wrong asset.
NFT checkout
Let users buy NFTs with fiat. Register the NFT listing with MoonPay or use a supported marketplace. Then generate a signed URL with contractAddress, tokenId, and listingId. MoonPay handles fiat processing and on-chain transfer.
Common errors and rate limits
Typical errors:
-
400 invalid_signature: Your HMAC input doesn’t match MoonPay’s. Sign the exact query string sent, paying attention to encoding. -
403 geo_restricted: User’s IP is in a restricted country. CheckisAllowedbefore showing the asset. -
422 transaction_limit_exceeded: User hit daily/weekly/monthly cap. Limits are usually $2,000/day, $10,000/month without enhanced KYC. -
429 rate_limited: About 100 requests/minute per API key. Cache aggressively.
Always act on webhook state, not browser state. If a user closes the tab, the webhook still confirms their wallet is funded.
If you’re building multi-wallet support, see our guides on how to use the MetaMask API and the best crypto wallet APIs. For compliance, check our best KYC API roundup.
MoonPay pricing
- Card purchases: 3.5%-4.5% (minimum $3.99).
- Bank transfer (ACH, SEPA, Open Banking): 1%-1.9%.
- Network fee: Passed through at cost.
- Sell flow: Similar structure; payout fees depend on the destination.
Revenue share is negotiated for high-volume partners.
Testing MoonPay with Apidog
Signed URLs and HMAC webhooks are easiest to debug in a dedicated API client. Apidog lets you:
- Import the MoonPay OpenAPI spec.
- Store sandbox/production keys as environments.
- Run buy-quote, transaction-status, and webhook-replay cycles—no backend needed.
Suggested workflow:
- Create Apidog environments for
sandboxandproduction. - Add signature generation as a pre-request hook using the Node.js snippet above.
- Save sample transaction IDs as variables.
- Replay production webhooks using Apidog’s mock server until your verification passes.
Download Apidog for signing hooks, mock server, and environment switching.
FAQ
Do I need my own KYC vendor with MoonPay?
No. MoonPay handles KYC server-side. If you need to pre-verify users, see our best KYC API comparison.
Can I use MoonPay without their branded widget?
Yes, via direct API or headless SDK. Additional compliance review is required. Most teams start with the widget, then migrate if needed.
Which countries does MoonPay support?
160+ for buy, ~50 for sell. Currencies and payment methods vary by region. The currencies endpoint returns the current matrix per user.
How long does a transaction take?
- Card: less than 5 minutes.
- Bank transfer: 1-3 business days.
- Sell: fiat in 1-3 days after blockchain confirmation.
What if a webhook fails to deliver?
MoonPay retries with exponential backoff for up to 24 hours. Only return 2xx after persisting the event. Deduplicate by id to avoid processing duplicates.
Is the sandbox production-equivalent?
Nearly, but with relaxed geo/KYC and test transaction controls. Always test in production before full launch.

Top comments (0)