MoonPay
The consumer-recognized way to buy, sell, and swap crypto.
What it is
MoonPay is a fiat-to-crypto on/off-ramp that teams embed into their products. Users buy, sell, and swap crypto with cards, bank transfers, PayPal, Venmo, Revolut, and Apple/Google Pay across ~160+ countries. Partners integrate via a hosted widget, web/React/mobile SDKs, or REST API, and MoonPay handles KYC, payment acceptance, fraud, liquidity, and settlement. It has expanded beyond ramps into NFT checkout, a commerce/payments stack (via the 2025 Helio acquisition), and stablecoin infrastructure (via the 2025 Iron acquisition).
How it works
- Partners embed MoonPay via a hosted widget (buy.moonpay.com / sell.moonpay.com), web/React/mobile SDKs, or the REST API.
- Users buy crypto with card, bank transfer, PayPal, Venmo, Revolut, or Apple/Google Pay; KYC + fraud run inside MoonPay.
- MoonPay sources liquidity and pays out the crypto on-chain to the user's wallet (and the reverse for the off-ramp/sell flow).
- Swaps let users trade one crypto for another in-widget; NFT checkout lets buyers pay fiat for an NFT.
- Order state is pushed to the partner via signed webhooks (Buy: created/updated/failed; Sell: updates + requote events).
Differentiators
- Consumer brand recognition + a polished, high-converting purchase flow.
- ~160+ country reach with an unusually broad set of payment methods (PayPal, Venmo, Revolut, Apple/Google Pay, cards, bank transfer).
- More than a ramp: swaps, NFT checkout, and a commerce/stablecoin stack via the Helio + Iron acquisitions.
- Large balance sheet (~$645M raised + a $200M Galaxy credit line) funds liquidity for volume spikes and aggressive expansion.
Business model
Fees + spread on each on/off-ramp transaction — roughly ~1% on bank transfer to ~4.5% on card (min ~$3.99), plus a quoted-rate spread (~1–3%) and network fees; commerce/checkout fees on the Helio side.
Depends on
- Card networks (Visa/Mastercard)
- Banking partners + acquirers
- Alt payment rails (PayPal, Venmo, Revolut, Apple/Google Pay)
- Supported chains
- Stablecoin / market-maker liquidity
Risks
- High card fees + embedded spread vs. cheaper rails (e.g. Coinbase zero-fee USDC).
- Fee compression as ramps commoditize.
- Card fraud + chargebacks at scale.
- Regulatory exposure as a money transmitter / VASP across many jurisdictions (MTLs, MiCA/CASP). [verify exact current license list]
Architecture & mechanics
On/off-ramp flow & architecture
MoonPay is the regulated, fraud-bearing layer between traditional payment rails and the chains. The partner only renders a widget; MoonPay does the heavy, regulated work.
- On-ramp: user pays fiat (card / bank / PayPal / Venmo / Apple-Google Pay) → MoonPay runs KYC + fraud → sources crypto → pays out on-chain to the wallet.
- Off-ramp (sell): user sends crypto to a MoonPay-controlled address → MoonPay pays out fiat to card/bank.
- Swaps: in-widget crypto-to-crypto exchange; NFT checkout: fiat payment for an NFT.
- Async status flows back via signed webhooks; the client redirect is only a UX hint, not settlement truth.
Security model: signed URLs + signed webhooks
MoonPay's distinguishing developer detail is mandatory cryptographic signing in both directions, which keeps the secret key off the client while still allowing rich pre-filled widgets.
- Outbound: widget URLs are signed HMAC-SHA256(querystring, secretKey) → base64 → URL-encode; required whenever email/walletAddress are passed.
- SDKs accept just the signature (updateSignature / onUrlSignatureRequested); raw integrations append &signature= to the URL.
- Inbound: webhooks carry a Moonpay-Signature-V2 header partners must verify; idempotency + de-dupe required.
- Virtual Accounts API uses RSA-SHA256 request signing for a higher-assurance server-to-server channel.
Fees & economics
Revenue is a blend of an explicit platform fee and an embedded spread, which makes the headline rate understate true cost.
- Card: up to ~4.5% (min ~$3.99); bank transfer: ~1% (min ~$3.99).
- Plus a ~1–3% spread baked into the quoted rate (not a separate line item) + on-chain network fees.
- All-in effective cost can reach ~7–8% on small card buys — the core vulnerability vs. zero-fee USDC rails.
- Fees vary by order type, volume, fiat, asset, location, and payment method. [verify current numbers against pricing disclosure]
Strategy, M&A & risk
- 2025 M&A: acquired Helio (~$175M, crypto commerce/checkout) and Iron (stablecoin infrastructure) — pushing beyond ramps toward a payments/stablecoin platform.
- Liquidity: $200M revolving credit line from Galaxy (Mar 2025) to absorb transaction spikes.
- Funding/valuation: ~$645M raised; ~$3.4B (2021); reportedly raising near ~$5B with ICE (NYSE parent) in talks late 2025 [verify if closed].
- Forward bet: adapting the platform for AI-agent-initiated transactions (2026).
- Risks: fee compression, card fraud/chargebacks, and multi-jurisdiction regulatory exposure as a money transmitter / VASP.
How it's built
Architecture
MoonPay sits between card acquirers / banks / alt-payment rails and the chains. The partner renders a hosted widget (buy.moonpay.com / sell.moonpay.com) — embedded as an iframe, overlay, or new tab — and MoonPay runs KYC, payment acceptance, fraud, liquidity sourcing, and the on-chain payout (reverse for sell). Unlike a publishable-key-only ramp, MoonPay requires the widget URL to be SIGNED: whenever sensitive params (email, walletAddress) are passed, the partner's backend signs the URL's query string with HMAC-SHA256 using the SECRET key, so the secret never touches the client. Order state is delivered to the partner via signed webhooks (Moonpay-Signature-V2 header).
Integration shape
Two layers: (1) a publishable apiKey (pk_) used on the client to identify the integration, and (2) a secret key (sk_) used server-side only to sign URLs and verify webhooks. Integrate via the hosted widget URL, the Web SDK (script attaches window.MoonPayWebSdk, or as a package), the React SDK (components expose an onUrlSignatureRequested async prop), or React Native / iOS / Android SDKs. For SDKs return just the signature via updateSignature; for raw URLs return the whole signed URL. Sandbox hosts (buy-sandbox.moonpay.com) mirror production by swapping keys.
API surface
buy.moonpay.com (widget URL)- On-ramp widget. Core params: apiKey, currencyCode, baseCurrencyCode, baseCurrencyAmount, walletAddress, paymentMethod, redirectURL, externalCustomerId, signature.
sell.moonpay.com (widget URL)- Off-ramp widget. Params: apiKey, baseCurrencyCode (crypto being sold), quoteCurrencyCode (fiat), refundWalletAddress, externalCustomerId, signature.
signature (query param)- HMAC-SHA256 of the URL query string (incl. leading '?'), keyed by the secret API key, base64-then-URL-encoded. Required when passing email/walletAddress or the widget won't load.
Web SDK (window.MoonPayWebSdk)- moonPay({ flow:'buy'|'sell', environment, variant, params }); .show()/.close(); supports updateSignature() callback for backend-signed URLs.
React SDK <MoonPayBuyWidget />- Component with visible, variant, baseCurrencyCode, walletAddress + onUrlSignatureRequested async prop that returns the backend-generated signature.
Webhooks (Buy / Sell)- POST events: transaction created / updated / failed (buy); sell updates + sell_transaction_requote_required. Verify Moonpay-Signature-V2; respond 200 within 10s; de-dupe (events may repeat / arrive out of order).
REST API + Virtual Accounts API- Server-side currencies, quotes, transactions, and customer data; Virtual Accounts API requests signed with RSA-SHA256 digital signatures.
Minimal integration
Minimal on-ramp: build a buy-widget URL on the server and HMAC-sign it before handing it to the client.
import crypto from 'crypto';
// SECRET key stays on the server — never ship it to the client.
const SECRET = process.env.MOONPAY_SECRET_KEY!; // sk_live_...
const PK = process.env.MOONPAY_PUBLISHABLE_KEY!; // pk_live_...
export function buildSignedBuyUrl() {
const base = 'https://buy.moonpay.com';
const qs = new URLSearchParams({
apiKey: PK,
currencyCode: 'usdc', // crypto to receive
baseCurrencyCode: 'usd', // fiat to pay with
baseCurrencyAmount: '50',
walletAddress: '0xabc...def', // sensitive -> signature now required
redirectURL: 'https://rails.app/done',
});
const search = `?${qs.toString()}`;
const signature = crypto
.createHmac('sha256', SECRET)
.update(search) // sign the query string incl. '?'
.digest('base64');
return `${base}${search}&signature=${encodeURIComponent(signature)}`;
}Build notes
- Two keys: publishable (pk_) on the client, secret (sk_) server-side only for URL signing + webhook verification. The widget refuses to load if a URL carrying email/walletAddress isn't signed.
- Treat the SIGNED WEBHOOK as the source of truth for order status — not the client redirect. Verify the Moonpay-Signature-V2 header, respond 200 within ~10s, and de-dupe (events may repeat or arrive out of order).
- Start on the sandbox hosts (buy-sandbox.moonpay.com) with test keys; flip to production by swapping keys + host.
- [verify against current docs — exact param names (e.g. quoteCurrencyCode vs currencyCode on sell) and the full webhook event list evolve]