Before you begin
- A backend that can make an authenticated HTTPS request
- You have private and public keys for both sandbox and production
- You know where in your app users should land before and after the checkout
Step 1: Mint a widget URL
CallPOST /crypto/session/widget/url from your backend. Use your private/secret key for authorization and your public key in the request body. For the basic flow, the body needs one field, your public key:
Use the URL exactly as returned. The host comes from your account’s configured checkout domain. If you are on a white-labeled domain, the minted URL already points at it, so do not rebuild the link from
meldcrypto.com.prefill. Everything the customer must not change goes in locked, by field name.
Request fields
Prefill fields
sourceAmount ranges depend on sessionType, because the field means different things in each flow. An out-of-range value is rejected, never adjusted:BUY, 1 to 10,000,000SELL, 0.0001 to 1,000,000sessionTypeomitted, 0.0001 to 10,000,000, the widest of the two
Locking fields
locked takes field names, not the value-carrying *Locked parameter names from the query-string era:
walletAddress, destinationCurrencyCode, sourceCurrencyCode, sourceAmount, paymentMethodType, countryCode. Names are case-insensitive, but anything else, including the old walletAddressLocked and lockWalletAddress forms, fails the request with a 400 rather than being silently dropped.
Step 2: Get the customer to the checkout
Mint when the customer acts, then hand the URL to the browser. Do not mint on page load and cache the URL across customers.Iframe best practices
- Minimum width: 450px
- Minimum height: 790px, to prevent UI overlap
- Permissions: include
camera,microphone, andpaymentfor KYC and payments - Full height: append
&fullScreen=trueto the minted URL and size the frame to100vh
Step 3: Adjust presentation on the minted URL
Presentation parameters have no equivalent in a stored configuration, so append them to the URL Meld returned:
Never append prefill or lock parameters to a minted URL, they are ignored. Mint again with different values instead.
Mobile considerations:
- Mint on your server, not in the app, the app cannot hold a secret key
- Test the deep-link return on both iOS and Android
- Pass
themeon the URL to match your app’s appearance
Step 4: Test in sandbox
1
Mint with your real prefill shape
Use the same payload your production code will send, against
https://api-sb.meld.io.2
Verify locks in the UI
Open the URL and confirm every field in
locked is read-only, and that the buy/sell toggle is hidden if you set sessionType.3
Confirm query parameters are ignored
Append
&walletAddress=SOMETHING_ELSE to the minted URL. The stored wallet must win, that is the security property you are shipping.4
Complete a transaction
Use the sandbox test credentials, then check webhooks and the dashboard Transactions tab.
Step 5: Go live
- ✅ Minting runs on your server with a production secret key held outside your source tree
- ✅ Production public key is paired with the production secret key (mints are rejected across accounts)
- ✅ URLs are minted per checkout, not cached or shared
- ✅ Locks verified in the UI for every field you rely on
- ✅
redirectUrland webhooks tested end to end - ✅ First real transactions monitored
Troubleshooting
Mint returns 400 onpublicKey. You might be sending the secret key twice, or mixing a sandbox key with a production key. The public and secret keys must be two different profiles on the same account and environment.
Nothing is prefilled when the URL opens. Either the token has aged out of its retention window, or the URL was altered in transit. A modified token cannot be resolved, and the checkout then denies prefill rather than trusting the query string. Mint a fresh URL.
Locks are not applied. Confirm the field name is in locked and that the same field carries a value in prefill; a lock without a value is rejected at mint time, so a successful mint means both are present.
Checkout will not load in an iframe. Check the frame permissions (camera, microphone, payment), the 450×790 minimum, and any popup blocker on the parent page.
Next steps
- Request reference, every field and query parameter in one place
- Customization, styling and advanced options
- Webhook events, real-time notifications
- Transaction statuses, understanding transaction states