Mnp
MoonPay Platform Skill
Product summary
MoonPay Platform is a headless fiat-to-crypto payment API and SDK for building crypto ramps directly in your app. You control the user experience while MoonPay handles compliance, risk, and fraud. The platform uses a combination of REST API calls and embedded frames (iframes on web, WebViews on mobile) to manage sensitive operations like KYC and payment processing.
Key resources:
- Base API URL:
https://api.moonpay.com/platform/v1 - SDK:
@moonpay/platform(npm) - Primary docs: https://moonpay.mintlify.app
- Comprehensive page listing: https://moonpay.mintlify.app/llms.txt
Key files and concepts:
- Session tokens (created server-side, sent to frontend)
- Access tokens and client tokens (returned from connect flow, used for API calls and frame initialization)
- Frames: co-branded (MoonPay-hosted UI) and headless (minimal UI like Apple Pay button)
- Connections: customer permission to associate MoonPay account with your app
- Quotes: real-time prices and fees for transactions (price quotes and executable quotes)
When to use
Reach for this skill when:
- Building payment flows: Implementing fiat-to-crypto purchase experiences (e.g., "buy crypto with Apple Pay")
- Managing customer connections: Onboarding customers, checking connection status, handling KYC verification
- Getting transaction quotes: Fetching real-time prices, fees, and limits before executing payments
- Executing transactions: Setting up payment methods and processing crypto purchases
- Handling compliance: Managing challenges (3D Secure, identity verification, authentication upgrades)
- Tracking transactions: Polling or using webhooks to monitor transaction status (pending → complete/failed)
- Testing integrations: Using test mode with test API keys and test payment cards
- Integrating on web or mobile: Rendering frames in iframes (web) or WebViews (iOS/Android)
Quick reference
Authentication
| Context | Method | Header |
|---|---|---|
| Server-side API calls | Secret key | Authorization: Api-Key sk_test_123 or sk_live_123 |
| Client-side API calls | Access token | Authorization: Bearer <accessToken> |
| Frame initialization | Client token | Pass clientToken to frame setup |
API Endpoints (core workflow)
| Endpoint | Purpose |
|---|---|
POST /session |
Create session token (server-side) |
POST /quotes |
Get executable quote with fees and limits |
GET /payment-methods |
List available payment methods for customer |
GET /transactions |
List transactions with pagination |
GET /transactions/{id} |
Get single transaction details |
Credential types
| Credential | Scope | Persistence | Use case |
|---|---|---|---|
| Session token | One-time setup | Don't persist | Initialize connect flow on frontend |
| Access token | API calls | In-memory only | Make authenticated API requests from frontend |
| Client token | Frame auth | In-memory only | Initialize payment frames (Apple Pay, etc.) |
| Secret key | Server operations | Secure storage | Create sessions, server-to-server calls |
Frame types
| Frame | Type | Use case |
|---|---|---|
| Connect | Co-branded | Customer login/KYC, returns access + client tokens |
| Check | Invisible | Verify existing connection without UI |
| Apple Pay | Headless | Execute Apple Pay transactions |
Transaction statuses
| Status | Meaning |
|---|---|
pending |
Payment accepted, assets transferring |
complete |
Payment finalized, assets delivered |
failed |
Payment failed, no funds transferred |
Decision guidance
When to use SDK vs manual frame integration
| Scenario | Use SDK | Use manual |
|---|---|---|
| You want drop-in frame setup with event handling | ✅ | |
| You need custom frame wrapper or WebView bridge | ✅ | |
| You're building with React/TypeScript | ✅ | |
| You're integrating into existing WebView infrastructure | ✅ | |
| You want automatic message serialization/validation | ✅ |
When to check connection vs render connect flow
| Condition | Action |
|---|---|
| Customer is new (first visit) | Render connect frame directly |
| Customer has connected before | Check connection first; render only if expired |
Connection check returns connectionRequired |
Render connect flow |
Connection check returns active |
Skip UI, use returned credentials |
When to use price quotes vs executable quotes
| Quote type | When to use |
|---|---|
| Price quote | Estimate costs before showing confirmation (no connection required) |
| Executable quote | Execute actual transaction (requires active connection) |
Workflow
1. Set up server-side session creation
Create a session endpoint on your server that:
- Takes a unique customer ID and device IP
- Calls
POST /sessionwith your secret key - Returns the
sessionTokento your frontend
const res = await fetch("https://api.moonpay.com/platform/v1/session", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Api-Key sk_test_123",
},
body: JSON.stringify({
externalCustomerId: "user_123",
deviceIp: "203.0.113.1",
}),
});
const { sessionToken } = await res.json();
2. Initialize client and check connection
On your frontend, create the SDK client and check if customer has an active connection:
const clientResult = createClient({ sessionToken });
const client = clientResult.value;
const checkResult = await client.checkConnection();
if (checkResult.value.status === "active") {
// Use existing credentials
const { accessToken, clientToken } = checkResult.value.credentials;
} else {
// Render connect flow
}
3. Render connect flow if needed
If connection is missing or expired, render the co-branded connect frame:
const connectResult = await client.connect({
container: document.querySelector("#connect-container"),
onEvent: (event) => {
if (event.kind === "complete") {
const { accessToken, clientToken } = event.connection.credentials;
// Store in memory, proceed to payment flow
}
},
});
4. List payment methods and get quotes
After connection, fetch available payment methods and get a quote:
const methodsResult = await client.getPaymentMethods();
const quoteResult = await client.getQuote({
source: "USD",
destination: "ETH",
sourceAmount: "100.00",
walletAddress: "0x...",
paymentMethod: "apple_pay",
});
if (quoteResult.value.challenge) {
// Handle challenge (see handling challenges section)
}
5. Execute transaction
Set up the payment frame and handle transaction events:
const applePayResult = await client.setupApplePay({
quote: quoteResult.value.signature,
container: document.querySelector("#apple-pay-container"),
onEvent: (event) => {
switch (event.kind) {
case "ready":
// Frame is ready, show button
break;
case "complete":
// Transaction initiated, track via polling or webhooks
const txnId = event.payload.transaction.id;
break;
case "quoteExpired":
// Fetch new quote and update frame
break;
case "error":
// Handle error, offer retry or alternate method
break;
}
},
});
6. Track transaction status
Poll the transaction endpoint or use webhooks to monitor status:
const txnResult = await fetch(
`https://api.moonpay.com/platform/v1/transactions/${txnId}`,
{
headers: { "Authorization": `Bearer ${accessToken}` },
}
);
const transaction = await txnResult.json();
// Check transaction.status: pending, complete, or failed
Common gotchas
- Never persist client credentials: Access tokens and client tokens expire and should only be held in memory. Fetch a new session token on each app visit.
- Secret key exposure: Never commit your secret key to code or send it to the frontend. Always create sessions server-side.
- Quote expiration: Quotes expire at
expiresAt. Check this before executing and fetch a new quote if needed. - Missing CSP rules: On web, your Content Security Policy must allow
frame-srcandconnect-srcfor*.moonpay.com. - Handshake timeout: If integrating frames manually, expect a handshake within 5 seconds. Fail fast if it doesn't arrive.
- Channel ID routing: When multiple frames are open, use
meta.channelIdto route messages correctly and prevent cross-talk. - Test mode vs live: Test mode is determined by your API key (
sk_test_...vssk_live_...), not the URL. Use test keys for development. - Real email/phone in test mode: Test accounts require real email and phone numbers for OTP verification. Use the
+suffix pattern to create multiple addresses from one inbox. - Executable quote requirements: Executable quotes require an active connection. If
executable: false, the customer must complete a challenge first. - Transaction polling: Transactions start as
pending. Use polling or webhooks to track when they reachcompleteorfailed. - Apple Pay domain verification: On web, you must complete Apple's domain verification to use Apple Pay. This is a manual process.
- WKWebView configuration: On iOS, set
allowsInlineMediaPlayback = truefor the connect frame to work properly.
Verification checklist
Before submitting work, verify:
- Session token is created server-side with secret key, never exposed to frontend
- Client credentials (access token, client token) are stored in memory only, not persisted
- Connection status is checked before rendering connect flow
- Quote signature is passed correctly to payment frame
- Quote expiration is checked before executing transaction
- All frame events are handled (ready, complete, error, quoteExpired, unsupported)
- Transaction ID is captured from
completeevent and tracked via polling or webhooks - Challenges are detected and rendered as full-screen frames on mobile
- Error responses include proper error handling and user-facing messages
- CSP headers allow
frame-srcandconnect-srcfor*.moonpay.com(web only) - Test mode uses
sk_test_...API key; production usessk_live_... - postMessage validation checks origin, version, and channelId (manual integrations)
Resources
Comprehensive page listing: https://moonpay.mintlify.app/llms.txt
Critical documentation pages:
- Core Concepts — Understand connections, frames, challenges, and quotes
- Connect a Customer — Step-by-step customer onboarding and connection flow
- Pay with Apple Pay — Complete transaction execution example with event handling
For additional documentation and navigation, see: https://moonpay.mintlify.app/llms.txt