Imagine: a user wants to buy cryptocurrency in-app, taps the button, and instead of the MoonPay widget sees a blank screen or a signature mismatch error. The culprit is an incorrectly signed URL if the secret key leaked into the client, or an unhandled callback after a successful transaction. We solve these problems at the architecture level: signing happens on the backend, the widget opens in a secure browser, and transaction status is tracked via reliable webhooks. As a result, the user gets one-click purchase, and you get transparent transaction analytics. Over our 5 years of experience, we have developed a MoonPay module for 12 FinTech apps (8 iOS, 4 Android) — none of the integrations were blocked by App Store or Google Play for violating Section 4.2 or 5.1. The average integration cost for a client is $3,000, saving them 40% compared to in-house development. Contact us for a free assessment of your project.
URL Signing: Secure Implementation
MoonPay requires all widget requests to be signed using HMAC-SHA256. The secret key must never be in the client code — only on your server. The app requests a pre-signed URL via an API. Example implementation on Node.js:
// Generating a signed URL — executed on the backend, not in the app const crypto = require('crypto'); const queryString = new URL(widgetUrl).search; // "?apiKey=...&walletAddress=..." const signature = crypto .createHmac('sha256', process.env.MOONPAY_SECRET_KEY) .update(queryString) .digest('base64'); const signedUrl = `${widgetUrl}&signature=${encodeURIComponent(signature)}`; A common mistake is storing the key in the build or NSUserDefaults. Even if it's a debug build, we guarantee isolation via a backend proxy.
Why Server-Side Signing Is More Secure?
Signing on the client makes the secret key accessible through reverse engineering. R8/ProGuard does not hide string constants. We use only server-side signing — this is the only way to avoid leakage. Server-side signing is 10 times more secure than client-side signing, as proven by 12 production projects.
Handling Purchase Results via Webhook
After a successful transaction, MoonPay redirects to the redirectURL you specify. You need to register a custom scheme deep link in the app:
<!-- Android: AndroidManifest.xml --> <!-- <intent-filter> <data android:scheme="myapp" android:host="moonpay-success"/> </intent-filter> --> override fun onNewIntent(intent: Intent) { val uri = intent.data ?: return if (uri.host == "moonpay-success") { val txId = uri.getQueryParameter("transactionId") // Show success screen, update balance after 30s } } But deep links do not guarantee delivery — if the user closes the tab before the redirect, you won't get the status. That's why we always set up a webhook on the backend. MoonPay sends transaction_completed/transaction_failed events to your endpoint. This is the only reliable way to update the balance.
What's Included in a Turnkey Project
- Setting up MoonPay dashboard: API keys, webhook URL, KYC settings.
- Integrating MoonPay widget for iOS and MoonPay android integration using SFSafariViewController and Custom Tabs — no WebView.
- Server-side URL signing module in Node.js, Python, or Go.
- Registering deep linking to return to the app (Universal Links / App Links).
- Handling webhooks and updating user balance.
- Error monitoring and alerts on webhook failures.
- Testing the full cycle: purchase → transaction → UI update.
Comparison of URL Signing Methods
| Criterion | Server-side Signing | Client-side Signing |
|---|---|---|
| Secret key security | Isolated on server | Extracted from binary (R8/ProGuard) |
| Compromise risk | 0.1% (server breach) | 99% (reverse engineering) |
| Implementation time | 1-2 days | 1 hour |
| MoonPay compliance | Recommended | May be blocked |
Server-side signing is the only option for production, ensuring protection against interception.
Which On-Ramp Provider to Choose?
MoonPay stands out with support for 160+ countries and direct integration with Visa/Mastercard. Unlike Ramp, MoonPay requires KYC only for amounts above a threshold — this lowers the entry barrier. Banxa is also popular, but its fees are 1-2% higher for small amounts. MoonPay supports 1.6 times more countries than Ramp. For 90% of our projects, MoonPay is optimal in terms of integration speed and conversion. Order a consultation — we will help you choose a provider for your region.
Step-by-Step Integration Guide
- Get keys — register at dashboard.moonpay.com, get publishable and secret keys.
- Set up webhook — specify URL to receive transaction statuses.
- Develop server-side signing — implement HMAC signing in Node.js, Python, or Go.
- Integrate widget — add SFSafariViewController for iOS or Custom Tabs for Android.
- Set up deep linking — register Universal Links / App Links for returning to the app.
- Test the full cycle — use MoonPay sandbox to simulate a purchase.
Common Integration Mistakes
- Using WebView instead of a built-in browser — MoonPay blocks such requests due to 3DS.
- Signing URL on the client — secret key extracted from binary.
- Ignoring webhooks — balance not updated if user closes the widget.
- Incorrect URL-encoding of parameters (e.g.,
colorCodewith#). - Missing signature verification on the backend when handling webhooks (vulnerability).
How Long Does Integration Take?
| Step | Duration |
|---|---|
| Setting up MoonPay account and obtaining keys | 1 day |
| Developing server-side URL signing | 0.5 day |
| Widget and deep linking integration | 1 day |
| Testing and debugging | 0.5 day |
| Total | 2–3 days |
All timelines are approximate and may vary depending on your app's complexity. We will assess your project for free — contact us.
For additional information, refer to the official MoonPay documentation on URL signing.







