DocsAPI Reference
Log In
Docs

White-Label API Guide

Build Your Own UI with Meld APIs

This comprehensive guide walks you through building a custom cryptocurrency interface using Meld's White-Label API Integration. Perfect for developers who want complete control over the user experience while leveraging Meld's crypto infrastructure.

⚠️

Important: This is White-Label API Integration, NOT Widget Integration (Public Key URL). This approach requires custom UI development and API integration.


⚠️

CRITICAL: API Changes & Compatibility

Non-Breaking Changes (No Versioning):

  • New fields may be added to API responses at any time
  • Your system MUST handle unexpected fields gracefully
  • Avoid strict JSON validation that rejects unknown properties

Breaking Changes (Versioned):

  • Field modifications or deletions will be versioned
  • Released approximately with advance notice

Required Actions:

  • ✅ Use flexible JSON parsing (ignore unknown fields)
  • ✅ Implement defensive coding practices
  • ✅ Test with mock responses containing extra fields
⚠️

IMPORTANT: Automatic Onramp Provider Management

Automatic Additions:

  • Meld may automatically enable onramps for your account based on:
    • Conversion rates and user success
    • Competitive pricing
    • Geographic coverage for your users

Automatic Removals:

  • Onramps may be disabled due to:
    • Compliance or regulatory issues
    • Technical problems or outages
    • Provider service interruptions

Manual Control:

  • Contact Meld to opt out of automatic management
  • Request specific provider additions/removals
  • Set custom provider preferences for your account

Prerequisites

Before starting, ensure you have:

  • API key from the Meld dashboard
  • Webhook endpoint (recommended)
  • Basic understanding of REST APIs
  • Development environment set up
💡

New to Meld? Complete the Quick Start Guide first to get familiar with the basics.


Step 1: Get User Location

Get the user's country to set default values and compliance requirements.

💡

Best Practice: Ideally, automatically detect the user's country from their device/browser using geolocation or IP detection rather than prompting them to manually select.

Auto-Detection (Recommended):

// Sample Code: Auto-detect user's country
function detectUserCountry() {
  // Option 1: Browser geolocation API
  if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition((position) => {
      const country = getCountryFromCoordinates(position.coords);
      return country;
    });
  }
  
  // Option 2: IP-based detection
  const country = await fetch('/api/detect-country').then(r => r.json());
  return country;
}

Manual Override Option:

If users choose to override the auto-detected country, they can get a list of supported countries and flags from this API:

// Sample Code: Get supported countries list with flags
GET /service-providers/properties/countries?accountFilter=true

For US Users - State Requirements:

// Sample Code: Also collect state information for US users
// Some US states have heavy restrictions on crypto and may not be supported
const location = "US-NY"; // Format: Country-State

Screenshot: Meld Widget showing automatic country detection with manual override option

API Response Example:

{
  "countries": [
    {
      "countryCode": "US",
      "countryName": "United States",
      "states": ["US-NY", "US-CA", "US-TX", ...]
    },
    {
      "countryCode": "GB", 
      "countryName": "United Kingdom"
    }
  ]
}

Step 2: Get Currency and Payment Defaults

Based on the user's country, get default settings to streamline the experience.

Get Country Defaults:

// Sample Code: Get default currency and payment method for country
GET /service-providers/properties/defaults/by-country?countries={countryCode}

Response:

[
  {
    "countryCode": "BR",
    "defaultCurrencyCode": "BRL",
    "defaultPaymentMethods": [
      "PIX",
      "CREDIT_DEBIT_CARD",
      "BINANCE_CASH_BALANCE"
    ]
  }
]
📋

Note: Response is an array. Default payment methods returns an ordered list with the most recommended option first.

Get Available Fiat Currencies:

Users can choose to use a different fiat currency than the default, so call this endpoint to populate a currency selection menu:

// Sample Code: Get all available fiat currencies for the country
GET /service-providers/properties/fiat-currencies?countries={countryCode}&accountFilter=true

Get Payment Methods:

// Sample Code: Get payment methods available for selected fiat currency
GET /service-providers/properties/payment-methods?fiatCurrencies={fiatCurrency}&accountFilter=true

UI Implementation:

// Sample Code: Set defaults but allow user override
const defaults = countryDefaults[0]; // Get first country from response array
const userSettings = {
  country: detectedCountry,
  fiatCurrency: defaults.defaultCurrencyCode || 'USD',
  paymentMethod: defaults.defaultPaymentMethods[0] // Use first (most recommended) option
};

// Provide dropdowns for user to change defaults
renderCurrencySelector(availableFiatCurrencies);
renderPaymentMethodSelector(availablePaymentMethods);

Screenshot: Meld Widget showing default fiat currency and payment method selection


Step 3: Get Cryptocurrencies

Display available cryptocurrencies based on user's location and preferences.

API Call:

// Sample Code: Get available cryptocurrencies for the country
GET /service-providers/properties/crypto-currencies?countries={countryCode}&accountFilter=true

Response Example:

{
  "cryptoCurrencies": [
    {
      "currencyCode": "BTC",
      "currencyName": "Bitcoin",
      "networkCode": "BTC",
      "networkName": "Bitcoin"
    },
    {
      "currencyCode": "ETH_ETHEREUM",
      "currencyName": "Ethereum",
      "networkCode": "ETHEREUM",
      "networkName": "Ethereum"
    }
  ]
}

UI Implementation:

// Sample Code: Create searchable crypto selector
function renderCryptoSelector(cryptoCurrencies) {
  return cryptoCurrencies.map(crypto => ({
    value: crypto.currencyCode,
    label: `${crypto.currencyName} (${crypto.networkName})`,
    icon: getCryptoIcon(crypto.currencyCode)
  }));
}

Screenshot: Meld Widget showing cryptocurrency selection interface


Step 4: Get Amount and Purchase Limits

Validate user input against provider limits and display helpful guidance.

Get Purchase Limits:

// Sample Code: Get purchase limits for validation
GET /service-providers/limits/fiat-currency-purchases?accountFilter=true

Response Example:

{
  "limits": {
    "USD": {
      "minAmount": 20,
      "maxAmount": 20000,
      "dailyLimit": 5000,
      "monthlyLimit": 20000
    }
  }
}

User Input Validation:

// Sample Code: Validate user input against limits
function validateAmount(amount, currency, limits) {
  const limit = limits[currency];
  
  if (amount < limit.minAmount) {
    return {
      valid: false,
      message: `Minimum purchase: $${limit.minAmount}`
    };
  }
  
  if (amount > limit.maxAmount) {
    return {
      valid: false,
      message: `Maximum purchase: $${limit.maxAmount}`
    };
  }
  
  return { valid: true };
}

Screenshot: Meld Widget showing amount input with purchase limits validation

Caching Note:

🚀

Performance Tip: The data in steps 1-4 rarely changes. Cache responses for 1 week to reduce latency.


Step 5: Get a Real-Time Quote

Fetch live pricing from multiple providers and display options to the user.

API Call:

// Sample Code: Get real-time quotes from multiple providers
POST /payments/crypto/quote

Request Body:

{
  "sourceAmount": "200",
  "sourceCurrencyCode": "USD",
  "destinationCurrencyCode": "ETH",
  "countryCode": "US",
  "walletAddress": "0xfCFAa8059080D01b27ccA2B1fA086df0853397E6"
}

Response Example:

{
  "quotes": [
    {
      "transactionType": "CRYPTO_PURCHASE",
      "sourceAmount": 200.00,
      "sourceAmountWithoutFees": 195.83,
      "fiatAmountWithoutFees": 195.83,
      "destinationAmountWithoutFees": null,
      "sourceCurrencyCode": "USD",
      "countryCode": "US",
      "totalFee": 4.17,
      "networkFee": 0.17,
      "transactionFee": 2,
      "destinationAmount": 0.04308219,
      "destinationCurrencyCode": "ETH",
      "exchangeRate": 4642.290,
      "paymentMethodType": "CREDIT_DEBIT_CARD",
      "customerScore": 20.00,
      "serviceProvider": "UNLIMIT",
      "institutionName": null,
      "lowKyc": true,
      "partnerFee": 2
    },
    {
      "transactionType": "CRYPTO_PURCHASE",
      "sourceAmount": 200,
      "sourceAmountWithoutFees": 196.93,
      "fiatAmountWithoutFees": 196.93,
      "destinationAmountWithoutFees": null,
      "sourceCurrencyCode": "USD",
      "countryCode": "US",
      "totalFee": 3.07,
      "networkFee": 0.11,
      "transactionFee": 1,
      "destinationAmount": 0.042577,
      "destinationCurrencyCode": "ETH",
      "exchangeRate": 4697.4,
      "paymentMethodType": "CREDIT_DEBIT_CARD",
      "customerScore": 19.76,
      "serviceProvider": "ROBINHOOD",
      "institutionName": null,
      "lowKyc": null,
      "partnerFee": 1.96
    }
  ],
  "message": null,
  "error": null,
  "timestamp": null
}

Response Fields Explained:

  • customerScore: Meld's recommendation score (higher = better conversion likelihood)
  • lowKyc: Whether provider requires minimal identity verification

Screenshot: Meld Widget showing multiple provider quotes with ranking

🏆 MELD RECOMMENDED QUOTE RANKING

Use Meld'scustomerScore for optimal conversion rates!

Meld provides a customerScore in each quote that represents the likelihood of transaction success based on:

  • Historical conversion rates for similar transactions
  • Provider reliability and success rates
  • User location and payment method compatibility
  • Real-time provider performance data

Recommended Quote Sorting:

// Sample Code: Use Meld's recommended ranking for best results
function rankQuotesByMeldScore(quotes) {
  return quotes.sort((a, b) => {
    // Primary: Sort by customerScore (higher = better)
    if (a.customerScore !== b.customerScore) {
      return b.customerScore - a.customerScore;
    }
    
    // Secondary: Sort by destinationAmount (more crypto = better)
    return b.destinationAmount - a.destinationAmount;
  });
}
🚀

Best Practice: Always display the highest customerScore quote first to maximize transaction success rates and user satisfaction.

📊 Learn More: See Conversion Routing for advanced ranking strategies.


Step 6: Launch Provider Payment UI

When user selects a quote, create the provider session and get a provider URL to launch their payment UI in a webview, new tab, or redirect.

API Call:

// Sample Code: Create widget session to get provider URL
POST /crypto/session/widget

Request Body:

{
  "sessionData": {
    "walletAddress": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
    "countryCode": "US",
    "sourceCurrencyCode": "USD",
    "sourceAmount": "100",
    "destinationCurrencyCode": "BTC",
    "serviceProvider": "TRANSAK",
    "redirectUrl": "https://yourapp.com/transaction-complete"
  },
  "sessionType": "BUY",
  "externalCustomerId": "user_123",
  "externalSessionId": "session_456"
}

Response Example:

{
  "id": "WePLapZetkn1hfeKFScf3T",
  "externalSessionId": "session_456",
  "externalCustomerId": "user_123", 
  "customerId": "WXEX4DsAX7cp6Ch78oq2w3",
  "widgetUrl": "https://meldcrypto.com?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Provider UI Launch Implementation:

Desktop Implementation:

// Sample Code: Launch provider UI in popup window
function launchProviderUI(sessionResponse, options = {}) {
  const popup = window.open(
    sessionResponse.widgetUrl,
    'meld-widget',
    'width=450,height=700,scrollbars=yes,resizable=yes'
  );
  
  // Note: Meld will redirect the user back when the transaction is complete
  // The popup will need to be closed based on your application's flow
}

Mobile Implementation:

// Sample Code: Launch provider UI on mobile
function launchMobileProviderUI(sessionResponse) {
  // For webviews
  if (isMobileApp()) {
    window.ReactNativeWebView?.postMessage(
      JSON.stringify({ 
        type: 'OPEN_WEBVIEW',
        url: sessionResponse.widgetUrl 
      })
    );
  } else {
    // Mobile browser - full redirect
    window.location.href = sessionResponse.widgetUrl;
  }
}

Screenshot: Meld Widget launching onramp service provider UI

Important Notes:

⚠️

Redirect Domain Whitelisting: Some providers require redirect domain whitelisting. Contact Meld support if provider UIs don't redirect back properly.

📱 Mobile Considerations: Webviews are recommended for mobile apps, but external browser works too.


🔄

Transaction Completion: Meld does not send completion events or use redirect parameters. When a transaction is complete, Meld simply redirects the user back to your specified redirectUrl. You should track transaction status through webhooks, not through redirect handling.


Step 7: Track Transaction

Monitor transaction progress and update your UI as the user completes the flow.

📡

Webhook Setup Required: Sign up for webhooks in the Meld Dashboard. You will receive a webhook each time a transaction is created or updated.

Webhook Integration:

// Sample Code: Webhook endpoint receives transaction updates
app.post('/webhook/meld', (req, res) => {
  const { transactionId, status, externalCustomerId } = req.body;
  
  // Use webhook as trigger to fetch full transaction details
  const transactionDetails = await fetchTransactionDetails(transactionId);
  
  // Update user's transaction in your database
  updateTransaction(transactionId, transactionDetails);
  
  // Notify user via websockets, push notifications, etc.
  notifyUser(externalCustomerId, {
    transactionId,
    status,
    message: getStatusMessage(status)
  });
  
  res.status(200).send('OK');
});

// Sample Code: Fetch transaction details when webhook arrives
async function fetchTransactionDetails(transactionId) {
  const response = await fetch(
    `/payments/transactions/${transactionId}`,
    {
      headers: {
        'Authorization': `BASIC ${apiKey}`,
        'Meld-Version': '2025-03-04'
      }
    }
  );
  return await response.json();
}

Transaction Status Display:

// Sample Code: Display user-friendly status messages
function getStatusMessage(status) {
  const statusMessages = {
    'PENDING': 'Processing your transaction...',
    'SETTLING': 'Finalizing crypto transfer...',
    'SETTLED': 'Complete! Crypto sent to your wallet.',
    'FAILED': 'Transaction failed. Please try again.',
    'CANCELLED': 'Transaction was cancelled.'
  };
  
  return statusMessages[status] || 'Transaction status updated.';
}

Screenshot: Meld Widget showing transaction status tracking

📋

Status Reference: See Transaction Statuses for complete status definitions.


API Response Caching Guide

Optimize performance by caching static data and keeping real-time calls fresh.

Cache Weekly (Static Data):

const CACHE_WEEKLY = 7 * 24 * 60 * 60 * 1000; // 1 week in milliseconds

// Cache these API responses:
- Countries list
- Fiat currencies by country
- Payment methods by currency
- Crypto currencies by country  
- Purchase limits by currency

Real-Time Calls (Never Cache):

// Always fetch fresh:
- Crypto quotes
- Widget session creation
- Transaction details
- Transaction status

Implementation Example:

class MeldAPICache {
  constructor() {
    this.cache = new Map();
  }
  
  async getCountries() {
    const cacheKey = 'countries';
    const cached = this.cache.get(cacheKey);
    
    if (cached && Date.now() - cached.timestamp < CACHE_WEEKLY) {
      return cached.data;
    }
    
    const data = await this.fetchCountries();
    this.cache.set(cacheKey, {
      data,
      timestamp: Date.now()
    });
    
    return data;
  }
}

Best Practices & Tips

Performance Optimization:

  • Cache static data for 1 week
  • Debounce quote requests when user types amounts
  • Preload country/currency data on app startup
  • Use CDN for crypto icons and assets

User Experience:

  • Show loading states during API calls
  • Display fees clearly before transaction
  • Provide transaction estimates (settlement time)
  • Handle errors gracefully with retry options

Error Handling:

async function handleAPIError(error, endpoint) {
  const errorMap = {
    401: 'Invalid API key or authentication failed',
    403: 'Access forbidden - check account permissions', 
    429: 'Rate limit exceeded - please wait and retry',
    500: 'Server error - please try again later'
  };
  
  const message = errorMap[error.status] || 'Unknown error occurred';
  
  // Log for debugging
  console.error(`API Error (${endpoint}):`, error);
  
  // Show user-friendly message
  showErrorToUser(message);
}

Security Considerations:

  • Never expose API keys in frontend code
  • Validate all user inputs before API calls
  • Use HTTPS only for API communications
  • Implement rate limiting to prevent abuse

Next Steps

Testing Your Implementation:

Advanced Features:

Production Deployment:


Support & Resources

This guide provides everything needed to build a production-ready crypto interface. Most teams complete their custom UI integration within 1-2 weeks using this approach.