Skip to main content
This guide is for developers who have completed the Quickstart and want a production integration: server-minted widget URL, prefilled and locked values authored by your backend, embedding, mobile returns, and go-live checks.

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

Minting requires a secret key and must happen on your server. Never ship a secret key to a browser or mobile app. That restriction is the point of the feature: it is what stops someone who scraped your public key from crafting their own prefilled checkout URL.
Call POST /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:
Response:
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.
Treat the minted URL like a session link: anyone holding it can open your checkout. Serve it over HTTPS and hand it to one customer. Meld stores only a hash of the token, so nobody, including Meld, can recover a live URL from the database.
Everything you want prefilled goes in prefill. Everything the customer must not change goes in locked, by field name.
Response:

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,000
  • SELL, 0.0001 to 1,000,000
  • sessionType omitted, 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:
Valid names: 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.
Field locking applies to the Meld Checkout interface. If you lock sourceCurrencyCode, destinationCurrencyCode, or walletAddress, the field is also locked in the provider’s own widget for the providers that support it. See White-Label customization → Locking fields for the provider list.
externalSessionId limits the URL to a single session. The value is not checked for uniqueness when you mint, but session creation rejects a reused one with DUPLICATE_EXTERNAL_SESSION_ID, including when the same customer retries after a failed attempt on the same URL. Set it only when you want exactly one session per minted URL. Otherwise omit it and correlate on the session id from webhooks.

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, and payment for KYC and payments
  • Full height: append &fullScreen=true to the minted URL and size the frame to 100vh

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 theme on 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
  • redirectUrl and webhooks tested end to end
  • ✅ First real transactions monitored

Troubleshooting

Mint returns 400 on publicKey. 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