# Account Verification Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/account-verification Account verification confirms that a user owns and has access to a specific bank account. Developers use it most commonly before initiating an ACH transaction so that they know the user is authorized to move money from the bank account in question. ## How it works Account verification involves creating a connection that requests the [OWNERS](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-owners) and [IDENTIFIERS](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-identifiers) products: * Through the **OWNERS** product you get the user's name and address. * Through the **IDENTIFIERS** product you get the user's account number and routing number. Once the connection completes, you have verified the user's identity and access to the bank account and can perform an ACH transaction. Every bank linking connection has a Meld `customerId` associated with it. Use that same `customerId` when creating a payments customer so that the bank account and payments customer are linked. # Android App to App Authentication Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/app-to-app-authentication/android-app-to-app-authentication This page outlines the steps to load the Meld bank linking widget inside your Android mobile app using a WebView, and to support App to App authentication with banks that have implemented it. ## Before you begin * An Android project (Kotlin) where you can integrate a `WebView`. * A domain you control for [Android App Links](https://developer.android.com/training/app-links). * A Meld API key and the ability to call `/bank-linking/connect/start` to obtain a widget URL. # Project Setup ## Step 1: App Links Setup App Links are the Android equivalent of iOS's universal links. ### App Links Step 1: Setup Android app links following this [guide](https://developer.android.com/training/app-links). * Here is an example of what **AndroidManifest.xml** will look like: ```xml XML theme={null} ``` ### App Links Step 2: Redirect URL * We need to pass the `redirectUrl` in Meld's `/bank-linking/connect/start` endpoint. * Ensure that the redirectUrl parameter is an HTTP URL and not a deep link. It should specifically be formatted as an Android App Link. For detailed guidance on creating App Links, refer to the instructions provided in this [guide](https://developer.android.com/training/app-links). ### App Links Step 3: Opening the Widget URL in a Webview * Add the following settings to the webview to give the proper permissions: ```kotlin Kotlin theme={null} private lateinit var webView : WebView private lateinit var childWebView : WebView override fun onCreate(savedInstanceState: Bundle?) { // ... webView = findViewById(R.id.webView); // please name accordingly // Using a custom WebView Client as we have to override the url loading functionality webView.webViewClient = CustomWebViewClient() webView.settings.apply { domStorageEnabled = true javaScriptEnabled = true javaScriptCanOpenWindowsAutomatically =true } // This webView will handle the login screen childWebView = findViewById(R.id.childWebView); // please name accordingly childWebView.webViewClient = CustomWebViewClient() childWebView.settings.apply { domStorageEnabled = true javaScriptEnabled = true javaScriptCanOpenWindowsAutomatically =true } // This will handle the incoming meld events like connect_complete, handover, cancel etc // Follow step #3 for the implementation webView.addJavascriptInterface(JsObject(), "Android") } ``` * Here’s the definition of the **CustomWebViewClient** class: ```kotlin Kotlin theme={null} private inner class CustomWebViewClient :WebViewClient() { override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean { // we'll add the code here based on different providers val parsedUri = request?.url val uriString = parsedUri.toString() return false } } ``` # Enable Visibility To enable app to app authentication, you may need to declare specific apps as visible to your app by adding queries in your **AndroidManifest.xml** file. For example, to enable visibility for the Chase app, include the following: ```xml XML theme={null} // this will make the chase app visible to your app ``` The above example is specific to the Chase app. If you wish to enable app to app authentication with additional apps, include the respective package entries for each additional app in the `` section. ## Important Considerations for QUERY\_ALL\_PACKAGES Permission From Android 11 (API level 30), the **QUERY\_ALL\_PACKAGES** permission is restricted to apps that target API level 30 or later on devices running Android 11 or newer. To use this permission, your app must fulfill specific criteria outlined by the Google Play policy. This includes having a core purpose that requires visibility of all installed apps on the device and providing a sufficient justification as to why alternative, less intrusive methods of app visibility would not enable the app’s policy-compliant, user-facing functionality. In some cases, Android may restrict visibility of certain apps to your app. If your app needs to access a comprehensive list of all installed apps on the device, include the **QUERY\_ALL\_PACKAGES** permission in the Android manifest. Be aware that if you intend to publish your app on Google Play, the usage of this permission is subject to [Google Play’s approval process](https://support.google.com/googleplay/android-developer/answer/10158779). # Provider Specific Code Depending on which provider(s) your app uses, you will have to configure some provider specific code in order to make app to app authentication works. This section details what that code is. ## MX 1. To handle the MX service provider appropriately, incorporate the following code snippet within the **shouldOverrideUrlLoading** function: ```kotlin Kotlin theme={null} override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean { // ... val isMessageFromConnect = uriString.startsWith("meldapp://") || uriString.startsWith("atrium://") if (isMessageFromConnect) { // 2. Handle OAuth redirects if the URL is related to OAuth return mxHandler(parsedUri, view); } } fun mxHandler(uri: Uri?, view: WebView?): Boolean { if (uri?.path == "/oauthRequested") { try { val mxMetaData = JSONObject(uri?.getQueryParameter("metadata")) val oauthURL = mxMetaData.getString("url") val oauthPage = Uri.parse(oauthURL) if (isAppLink(context, oauthURL)) { val intent = Intent(Intent.ACTION_VIEW, oauthPage) view?.context?.startActivity(intent) } else { view?.loadUrl(oauthPage.toString()) } } catch (err: Exception) { Log.e("MX:Error with OAuth URL", err.message!!) } } return true } ``` ## Plaid and Finicity 1. For Plaid and Finicity, integrate the following code into the **shouldOverrideUrlLoading** function. This addition functions as an “else” case subsequent to the “if” condition handling the MX provider: ```kotlin Kotlin theme={null} val isMessageFromConnect = uriString.startsWith("://") || uriString.startsWith("atrium://") if (isMessageFromConnect) { // 2. Handle OAuth redirects if the URL is related to OAuth return mxHandler(parsedUri, view); } else { // 3. Handle HTTP(S) URLs (open in browser or load in WebView) if (parsedUri?.scheme == "https" || parsedUri?.scheme == "http" || uriString != widgetUrl) { if(parsedUri.toString().contains("") && uriString.contains("oauth_state_id")) { val oauthStateId = parsedUri?.getQueryParameter("oauth_state_id") val newWidgetUrl = Uri.parse(widgetUrl).buildUpon().appendQueryParameter("plaidStateId", oauthStateId) webView.visibility = View.GONE childWebView.visibility = View.VISIBLE childWebView.loadUrl(uriString) return true } if (isAppLink(context, parsedUri.toString())) { // checking if an app is available to handle this url view?.context?.startActivity(Intent(Intent.ACTION_VIEW, parsedUri)) return true } if(uriString != widgetUrl) { webView.visibility = View.GONE childWebView.visibility = View.VISIBLE childWebView.loadUrl(uriString) return true } } } ``` ```kotlin Kotlin theme={null} // Helper function to check if there is an app that can handle this URL fun isAppLink(context: Context, url: String): Boolean { val uri = Uri.parse(url) val intent = Intent(Intent.ACTION_VIEW, uri) val resolveInfo = context.packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) return resolveInfo != null } ``` # Listening to Front End Events 1. Currently these events work for only Plaid & Finicity, not MX. After a successful OAuth process Meld’s widget will emit a handful of events which will need to be appropriately handled when control returns back to the app of origin. 2. You can listen to couple of pre-defined events which can be used to detect if the authentication process is completed, canceled, or if there was an error. ```kotlin Kotlin theme={null} enum class ConnectHandlerResponse(val message: String) { /** * Emitted when the widget initializes */ INIT("[meld-connect]init"), /** * Emitted at various times with debug information: * When your customer cancels an integrated service provider's embedded widget * Whenever your customer navigates to another screen within an integrated service provider's embedded widget */ DEBUG("[meld-connect]debug"), /** * when the widget initializes */ INITIALIZE("[meld-connect]init"), /** * Emitted when the widget encounters an error * Will be accompanied by query-string metadata, containing details and reason keys, as available */ ERROR("[meld-connect]error"), /** * Emitted once the call to /connect/complete has finished * Will be accompanied by query-string metadata, containing: * institutionId: Meld's institution ID for the institution your customer chose to connect with * institutionName: The name of the same institution * accountCount: The number of accounts your customer has connected * accounts: Where available, an array of connected accounts including details such as name, type, and mask; null otherwise */ COMPLETE("[meld-connect]connect_complete"), /** * Emitted when the connect has been completed */ HANDOVER("[meld-connect]handover"), /** * Emitted when your customer cancels the widget */ CANCEL("[meld-connect]cancel") } private inner class JsObject { private val TAG = "MeldActivity" @JavascriptInterface fun postMessage( event : String) { if (!event.isNullOrEmpty() && event.startsWith("[meld-connect]")){ var eventData : String? = null val event = if (event.contains("?")) { val splitResult = event.split("?") eventData = splitResult[1] splitResult.firstOrNull() } else event when(connectHandlerResponseFindValueOf(event.orEmpty())){ ConnectHandlerResponse.DEBUG -> {} ConnectHandlerResponse.INITIALIZE -> {} ConnectHandlerResponse.ERROR -> {} ConnectHandlerResponse.COMPLETE -> { Log.d("connectComplete", eventData.toString()) } ConnectHandlerResponse.HANDOVER -> { // close the Meld Connect Widget setResult(RESULT_OK) finish() } ConnectHandlerResponse.CANCEL -> { setResult(RESULT_CANCELED) finish() } else -> {} } } else { Log.d(TAG, "postMessage: MELD: got unknown response: $event") } } } ```
# App to App Authentication Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/app-to-app-authentication/index App to App Authentication lets a user who launches the Meld bank picker inside your mobile app jump directly into the bank's native app to log in (using biometrics, for example), then return to your app once authenticated. Developers integrate it to improve conversion and user trust during bank linking. Meld supports this flow for Plaid, MX, and Finicity, with provider-specific code on each platform. ## How it works When enabled, a user who sees Meld's bank picker in your mobile app gets redirected to the bank's app to log in, then back to your app of origin afterward. This makes authentication easier and more secure — the user can log in with biometrics rather than typing a username and password, and they can feel more secure that their bank credentials are protected because they are using the bank's own app. If the user does not have their bank app installed on their phone, the bank login screen is launched in a webview instead. ## Supported banks App to app authentication is currently only supported for Chase, as it is a feature developed by each bank. Other banks are following suit and will have this feature available in the future. | | Plaid | Finicity | MX | | :------ | :------------ | :------------ | :---------- | | iOS | **Supported** | **Supported** | Coming Soon | | Android | **Supported** | Coming Soon | Coming Soon | ## Platform guides The subpages describe how to implement App to App Authentication in iOS Native (Swift) and Android Native (Kotlin) mobile apps. If you are using other technologies such as Flutter or React Native, you will need to adapt the code samples to fit your framework. * [iOS App to App Authentication](/docs/bank-linking/bank-linking-quickstart/app-to-app-authentication/ios-app-to-app-authentication) * [Android App to App Authentication](/docs/bank-linking/bank-linking-quickstart/app-to-app-authentication/android-app-to-app-authentication) # iOS App to App Authentication Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/app-to-app-authentication/ios-app-to-app-authentication This page outlines the steps to load the Meld bank linking widget inside your iOS mobile app using a `WKWebView`, and to support App to App authentication with banks that have implemented it. ## Before you begin * An iOS project in Xcode where you can integrate a `WKWebView`. * Access to your website's server to host the `apple-app-site-association` file. * An Apple Developer account to create and use App IDs and provisioning profiles. * A Meld API key and the ability to call `/bank-linking/connect/start` to obtain a widget URL. # Project Setup ## Step 1: Universal Links Setup Universal Links provide a seamless user experience by connecting a URL to a specific part of your iOS app, bypassing the browser. This guide is designed for beginners and explains how to set up and use Universal Links in your iOS applications. ### Universal Links Step 1: Enable Associated Domains * Create App ID: Log into the [Apple Developer Portal](https://developer.apple.com/), go to “Certificates, Identifiers & Profiles,” and create a new App ID for your project. Ensure that you enable “Associated Domains". * Update Xcode Project: In Xcode, open your project settings, select your target, and go to the “Signing & Capabilities” tab. Add the “Associated Domains” capability by clicking the “+” icon and selecting it from the list. ### Universal Links Step 2: Configure Your Website * Create Apple-App-Site-Association File: You need to create a JSON file named apple-app-site-association (without any file extension) and host it at the root of your HTTPS-enabled web server or in the .well-known\ subdirectory. The content should look like this: ```json JSON theme={null} com.apple.developer.associated-domains applinks:*.yourdomain.com ``` * Replace TEAMID.BUNDLEID in the above code with your actual team ID and bundle identifier. The paths array should contain the URLs you want to link to your app. * Host the File: Ensure the apple-app-site-association file is accessible via HTTPS without any redirects at https\://``.com/apple-app-site-association. ### Universal Links Step 3: Configure Associated Domains in Xcode * Modify Entitlements: In your Xcode project, under the “Associated Domains” capability you added earlier, add an entry: applinks:`` ### Universal Links Step 4: Handle Universal Links in Your App * Update AppDelegate: Open AppDelegate.swift and implement the following\ function to handle incoming universal links: ```swift Swift theme={null} func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { if userActivity.activityType == NSUserActivityTypeBrowsingWeb, let url = userActivity.webpageURL { // Handle URL and parse parameters if needed return true } return false } ``` This code checks if the incoming activity type is a web browsing session and prints the URL, but you’ll likely want to redirect the user to the appropriate part of your app based on the URL. ### Universal Links Step 5: Test Your Universal Links * Prepare Your Device: To test on a real device, ensure the device has the provisioning profile that includes the Associated Domains capability. * Test the Link: Send yourself an email or message with the link you configured earlier (e.g., https\://``.com/path/to/content). Tap the link on your device, and it should open your app directly, bypassing the browser. ### Troubleshooting Tips * Verify Your AASA File: Use online tools like Apple’s AASA validator to ensure your apple-app-site-association file is correctly configured and accessible. * Check Console Logs: If the universal link is not working, check the device’s console logs for any errors related to universal links or associated domains. By following these steps, you should be able to implement and test Universal Links in your iOS apps effectively, providing users with a direct link into your app from web content. ## Step 2: WKWebview Setup ### WKWebview Step 1: Adding to View Controller * Start by setting up a standard Xcode project and add a WKWebView to your ViewController. Ensure you configure the WKWebView’s javaScriptCanOpenWindowsAutomatically setting to true. ```swift Swift theme={null} let preferences = WKPreferences() preferences.javaScriptCanOpenWindowsAutomatically = true let webConfiguration = WKWebViewConfiguration() webConfiguration.preferences = preferences webConfiguration.userContentController.add(self, name: meldMessageHandler) webView = WKWebView(frame: .zero, configuration: webConfiguration) webView.navigationDelegate = self webView.uiDelegate = self ``` ### WKWebview Step 2: Load the Widget URL * After adding the WKWebView to your ViewController as described in the previous step, load the Widget URL, which you can obtain from the connect/start API. Once the Widget URL is successfully loaded into the WebView, it will display the bank picker interface. Ensure proper error handling and network checks are in place to manage the loading process smoothly. ### WKWebview Step 3: Updating the Code * Add the following code: ```swift theme={null} let appScheme = "meldapp://" // Your apps custom scheme let atriumScheme = "atrium://" // MX atrium's default scheme (deprecated) let mxScheme = "mx://" // MX default scheme ``` ### WKWebview Step 4: Manage Navigation Requests * To capture and address specific use-cases for specific service providers, developers need to manage all navigation requests within the WKWebView. This requires implementing the WKNavigationDelegate protocol, which provides methods to track and control the loading of web content. A crucial method to implement is **webView(\_:decidePolicyFor:decisionHandler:)**. This method determines whether a navigation request should proceed, be canceled, or handled differently based on the request’s characteristics. # Provider Specific Code Depending on which provider(s) your app uses, you will have to configure some provider specific code in order to make app to app authentication work. This section details what that code is. ## MX 1. This code needed since MX uses the custom URL schema and relies on loading the content inside the MX widget. It also extracts the url parts and loads inside the MX widget and decision handler cancels the navigating requests. ```swift theme={null} let isPostMessageFromMX = (urlString?.hasPrefix(appScheme) == true || urlString?.hasPrefix(atriumScheme) == true || urlString?.hasPrefix(mxScheme) == true) if (isPostMessageFromMX){ let urlc = URLComponents(string: urlString ?? "") let path = urlc?.path ?? "" // there is only one query param ("metadata") with each url, so just grab the first let metaDataQueryItem = urlc?.queryItems?.first if path == "/oauthRequested" { handleOauthRedirect(payload: metaDataQueryItem) } decisionHandler(.cancel) return } ``` 2. handleOauthRedirect handles the APP to APP or APP to web passing ```swift theme={null} func handleOauthRedirect(payload: URLQueryItem?) { let metadataString = payload?.value do { if let json = try JSONSerialization.jsonObject(with: Data(metadataString.utf8), options: []) as? [String: Any] { if let url = json["url"] as? String { print("Intercepted Request inside:handleOauthRedirect \(url)") let options: [UIApplication.OpenExternalURLOptionsKey: Any] = [ .universalLinksOnly: true, // Try to open the app via universal link ] UIApplication.shared.open(URL(string: url)!, options: options) { (success) in if success { print("The URL was successfully opened.") } else { print("The URL could not be opened.") self.presentWebView(with: URL(string: url)!) } } } } } catch let error as NSError { print("Failed to parse payload: \(error.localizedDescription)") } } ``` 3. The web view loaded in the case of MX is handled as follows in **webView(\_:decidePolicyFor:decisionHandler:)**: ```swift theme={null} let urlString = navigationAction.request.url?.absoluteString currentURLOpened = navigationAction.request.url let currentURLOpenedString = currentURLOpened?.absoluteString if(currentURLOpenedString!.contains("{{universal-link}}/?status")){ self.popupWebViewControllerExternal?.dismiss(animated: true) popupWebView = nil } ``` ## Plaid and Finicity 1. To manage HTTPS requests from Plaid and Finicity, handle the navigation within the **webView(\_:decidePolicyFor:decisionHandler:)** method of the WKNavigationDelegate. Use the decision handler to allow navigation when these requests occur, enabling the WKWebView to securely load the relevant webpage. 2. **Plaid specific:** Upon completing the Plaid process, it is essential to pass the **oauth\_state\_id** to the Meld backend. This should be done along with the initial URL that was saved earlier in the process. This specific code block is only for Plaid, and does not need to be implemented for Finicity. ```swift theme={null} if url.absoluteString.contains("{{universal-link}}/?oauth_state_id=") { let urlString = url.absoluteString self.dismiss(animated: true, completion: nil) if let range = urlString.range(of: "=") { let substring = urlString[range.upperBound...] Let request = URLRequest(url: URL(string: connectUrlToSave!.absoluteString + "&plaidStateId=\(substring)")!) webView.load(request)// Output: 11834d6d-7d09-459b-b757-068697a5a07b } decisionHandler(.cancel) return } ``` All of the code under this message will need to be implemented for both Plaid and Finicity. Complete implementation for the **webView(\_:decidePolicyFor:decisionHandler:)**: ```swift theme={null} // handle navigation requests extension ConnectViewController: WKNavigationDelegate { // Capture request URLs public // Intercept all requests before they are loaded func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { let urlString = navigationAction.request.url?.absoluteString currentURLOpened = navigationAction.request.url let currentURLOpenedString = currentURLOpened?.absoluteString if(currentURLOpenedString!.contains("{{universal-link}}/?status")){ self.popupWebViewControllerExternal?.dismiss(animated: true) popupWebView = nil } let isPostMessageFromMX = (urlString?.hasPrefix(appScheme) == true || urlString?.hasPrefix(atriumScheme) == true || urlString?.hasPrefix(mxScheme) == true) if (isPostMessageFromMX){ let urlc = URLComponents(string: urlString ?? "") let path = urlc?.path ?? "" // there is only one query param ("metadata") with each url, so just grab the first let metaDataQueryItem = urlc?.queryItems?.first if path == "/oauthRequested" { handleOauthRedirect(payload: metaDataQueryItem) } decisionHandler(.cancel) return } else{ if let url = navigationAction.request.url { // If needed, modify the request or block it if url.absoluteString.contains("{{universal-link}}/?oauth_state_id=") { let urlString = url.absoluteString self.dismiss(animated: true, completion: nil) if let range = urlString.range(of: "=") { let substring = urlString[range.upperBound...] Let request = URLRequest(url: URL(string: connectUrlToSave!.absoluteString + "&plaidStateId=\(substring)")!) webView.load(request)// Output: 11834d6d-7d09-459b-b757-068697a5a07b } decisionHandler(.cancel) return } // Check if the URL scheme is something other than http or https if url.absoluteString.contains("closemyapp") { // Close the WebView or dismiss the view controller self.dismiss(animated: true, completion: nil) } } } decisionHandler(.allow) } ``` # Listening to Front End Events 1. This section applies to all service providers. Handle the UI requests so that app to web request can be handled internally. ```swift theme={null} / handle UI requests extension ConnectViewController: WKUIDelegate { public func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures _: WKWindowFeatures) -> WKWebView? { // create a new webView guard webView == view else { debugPrint("MELD: popup cannot launch another webview") return nil } let popup = WKWebView(frame: webView.bounds, configuration: configuration) popup.uiDelegate = self popup.navigationDelegate = self popupWebView = popup DispatchQueue.main.async { let popupWebViewController = PopupWebViewController(with: popup) popupWebViewController.delegate = self self.present(popupWebViewController, animated: true) } return popupWebView } public func webViewDidClose(_ webView: WKWebView) { // handle closing. make sure to pop if it's the root webView // root view if view == webView { delegate?.cancel() return } // close child view } ``` 2. Meld emits events to inform the status of the handshake.intercept. ```swift theme={null} private enum ConnectHandlerResponse: String { case debug = "[meld-connect]debug" // debug information case initialize = "[meld-connect]init" // when the widget initializes case error = "[meld-connect]error" // when the widget encounters an error case complete = "[meld-connect]connect_complete" // once the call to /connect/complete has finished case handover = "[meld-connect]handover" // when the connect has been completed case cancel = "[meld-connect]cancel" // when your customer cancels the widget } public func userContentController(_: WKUserContentController, didReceive message: WKScriptMessage) { guard message.name == meldMessageHandler else { return } guard let body = message.body as? String, body.hasPrefix("[meld-connect]") else { return // ignore } let parts = body.split(separator: "?", maxSplits: 1) guard parts.count <= 2 else { return } guard let response = ConnectHandlerResponse(rawValue: String(parts[0])) else { return // ignore } let payload: Any? if parts.count >= 2, let payloadData = parts[1].data(using: .utf8) { payload = try? JSONSerialization.jsonObject(with: payloadData) } else { payload = nil } switch response { case .debug: // Debug. Ignore debugPrint("MELD:debug") case .initialize: // Initialization. Ignore. debugPrint("MELD:init") case .error: // An Error debugPrint("MELD:error") case .complete: // Complete — linking is done debugPrint("MELD:complete") DispatchQueue.main.async { [weak self] in debugPrint("got payload \(payload ?? "")") self?.delegate?.handover() } case .handover: // DONE. Dismiss and proceed debugPrint("MELD:handover") DispatchQueue.main.async { [weak self] in debugPrint("got payload \(payload ?? "")") self?.delegate?.handover() } case .cancel: debugPrint("MELD:cancel") // Cancel. Dismiss DispatchQueue.main.async { [weak self] in self?.delegate?.cancel() } } } ```
# Bank Linking Glossary Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/bank-linking-glossary This page defines the terms used throughout Meld's Bank Linking documentation so that you and your team can speak the same language as the API. Use the diagram at the bottom to see how each entity relates to the others. ## Terms * **Account** — Your Meld account, which represents your company. * **Customer** — A customer you create with Meld. A single customer usually represents one person or business and can have multiple bank connections. Track customers using `customerId` (a Meld-generated unique ID), or pass your own ID in as `externalCustomerId`. * **Service Provider** — The entity responsible for completing a connection between a user and a bank account. Plaid, Finicity, MX, Yodlee, Salt Edge, Salt Edge Partners, and Akoya are all service providers. * **Connection** — A link between a service provider and a login to a bank. A connection can have several financial accounts associated with it (for example, a checking account and a savings account). A connection is tied to a particular institution. * **Institution** — A financial entity where money is stored. A bank is the most common type of institution; credit unions are another. * **Financial Account** — Includes checking accounts, savings accounts, 401(k)s, IRAs, credit card accounts, and so on. A single connection can have multiple financial accounts, and individual accounts may support different sets of products. For example, credit card accounts don't support identifiers (i.e. routing and account numbers). ## Relationship diagram ![Relationship diagram showing Account, Customer, Connection, Institution, and Financial Account relationships](https://files.readme.io/ba3de38-image.png) # Balances Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-balances The Balances product returns the cached or realtime balances for a customer's financial account. Use it to determine if an account has sufficient funds before using it as a funding source for a transaction. **API Reference** * Search financial accounts: `GET /bank-linking/accounts` * Get a specific financial account: `GET /bank-linking/accounts/{accountId}` * Refresh customer accounts: `POST /bank-linking/connections/{connectionId}/refresh` ## Overview There are two types of balance data: current balance and available balance. * **Current balance** — The total amount of money in the account. This does not mean it is all available to spend. Some funds may be from deposits or checks that have not cleared yet. * **Available balance** — The total amount of money in the account that is available for your customer to spend. For fraud detection and insufficient-funds (NSF) prevention use cases, use available balance to determine fund sufficiency — it represents the predicted balance net of any pending transactions. Current balance does not take pending transactions into account. In some cases, a financial institution may not return available balance information. If that happens, you can calculate it by starting with the current balance, then using the transactions endpoint to detect pending transactions and adjusting the balance accordingly. ## Balance data When calling the financial accounts endpoint, you can access the `currency`, `currentAmount`, and `availableAmount` for that account. See [Retrieving Connection Data](/docs/bank-linking/get-connection-data/retrieving-connection-data) for an example. ## Sample response ```json theme={null} { "id": "W9ja25UmrZF25xbG22wdd", "accountId": "W9kbkRme9VT2iz6qdsaww2", "customerId": "W9ja24edcTHBks8h2e2dww", "institutionId": "Ntj3AwgdridJc2rasdssdd", "institutionName": "Chase", "status": "ACTIVE", "type": "DEPOSITORY", "subtype": "CHECKING", "name": "Checking", "truncatedAccountNumber": "1234", "identifiers": { // data }, "balances": { "currency": "USD", "currentAmount": 3578.41, "availableAmount": 3578.41, "updatedAt": "2022-03-08T23:19:23Z" }, "owners": [ // data ], "serviceProviderDetails": [ // data ] } ``` ## Account balance updates Account balance data is constantly updated as new transactions are made and previous transactions are processed. Use Meld's webhooks to receive updates on the data — see [Webhooks Quickstart](/docs/bank-linking/onboarding/step-5-setting-up-webhooks/webhook-quickstart) for setup details. # Identifiers Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-identifiers The Identifiers product returns the account number, routing number, and related identifiers for a customer's financial account. Use it to ensure that subsequent transactions (such as ACH transfers) end up in the right place. **API Reference** * Search financial accounts: `GET /bank-linking/accounts` * Get a specific financial account: `GET /bank-linking/accounts/{accountId}` ## Overview Each checking or savings account is assigned a unique account number and routing number. * **Account number** — Identifies your customer's unique bank account. Typically a 10–12 digit string that tells the bank which account to withdraw funds from or deposit funds in. * **Routing number** — A nine-digit number that financial institutions use to identify other financial institutions. It indicates which bank holds your customer's financial account. ## Use cases There are many use cases that require a customer's account and routing number, such as: * Your app allows users to cash out a credit balance to a bank account. * Your app allows users to pay you directly from their checking or savings account. * Your app allows an end-to-end ACH transfer. Account authentication lets you request a customer's account number and bank identification number (such as routing number, for US accounts) in a seamless and validated way without having to look up and type their account numbers. Account authentication generally supports checking and savings accounts only. ## Account authentication data When calling the financial account endpoint, you can access the `accountNumber` and `routingNumber` for that account. See [Retrieving Connection Data](/docs/bank-linking/get-connection-data/retrieving-connection-data) for an example. **Tokenized Account and Routing Numbers** Service providers may provide "tokenized" account and routing numbers when working with institutions that support an OAuth flow. This may be due to the provider's or institution's policies. A tokenized account number is not the actual number but a representation of it. These tokenized numbers work identically to normal account numbers. The digits returned in the `mask` field continue to reflect the actual account number, not the tokenized account number. For this reason, when displaying account numbers in your UI, always use the `mask` rather than truncating the account number. If a user revokes permissions to your app, the tokenized numbers will continue to work for ACH deposits but not withdrawals. ## Sample response ```json theme={null} { "id": "W9ja25UmrZF25xbG22wdd", "accountId": "W9kbkRme9VT2iz6qdsaww2", "customerId": "W9ja24edcTHBks8h2e2dww", "institutionId": "Ntj3AwgdridJc2rasdssdd", "institutionName": "Chase", "status": "ACTIVE", "type": "DEPOSITORY", "subtype": "CHECKING", "name": "Checking", "truncatedAccountNumber": "1234", "identifiers": { "ach": { "accountNumber": "00100000034561234", "routingNumber": "023000797" }, "eft": null, "international": null, "bacs": null }, "balances": { // data }, "owners": [ // data ], "serviceProviderDetails": [ // data ] } ``` # Investments Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-investments The Investments product lets developers connect to a customer's investment (brokerage) account and retrieve the current holdings and trade history. Use it for wealth-management, portfolio tracking, and financial-advice use cases. **API Reference** * Search investment holdings: `GET /bank-linking/investment-holdings` * Search investment transactions: `GET /bank-linking/investment-transactions` ## Overview Investments refers to an investment account (also known as a brokerage account) where one has various stocks and other securities such as mutual funds, CDs, bonds, and ETFs. The investment product lets you connect an investment account and get back all relevant information about the account. There are two types of investment data: investment holdings and investment transactions. See [Retrieving Connection Data](/docs/bank-linking/get-connection-data/retrieving-connection-data) for an example on fetching the data. ## Investment Holdings Investment Holdings refers to the current holdings of an investment account — what securities are currently in the account, along with other relevant information such as the number of shares, the cost basis, and the current value of the holding. This is similar to the Balances product but is a breakdown, since it includes all the securities in an account. ### Sample response for a single holding ```json theme={null} { "id": "WQ57LwnwzUo5WMM5Fi7nn2", "accountId": "W9kbkRme9VT2iz6qRT3212", "customerId": "WQ43wxs1bjUhohY8ekjeje", "financialAccountId": "WQ44KLVNHt9ARfp7aRWCC4", "symbol": "RKLB", "description": "Rocket Lab USA", "quantity": 2.00000000000000000000, "currentValue": 11.12000000000000000000, "costBasis": 11.11000000000000000000, "currency": "USD", "isin": "US7731221062", "cusip": "773122106", "type": "stock", "serviceProviderDetails": [ // data ] } ``` ## Investment Transactions Investment Transactions are the trade history of an account. For example, if the account owner purchased 10 shares of Google in a single trade, that would be a transaction. Unlike holdings, which are a snapshot of the present, transactions are the history of the account and give insight into how the current holdings were assembled. ### Sample response for a single transaction ```json theme={null} { "id": "WQ57iFJpqR524P82QZrFiW", "accountId": "W9kbkRme9VT2iz6qRT3212", "customerId": "WQ43wxs1bjUhohY8ekjeje", "financialAccountId": "WQ44KLVNHt9ARfp7aRWCC4", "amount": 11.11000000000000000000, "currency": "USD", "description": "Rocket Lab USA Limit Buy", "transactionDate": "2023-06-16T00:00:00Z", "postedDate": "2023-06-16T00:00:00Z", "symbol": "RKLB", "quantity": 2.00000000000000000000, "costBasis": 5.56000000000000000000, "type": "buy", "status": "POSTED", "serviceProviderDetails": [ // data ] } ``` Investments support varies by service provider. See the provider-specific mapping pages under [Partner Details](/docs/bank-linking/partner-details/index) for what is supported. # Owners Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-owners The Owners product returns the account-identity data recorded with a customer's financial institution — full name, phone number, mailing address, and email address. Developers use it to verify customer identity for ACH, KYC, and risk workflows. **API Reference** * Search financial accounts: `GET /bank-linking/accounts` * Get a specific financial account: `GET /bank-linking/accounts/{accountId}` ## Overview Account identity information helps you verify your customer's identity by accessing the data recorded with their financial institution. ## Use cases Common use cases for verifying a customer's identity: * Verify an account before initiating an ACH transaction. * Verify depository-type accounts (such as checking and savings) and credit-type accounts (such as credit cards). * Verify the information provided by your customer with a trusted source when opening a new account. * Verify customers you have identified as higher risk based on data such as email address, location, financial institution, or activity patterns. ## Account identity data When calling the financial account endpoint, you can access the account's identity data for that account. See [Retrieving Connection Data](/docs/bank-linking/get-connection-data/retrieving-connection-data) for an example. Specifying `OWNERS` as a product in `/bank-linking/connect/start` does not guarantee account-owners data will be available for every account in the connection. Some accounts do not have owners data, and others may only have a subset of the owner fields. ## Sample response ```json theme={null} { "id": "W9ja25UmrZF25xbG22wdd", "accountId": "W9kbkRme9VT2iz6qdsaww2", "customerId": "W9ja24edcTHBks8h2e2dww", "institutionId": "Ntj3AwgdridJc2rasdssdd", "institutionName": "Chase", "status": "ACTIVE", "type": "DEPOSITORY", "subtype": "CHECKING", "name": "Checking", "truncatedAccountNumber": "1234", "identifiers": { // data }, "balances": { // data }, "owners": [ { "addresses": [ { "data": { "street": "123 Cherry Hill Lane", "city": "Palo Alto", "region": null, "postalCode": "94537-8038", "country": "USA", "full": "123 Cherry Hill Lane Palo Alto CA 94537-8038 USA" }, "primary": "UNKNOWN" } ], "emails": [ "gjones@hotmail.net" ], "names": [ "George Jones" ], "phoneNumbers": null } ], "serviceProviderDetails": [ // data ] } ``` # Processor Token Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-processor-token A processor token tokenizes sensitive information (such as account and routing numbers) between a bank linking provider and a payment service provider — for example, a Plaid–Checkout integration. Use a processor token when a downstream processor needs to act on a bank account without handling raw account details. **API Reference** * Create a processor token: `POST /bank-linking/accounts/{accountId}/processor-token` ## Overview Processor tokens are optional and are used to make calls to a payment service provider (for example, Checkout) on behalf of the bank linking provider (for example, Plaid) without needing to directly pass the sensitive data. For example, Plaid can authorize Checkout to pull money from a user's bank account by sending a processor token instead of the raw account details. * Processor tokens do not expire. * Processor tokens cannot be modified, but can be revoked at any time. * Meld currently supports processor tokens for **Plaid** and **MX**. Processor Token is treated differently than other products: it isn't requested in the initial call to `/bank-linking/connect/start`. It has its own dedicated endpoint. For Plaid-specific details, see [Plaid's processor documentation](https://plaid.com/docs/api/processors/). # Transactions Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-transactions The Transactions product returns a customer's account transaction history. Developers use it for cash-flow modeling, risk analysis, expenditure categorization, and similar use cases. **API Reference** * Search financial transactions: `GET /bank-linking/transactions` * Get a financial transaction: `GET /bank-linking/transactions/{transactionId}` ## Overview Transactional data helps you understand a customer's expenditure, timing, and frequency of purchases. Call the transactions endpoint to filter transactions. See [Retrieving Connection Data](/docs/bank-linking/get-connection-data/retrieving-connection-data) for an example. Transactions are typically only available for the following account types: * Depository accounts such as checking and savings. * Credit-type accounts, such as credit cards or student loan accounts. ## Fetching transactions See [Fetching Transaction Information](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/fetching-transaction-information) for how to fetch Meld transactions. ## Updating transactions Transactions can get updated, especially their status. See [When Is Your Data Available?](/docs/bank-linking/get-connection-data/when-is-your-data-available) for more. ## Data normalization Meld normalizes transaction data, including the type, signage, and status. See [Data Normalization](/docs/bank-linking/get-connection-data/data-normalization) for more information. ## Transaction days fetched by refresh type Meld loads a different period of transaction history depending on several factors — whether it is the first load (`INITIAL`), a subsequent refresh (`SUBSEQUENT`), or a historical aggregation (`HISTORICAL`). ### Initial loads For `INITIAL` loads (immediately following completing or reconnecting a connection), only a limited transaction history is available. Meld loads whatever the service provider has initially available: * Plaid: last 30 days * Finicity: last 180 days * MX: last 90 days * Other service providers: 30 days ### Historical loads After completing an `INITIAL` aggregation, Meld triggers a `HISTORICAL` aggregation with the service provider. Historical aggregations collect **2 years** of transaction history across all service providers. Historical transaction data typically takes longer to aggregate (and is billed separately by the service provider), which is why it is executed after the initial aggregation. Because of this, a secondary `BANK_LINKING_HISTORICAL_TRANSACTIONS_AGGREGATED` webhook is issued after the first `BANK_LINKING_TRANSACTIONS_AGGREGATED` webhook to indicate that historical transactions are now also available (as well as how many were loaded). Since each service provider varies on what is immediately available during the `INITIAL` load, the `HISTORICAL` load collects whatever remains up to 2 years (i.e., from 30 days to 2 years for Plaid, from 180 days to 2 years for Finicity, etc.). The numbers of transactions loaded are broken down by period and reflected in the aforementioned webhooks. ### Subsequent loads Following both the `INITIAL` and `HISTORICAL` loads, only the recent transaction history is aggregated daily to capture any new transactions or updates to existing transactions. These loads are considered `SUBSEQUENT` and only retrieve the last 20 days of transaction data. This is sufficient to capture recent transactions as well as updates to existing transactions (such as transactions transitioning from `PENDING` to `POSTED`, or cancellations of transactions). ## Recurring transactions Certain providers identify recurring transactions (such as a monthly subscription). For Plaid and MX specifically, recurring transactions in the transaction details object have `recurring=TRUE`, while all other transactions have `recurring=FALSE`. ## Sample response ```json theme={null} { "id": "W9ja24xnWMVFc1s7D3rewfw", "accountId": "W9kbkRme9VT2iz623efee", "customerId": "W9ja24edcTHBks8hz2cvvf", "financialAccountId": "W9ja25UmrZF25xb2E3WSQ", "amount": 2018.27, "currency": "USD", "description": "Payment to Chase card ending in 1234", "status": "POSTED", "category": "Fee", "transactionDate": "2022-02-28T11:00:00Z", "postedDate": "2022-02-28T11:00:00Z", "serviceProviderDetails": [ // data ] } ``` # Fetching Transaction Information Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/bank-linking-products/fetching-transaction-information This page explains the recommended strategies for fetching and updating transactions in your application after Meld signals new data is available. Pick the strategy that best matches how aggressive your refresh cadence is and how much extra data you are willing to fetch. ## Before you begin * You have an active bank linking connection. * You are subscribed to the `BANK_LINKING_TRANSACTIONS_AGGREGATED` webhook (see [Webhooks Quickstart](/docs/bank-linking/onboarding/step-5-setting-up-webhooks/webhook-quickstart)) or you are polling the connection's `successfullyAggregatedAt` timestamp. ## When to fetch Fetching and updating transactions follows a general process: 1. Wait for an indication that updates have occurred — either via the `BANK_LINKING_TRANSACTIONS_AGGREGATED` webhook or by noticing an updated `successfullyAggregatedAt` time for the connection. 2. Capture those updates using one of the strategies below. Transactions are ordered by date descending (newest first, oldest last) for each financial account, and each transaction has an associated key field that represents its position in this sequence. When capturing additions/updates, retrieving the full transaction history each time isn't necessary (except right after the initial connection). Transactions typically aren't added or modified beyond 2 weeks of the time of fetching them. ## Recommended strategies Use one of the following to ensure all transaction additions/updates are captured during each refresh: 1. **Date-range fetch.** Fetch transactions using the `startDate` and `endDate` params. Following a successful refresh, set them so the last 2 weeks of transaction history is fetched. This reasonably ensures all transaction updates are captured. The length of this period can be modified to be longer or shorter depending on how conservative you want to be. Paginate through these results using the `after` param set to the key of the last transaction on each page until reaching the end of the results for that time period. 2. **Oldest-updated key (forward).** Use the `oldestTransactionUpdatedSearchKey` present for each financial account on the `BANK_LINKING_TRANSACTIONS_AGGREGATED` webhook. This key can be used as a baseline so all updates are captured without also capturing unnecessary older transactions that weren't updated (which could occur with option 1). Call the search transactions endpoint with no key initially, then paginate through the results using the `after` key until you reach the transaction with key matching the `oldestTransactionUpdatedSearchKey` (newest transaction → oldest transaction updated). 3. **Oldest-updated key (reverse).** The reverse of strategy #2: call the search transactions endpoint with the `before` key set to the `oldestTransactionUpdatedSearchKey`, and paginate through the results in the opposite direction using the `before` key until there are none remaining (oldest transaction updated → newest transaction). The `after` and `before` fields refer to pagination order, not occurrence order. This is standard across all bank-linking search endpoints. In particular for transactions, this can be misleading: they do **not** mean "occurred after" or "occurred before". Since transactions are ordered with the newest first and oldest last, each call with an `after` key returns the transactions on the next page (which are actually older), and each call with a `before` key returns the results on the previous page (which are actually newer). When updating transactions, Meld recommends searching by financial account ID rather than connection ID. The ordering of transactions is done by transaction date per financial account, so searching by connection ID would yield a sequence of: *account1 transactions ordered by date* → *account2 transactions ordered by date* → etc. This can be misleading when parsing the transactions, as some recent transactions may appear missing when in reality they are later in the sequence because they belong to a different financial account for the connection. # Bank Linking Products Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/bank-linking-products/index ## Overview Once your customer has connected their account to your app, you can use Meld's endpoints to retrieve customer financial account information. This page lists the products available, shows how Meld's product names map to each service provider's terminology, and links to the deeper documentation for each. The following Meld products are normalized to return data across any bank linking service provider configured to your account: * [Balances](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-balances): The cached or realtime balances of the customer's account. * [Identifiers](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-identifiers): The institution's account and routing numbers (or related account-specific identifiers). * [Owners](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-owners): Account-owner information such as name and address. * [Transactions](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-transactions): Account transaction history. * [Investment Holdings](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-investments): Current investment holdings, including stocks and other securities. * [Investment Transactions](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-investments): Trade history. The following are not standalone products but features Meld supports: * [Processor Token](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-processor-token): Where supported by the service provider, this token can be used by an authorized third party to make API calls directly to the processor on your behalf. * [Recurring Transactions](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-transactions): Certain providers identify recurring transactions (such as a monthly subscription). For Plaid and MX specifically, recurring transactions in the transaction details object have `recurring=TRUE`, while all other transactions have `recurring=FALSE`. Processor Token is treated differently than other products: it isn't requested in the initial call to `/bank-linking/connect/start`. It has its own dedicated endpoint described on the [Processor Token](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-processor-token) page. Mapping of Meld's products to service provider's products:
Meld Product
Plaid Product
Finicity Product
MX Product
Yodlee Product
SaltEdge Product
SaltEdge Partners Product
Akoya Product
**Balances**
Balance
availBalance
Balance
Account\_details
Account\_details
Account\_details
Balances
**Identifiers**
Auth
ACH
Verification
Account\_details
Account\_details
Account\_details
Payments
**Owners**
Identity
accountOwner
Identity
Account\_details
Holder Info
Holder Info
Customers
**Transactions**
Transactions
transAgg
Transactions
Transactions
Transactions
Transactions
Transactions
**Investment Holdings**
Investment Holdings
availBalance
Investment Holdings
Holdings
Investments
**Investment Transactions**
Investment Transactions
transAgg
Transactions
Transactions
Transactions
# Account Types and Subtypes Each service provider has its own way of returning data and hierarchy/nomenclature when it comes to account types and subtypes. Meld normalizes the data across providers so accounts have a uniform way of identifying account types. You can reference the service provider mappings to the Meld account types/subtypes in the *Normalized Account Types and Subtypes* section at the bottom of each service provider's mappings page (see [Partner Details](/docs/bank-linking/partner-details/index)). If service providers add new account types/subtypes, they will be mapped to the Meld type of `UNIDENTIFIED` until a proper mapping has been determined. A list of all the Meld account types and subtypes can be found below: # Bank Linking Use Cases Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/bank-linking-use-cases This page summarizes how to build an app that uses Meld's Bank Linking stack end to end — from creating a connection through ongoing management — and lists common use cases that Bank Linking data unlocks. Use it as a roadmap that points to the deep-dive pages for each step. ## How to use the product 1. [Create a connection](/docs/bank-linking/creating-connections/index) to your customer's bank account(s). 1. Test credentials and sandbox flows are described in the [Bank Linking Sandbox Testing Guide](/docs/bank-linking/testing-and-debugging/bank-linking-sandbox-testing-guide). 2. Inform the user that the connection is complete while you fetch the data in the background. See [Informing the user of the status](/docs/bank-linking/get-connection-data/index). 2. [Get connection data](/docs/bank-linking/get-connection-data/index) to pull the financial data you need. 1. This includes [balances](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-balances), [identifiers](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-identifiers), [owners](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-owners), [transactions](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-transactions), and [investments](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-investments). 2. Configure and consume [webhooks](/docs/bank-linking/webhook-events-bank-linking) so you know when new financial data is available or a connection is broken. 3. You can also create a [processor token](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-processor-token) for a financial account. 3. [Manage connection status](/docs/bank-linking/manage-connection-status/index) to keep connections organized and up to date. 1. [Refresh](/docs/bank-linking/manage-connection-status/refreshing-connections) a connection to get the latest data. 2. [Repair](/docs/bank-linking/manage-connection-status/repairing-connections) a connection to start receiving updates again. 3. [Delete](/docs/bank-linking/manage-connection-status/deleting-connections) a connection as needed. 4. [Route](/docs/bank-linking/creating-connections/select-an-institution/routing-overview) between providers to optimize conversion. 1. Each service provider has different coverage and details. Be sure to read through them before using a particular provider. See [Plaid](/docs/bank-linking/partner-details/plaid/index), [Finicity](/docs/bank-linking/partner-details/finicity/index), [MX](/docs/bank-linking/partner-details/mx/index), [Salt Edge](/docs/bank-linking/partner-details/salt-edge/index), and [Salt Edge Partners](/docs/bank-linking/partner-details/salt-edge-partners/index). 2. Meld's Bank Linking stack lets you route customers to specific providers based on predefined defaults, a provider waterfall using institution coverage and availability, or institution-specific decisioning. Routing rules are defined in the Meld dashboard. 5. If you already have existing connections with a service provider, you can bring them over by [importing connections](/docs/bank-linking/manage-connection-status/importing-connections). ## Potential use cases Common reasons to integrate Bank Linking: 1. View users' balances and investment holdings to give them financial advice. 2. View users' balances and transactions to assess loan qualification. 3. View users' owner information to verify their identity. 4. View users' identifier information (account number and routing number) to perform an ACH transaction. See [Account Verification](/docs/bank-linking/bank-linking-quickstart/account-verification) for more. ## Building your own institution picker The first step of bank linking is knowing which institution the user wants to connect to. Most customers launch Meld's widget, which has an institution picker as the first screen. However, you can build your own picker, have the user select an institution, and pass that `institutionId` into the Meld `/bank-linking/connect/start` endpoint — this skips the institution picker screen in the Meld UI. To populate institutions along with their symbols in your own UI, fetch this data from the institutions endpoint. Institution data rarely changes, so cache the response rather than calling the institutions endpoint live every time. # Bank Linking Quickstart Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/index Welcome to Meld's Bank Linking product. Meld's Bank Linking stack lets developers connect their users' bank accounts through multiple service providers (Plaid, MX, Finicity, Salt Edge, Yodlee, Akoya) using a single integrated API and UX flow. This quickstart covers how connections are created and managed, the data products available, and the terminology used throughout the rest of the bank linking documentation. Looking for Meld's Crypto / Digital Assets documentation? Skip this section and head to the [Crypto Overview](/docs/stablecoins/crypto-overview_/index) instead. ## Before you begin * Complete [Onboarding](/docs/bank-linking/onboarding/index) so you have a Meld dashboard account. * Grab your Meld API key from the **Developer** tab of the Dashboard (see [Step 3: Meld API Key](/docs/bank-linking/onboarding/step-3-meld-api-key)). * Confirm which environment your key targets (sandbox or production). Reach out to Meld if you would like the OpenAPI spec for bank linking. ## How to use the product The high-level flow is summarized in [Bank Linking Use Cases](/docs/bank-linking/bank-linking-quickstart/bank-linking-use-cases). For terminology and a relationship diagram, see the [Bank Linking Glossary](/docs/bank-linking/bank-linking-quickstart/bank-linking-glossary). For details on the financial-data products you can retrieve after a connection completes, see [Bank Linking Products](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/index). **Environments** Meld offers separate sandbox and production environments. Each environment has its own API key and base URL: | Environment | API Base URL | | :---------- | :----------------------- | | Sandbox | `https://api-sb.meld.io` | | Production | `https://api.meld.io` | ## Accounts with service providers Meld has reseller agreements with Finicity, MX, and Akoya and can create an account for you with each of these providers. For the other providers, if you don't have your own account with the provider already, Meld can temporarily share their account with you for testing while your account is being spun up. Contact Meld for more details. # Step 4: Complete the Connection Source: https://docs.meld.io/docs/bank-linking/creating-connections/complete-the-connection/index The final step of the connection flow is for the customer to authenticate with their bank. After they pick an institution, the widget hands off to that institution's login screen (or to the service provider's OAuth flow). This page covers what your customer sees, how to handle failure cases, and what events to expect when the connection succeeds. Once they have chosen their institution, they will be taken to that institution's login page. Here's an example: ![](https://files.readme.io/6010dd5-image.png) For security reasons, many institutions will open their login screens in a new popup to enforce an OAuth login. In that case, the login screen will be made by the institution, rather than the provider. Here's an example: ![](https://files.readme.io/9f177ec-image.png) Once the user enters their username and password, if they have multi factor authentication set up with their bank, they may have to enter a pin or passcode. After this, provider will complete the connection to the institution, and once the user sees a success screen they can close the widget. Once you have successfully completed a connection, you can now view the user's financial data. See [Get Connection Data](/docs/bank-linking/get-connection-data) for more information. ## Next steps To see what to do next, including which webhooks to expect once a connection completes, see [Get Connection Data](/docs/bank-linking/get-connection-data). ## Connection issues There are several reasons why a connection might fail. The most prevalent reason is that the user entered the incorrect credentials, in which case they will be asked to reenter their credentials. A user may also connect to an institution where they don't have any eligible accounts. For example, Meld does not support Mortgages for Plaid right now, so if the user uses Plaid to connect to an institution where they only have a mortgage account, they may not be able to connect it, and would see a message like this: ![](https://files.readme.io/7faa787-image.png) Sometimes, a service provider may temporarily have an issue connecting to a particular institution. While Meld tries to mitigate this by noticing breakages and steering users towards providers whose connection to that institution is working, on occasion users may still see a screen like below: ![](https://files.readme.io/da7e798-image.png) In this case, the user should go back and select a different institution, or route to the same institution through a different provider. For more information on the latter option, see [Bank Linking Routing](/docs/bank-linking/creating-connections/select-an-institution/routing-overview). For a deep dive into widget errors, including how to query them from the Activity Log, see [Widget Errors During a Connection](/docs/bank-linking/creating-connections/complete-the-connection/widget-errors-during-a-connection). # Widget Errors During a Connection Source: https://docs.meld.io/docs/bank-linking/creating-connections/complete-the-connection/widget-errors-during-a-connection A customer may encounter errors inside the Meld Connect Widget while trying to make a connection. Examples include requesting a product you do not have enabled with the service provider, hitting test connection limits, or transient service provider failures. This page is for developers debugging failed connections — it explains how to query widget errors and what each error subtype means. ## Before you begin * You have a Meld API key for the environment in question (sandbox or production) * You know either the `connectionId` of the failed attempt, or a date range you want to inspect * You are comfortable querying the Meld Activity Log ## How to query widget errors Widget errors are recorded in the Activity Log. Query them by filtering on the action `INSTITUTION_CONNECTION_WIDGET_ERROR`: ```bash theme={null} curl --location 'https://api-sb.meld.io/activity-log?start=2024-05-08T00:00:00.000Z&end=2024-05-10T00:00:00.000Z&connectionId=W9343jnjn3jn3&actions=INSTITUTION_CONNECTION_WIDGET_ERROR' \ --header 'Meld-Version: 2022-11-10' \ --header 'Content-Type: application/json' \ --header 'Authorization: BASIC ***Redacted API Key***' ``` For the full Activity Log schema and supported query parameters, refer to the [API reference](/api-reference). ## Widget error subtypes There are three event types (subtypes) of widget errors. Filter by one or more using the `eventTypes` query parameter on the Activity Log endpoint. | Event type | What it means | Recommended developer action | | :---------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `SERVICE_PROVIDER_INITIALIZATION_FAILURE` | The widget could not initialize with the underlying service provider. Common causes: the requested product is not enabled on your provider account, missing provider credentials, or invalid configuration in the dashboard. | Check that the products you requested in `/connect/start` are enabled with the provider for your account. Verify provider credentials in the Meld dashboard. If the issue persists, contact Meld support with the connection id. | | `SERVICE_PROVIDER_FAILURE` | The service provider returned an error during the customer's session. Common causes: institution outage, provider rate limit, expired provider session, or customer-side issues such as repeated invalid credentials. | Inspect the `serviceProviderDetails` in the activity log entry. If the institution is unhealthy, advise the customer to retry later or use a different provider via routing. If you see repeated provider rate-limit errors, throttle test traffic. | | `INTERNAL_FAILURE` | An unexpected error inside Meld occurred during the widget flow. | Retry the connection. If the error persists, contact Meld support with the `connectionId` and the timestamp. | ## Common scenarios and fixes | Symptom | Likely cause | Developer action | | :---------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Widget fails immediately on launch | The connect token expired (older than 3 hours) or was malformed | Generate a fresh token via `/connect/start` and relaunch | | Picker is empty or has very few institutions | Too many required products or an overly restrictive region filter | Reduce the `products` array, move non-essential products to `optionalProducts`, or remove `regions` | | "Error creating user" when selecting an institution | You re-enabled a service provider with the same `customerId` / `externalCustomerId` used before disabling | Use new `customerId` / `externalCustomerId` values when reconnecting via a re-enabled provider | | Customer sees "Institution unavailable" or a routing retry screen | Provider's integration with that institution is degraded | Encourage the customer to use Routing Retry to try another provider; consider enabling Smart Routing in the dashboard | | Connection completes but no webhooks fire | Webhook profile not configured or filtered too narrowly | Verify your webhook profile per [Webhook Quickstart](/docs/bank-linking/onboarding/step-5-setting-up-webhooks/webhook-quickstart) and confirm event types include bank linking events | **Sandbox vs. production** Widget errors are logged identically in sandbox (`api-sb.meld.io`) and production (`api.meld.io`). Sandbox traffic may produce additional `SERVICE_PROVIDER_FAILURE` entries that mirror the test behavior of each provider's sandbox; these are expected and do not indicate a real outage. # Step 1: Create a Connect Token Source: https://docs.meld.io/docs/bank-linking/creating-connections/create-a-connect-token/index The first step in linking a customer's bank account is creating a **connect token**. A connect token is a short-lived JWT that authorizes the Meld Connect Widget to start a connection session for a specific customer with a specific set of products and regions. This page walks developers through generating a token, configuring it correctly, and using the response to launch the widget in Step 2. ## Before you begin * You have a Meld sandbox or production API key * You have decided which [products](/docs/bank-linking/creating-connections/create-a-connect-token/supported-bank-linking-products) you need (you can also use [optional products](/docs/bank-linking/creating-connections/create-a-connect-token/optional-products)) * You know the [regions](/docs/bank-linking/creating-connections/create-a-connect-token/supported-bank-linking-products) your customers bank in, or are comfortable leaving regions unset * Your server is calling the endpoint — **never call this endpoint from the browser or mobile client**, the API key is a secret ## Create the token Send a `POST` request to `{api_base_url}/bank-linking/connect/start` using your API key. The response contains the `connectToken` you will use in [Step 2: Launch the widget](/docs/bank-linking/creating-connections/launch-the-widget). For the full request and response schema, refer to the [Bank Linking — Create Connect Token API reference](/api-reference/bank-linking). The connect token is used only while the user is making the connection to their bank and **expires after 3 hours**. You do not have to persist it — once the connection completes it cannot be reused. To track a connection over time, use the `customerId` returned in the response, or the `externalCustomerId` you optionally pass in. Be deliberate about the regions and products you request. In Step 3, when the user selects their institution, only institutions that support **all** requested products and are in one of the requested regions appear in the widget. Choosing too many products or too few regions can dramatically shrink the institution list. If you don't know the region in which your customer banks, omit the `regions` parameter entirely. **Disabling / re-enabling service providers** If you disable a service provider in the Meld dashboard and later re-enable it (with the same or different credentials), use new `customerId` / `externalCustomerId` values for subsequent connections. Reusing prior identifiers can result in an `Error creating user` when the customer selects an institution. ## Skipping or prepopulating the Meld picker If you already know which institution the user wants to link (for example, you collect that in your own UI), you have two options to make the flow more seamless: 1. **Prepopulate the search box.** Pass the institution name as a string when creating the connect token. The Meld picker autofills the search input so matching institutions appear by default. The user can still edit the search string. 2. **Skip the picker entirely.** Pass the `institutionId` corresponding to the user's bank. This skips the Meld picker screen and sends the user straight to the institution login. To resolve an institution name to a Meld `institutionId`, use the [/institutions endpoint](/api-reference/bank-linking). Note that for MX, not all institutions that support transactions also support historical transactions, so `HISTORICAL_TRANSACTIONS` appears as a separate product in the institution search response. However, when listing products in `/connect/start`, pass `TRANSACTIONS` only — `HISTORICAL_TRANSACTIONS` is not a valid value for the products array on `/connect/start`. A key part of building the request body is the list of products. For more information on the available products and which to request, see [Supported Bank Linking Products](/docs/bank-linking/creating-connections/create-a-connect-token/supported-bank-linking-products). ## Sample request ```bash theme={null} curl --location 'https://api-sb.meld.io/bank-linking/connect/start' \ --header 'Meld-Version: 2022-11-10' \ --header 'Content-Type: application/json' \ --header 'Authorization: BASIC ***Redacted API Key***' \ --data '{ "externalCustomerId": "testCustomer", "products": [ "BALANCES", "OWNERS", "IDENTIFIERS", "TRANSACTIONS", "INVESTMENT_HOLDINGS", "INVESTMENT_TRANSACTIONS" ], "regions": ["US", "CA"], "institutionSearchString": "Fidelity" }' ``` ## Sample response ```json theme={null} { "id": "WQ46XEVzT14vrrDq6LSJVg", "connectToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyZWRpcmVjdCI6dHJ1ZSwicmVnaW9ucyI6WyJVUyIsIkNBIl0sImlzcyI6Im1lbGQuaW8iLCJjb25uZWN0aW9uIjoiV1E0NlhFVnpUMTR2cnJEcTZMU0pFZCIsImV4cCI6MTY4Njc5ODkzMywiaWF0IjoxNjg2Nzg0NTMzLCJhY2NvdW50IjoiVzlrYmtSbWU5VlQyaXo2cUFTZkFmMiIsImN1c3RvbWVyIjoiV1E0NlhEcllZTVlyZTlSQmt3YXl5USIsInByb2R1Y3RzIjpbIlRSQU5TQUNUSU9OUyIsIkJBTEFOQ0VTIiwiSURFTlRJRklFUlMiLCJPV05FUlMiXSwicm91dGluZ1Byb2ZpbGUiOiJXR3ZGWHRYYkRvQ2VXc3hVdHVpeFB5In0.jAwpUokwkSKi7BTwKJOY_Y6XAPv_O8yOi6mVaUKxnMI", "customerId": "WQ46XDrYYMYre9RBkwayyZ", "externalCustomerId": "testCustomer", "widgetUrl": "https://institution-connect-sb.meld.io?connectToken=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyZWRpcmVjdCI6dHJ1ZSwicmVnaW9ucyI6WyJVUyIsIkNBIl0sImlzcyI6Im1lbGQuaW8iLCJjb25uZWN0aW9uIjoiV1E0NlhFVnpUMTR2cnJEcTZMU0pFZCIsImV4cCI6MTY4Njc5ODkzMywiaWF0IjoxNjg2Nzg0NTMzLCJhY2NvdW50IjoiVzlrYmtSbWU5VlQyaXo2cUFTZkFmMiIsImN1c3RvbWVyIjoiV1E0NlhEcllZTVlyZTlSQmt3YXl5USIsInByb2R1Y3RzIjpbIlRSQU5TQUNUSU9OUyIsIkJBTEFOQ0VTIiwiSURFTlRJRklFUlMiLCJPV05FUlMiXSwicm91dGluZ1Byb2ZpbGUiOiJXR3ZGWHRYYkRvQ2VXc3hVdHVpeFB5In0.jAwpUokwkSKi7BTwKJOY_Y6XAPv_O8yOi6mVaUKxnMI" } ``` ### Key response fields | Field | Description | | :------------------- | :-------------------------------------------------------------------------------------------------- | | `id` | The Meld connection id. Use this to look up the connection later. | | `connectToken` | The JWT used to launch the widget. Expires after 3 hours. | | `customerId` | Meld's id for the customer. Persist this to track the customer across connections. | | `externalCustomerId` | The id you passed in (if any), echoed back. | | `widgetUrl` | The full URL with the token attached. You can load this directly in an iframe to launch the widget. | ## Common errors | HTTP status | Likely cause | Developer action | | :----------------- | :-------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------ | | `401 Unauthorized` | Missing or invalid `Authorization` header | Verify the API key value and the `BASIC ` prefix; confirm you are hitting the correct environment | | `400 Bad Request` | Invalid product name, both required and optional product overlap, or unsupported region | Check `products` / `optionalProducts` / `regions` values against the API reference | | `400 Bad Request` | No required product specified when using `optionalProducts` | Move at least one product into the `products` array | | `404 Not Found` | The `institutionId` you passed does not exist | Re-resolve the institution via the `/institutions` endpoint | | `5xx` | Transient Meld or provider issue | Retry with backoff; if it persists, contact Meld support | Now that you have your connect token, you are ready to move on to [launching the widget](/docs/bank-linking/creating-connections/launch-the-widget). # Optional Products Source: https://docs.meld.io/docs/bank-linking/creating-connections/create-a-connect-token/optional-products By default, Meld filters the institutions shown in the picker to those that support **every** product in the `/connect/start` request. Sometimes you want users to see institutions that may not support all products, and then fetch data only for the products that are supported. **Optional products** solve this use case: they are aggregated on a best-effort basis and do not restrict which institutions appear. ## When to use optional products * You want maximum institution coverage but still want bonus data (such as investments) where it exists. * A product like `INVESTMENT_HOLDINGS` is nice to have for some customers but should not gate the rest of the flow. * You are retroactively adding products via the [update connection endpoint](/docs/bank-linking/manage-connection-status/updating-connections) — those products always land in `optionalProducts`. ## Rules * You must specify **at least one** product in the required `products` array. * A product cannot appear in both `products` and `optionalProducts`. * Optional products are aggregated on a best-effort basis — Meld does not guarantee data will be returned for them. Build your UI accordingly. ## Sample request ```bash theme={null} curl --location 'https://api-sb.meld.io/bank-linking/connect/start' \ --header 'Meld-Version: 2022-11-10' \ --header 'Content-Type: application/json' \ --header 'Authorization: BASIC ***Redacted API Key***' \ --data '{ "externalCustomerId": "testCustomer", "products": [ "BALANCES", "TRANSACTIONS" ], "optionalProducts": [ "INVESTMENT_HOLDINGS", "INVESTMENT_TRANSACTIONS" ] }' ``` In the example above, the picker filters institutions to those that support `BALANCES` and `TRANSACTIONS`. Institutions are **not** filtered out for missing investment support. If the connected accounts do carry investment data, Meld retrieves it and emits the corresponding webhooks. Optional products are powerful for maximizing coverage, but do **not** guarantee data will be returned for those products. Always handle the case where investment or other optional data is empty. # Supported Bank Linking Products Source: https://docs.meld.io/docs/bank-linking/creating-connections/create-a-connect-token/supported-bank-linking-products A **product** in Meld's Bank Linking stack is a category of financial data you can request for a connection — for example `BALANCES`, `TRANSACTIONS`, or `INVESTMENT_HOLDINGS`. This page is for developers deciding which products to request in [`/connect/start`](/docs/bank-linking/creating-connections/create-a-connect-token) and which service provider to route to. Choosing the right product set is critical because it determines which institutions appear in the picker and what data Meld returns. ## Explanation of the products * **Identifiers** — account and routing numbers used for ACH and payment instruments. * **Owners** — name and contact details for the account holder. * **Balances** — current and available balance on each account. * **Transactions** — posted and pending transactions; up to 2 years of history is loaded once available. * **Investment Holdings** — securities held in an investment account. * **Investment Transactions** — buys, sells, transfers, and other movement within investment accounts. * **Processor Token** — a token usable with downstream processors (e.g. Stripe, Dwolla); availability depends on the provider. The complete catalog of products in this table is the canonical list. The [Optional Products](/docs/bank-linking/creating-connections/create-a-connect-token/optional-products) feature uses the same product names — keep that page and this one in sync when adding new products. ## Supported products Each connection is established via a service provider. The service provider handles the customer's credentials, multi-factor authentication, and deals directly with the customer's bank. The list of currently supported service providers and their product availability is below. Specific details for each provider live on the Provider Details pages. | | | | | | | | | | :-------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------- | | |
[Identifiers](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-identifiers)
|
[Owners](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-owners)
|
[Balances](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-balances)
|
[Transactions](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-transactions)
|
[Investment Holdings](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-investments)
|
[Investment Transactions](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-investments)
|
[Processor Token](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-processor-token)
| | [Finicity](/docs/bank-linking/partner-details/finicity) |
X
|
X
|
X
|
X
|
X
|
X
| | | [Plaid](/docs/bank-linking/partner-details/plaid) |
X
|
X
|
X
|
X
|
X
|
X
|
X
| | Akoya |
X
|
X
|
X
|
X
|
X
|
X
| | | Yodlee |
X
|
X
|
X
|
X
|
X
|
X
| | | [MX](/docs/bank-linking/partner-details/mx) |
X
|
X
|
X
|
X
|
X
|
X
|
X
| | [Salt Edge Open Banking](/docs/bank-linking/partner-details/salt-edge) |
X
|
X
|
X
|
X
| | | | | [Salt Edge Partners](/docs/bank-linking/partner-details/salt-edge-partners) |
X
|
X
|
X
|
X
| | | | | Mesh | | |
X
| |
X
|
X
| | ## Product initialization Products are initialized in the [`/connect/start`](/docs/bank-linking/creating-connections/create-a-connect-token) request when creating a connection. Which products to initialize depends on your application's requirements. The products specified restrict the institutions shown in the selection pane to those that support **all** of them. If maximizing institution coverage is a high priority, only request products in `/connect/start` that are critical to your use case. To fetch more data where available without filtering institutions, use [Optional Products](/docs/bank-linking/creating-connections/create-a-connect-token/optional-products). This filtering is done at the **institution** level only — not at the account level. When the customer selects which accounts to share, all of their accounts are displayed even if individual accounts do not support all requested products. There is no guarantee that each selected account will support a given product; the filter only guarantees that the institution supports the product for at least some account types. Some products are priced differently and vary in availability by service provider. Confirm pricing with Meld before adding products in production. # Creating Connections Source: https://docs.meld.io/docs/bank-linking/creating-connections/index Creating institution connections is the core of Meld's Bank Linking service: it is how your application gains permissioned access to a customer's financial data. This guide is for developers integrating bank account linking into their product, and walks you through every step required to establish a working connection. Each connection has its own set of [products](/docs/bank-linking/creating-connections/create-a-connect-token/supported-bank-linking-products) associated with it, and is established via an underlying [service provider](/docs/bank-linking/creating-connections/create-a-connect-token/supported-bank-linking-products). ## Before you begin Before creating your first connection, make sure you have: * A Meld account with at least one Bank Linking service provider enabled in the dashboard * A sandbox API key (treat it as a secret — never expose it in front-end code) * A webhook profile configured if you plan to react to connection lifecycle events * Reviewed the list of [supported products](/docs/bank-linking/creating-connections/create-a-connect-token/supported-bank-linking-products) so you know which to request ## Steps to make a connection 1. [Create a connect token.](/docs/bank-linking/creating-connections/create-a-connect-token) Choose the [products](/docs/bank-linking/creating-connections/create-a-connect-token/supported-bank-linking-products) for which you would like data. 2. [Launch the widget.](/docs/bank-linking/creating-connections/launch-the-widget) 3. [The user selects an institution.](/docs/bank-linking/creating-connections/select-an-institution) 4. [The user completes the connection.](/docs/bank-linking/creating-connections/complete-the-connection) ## Routing Routing sends the user to the best available provider to complete their connection. More information on bank linking routing can be found in the [Routing overview](/docs/bank-linking/creating-connections/select-an-institution/routing-overview). **Routing across service providers** Meld's Bank Linking stack lets you route customers to specific service providers based on predefined defaults, a service provider waterfall driven by institution coverage and availability, or institution-specific decisioning. Routing rules are defined within the Meld dashboard. # Step 2: Launch the Widget Source: https://docs.meld.io/docs/bank-linking/creating-connections/launch-the-widget/index The Meld Connect Widget is the embedded UI your customer uses to pick a financial institution and authenticate with their bank. This page is for developers who already have a connect token and need to render the widget on web, mobile web, or in a native mobile app. Once your customer completes the flow, you can call Meld's APIs to retrieve their financial data. ## Before you begin * You have a valid `connectToken` from [Step 1: Create a Connect Token](/docs/bank-linking/creating-connections/create-a-connect-token) (tokens expire after 3 hours) * You have decided how to render the widget: web iframe, mobile WebView, or new tab * Your front-end is set up to listen for `window.message` events (or the platform equivalent) so it can react to widget events ## Overview The Meld Connect Widget contains two components: * **Front-end component** — used by the customer to select the financial institution. * **Back-end component** — used by your server to access the customer's financial data via the Meld API. Meld recommends launching the widget inside an iframe for optimal UI, and closing it when the `connect_complete` event is emitted. You can also load the URL in a new tab or webview but certain UI elements will not look as polished. How you display the widget does not affect the customer's ability to connect. ## Launch the Meld Connect Widget Use the connect token to build the URL and render the widget. Below is sample code: ```html theme={null} ``` ### Iframe Best Practices: * **Minimum width:** 450px for optimal display * **Minimum height:** 790px to prevent UI overlap * **Allow permissions:** Include `camera`, `microphone`, `payment` for KYC and transactions * **Responsive design:** Consider dynamic sizing for mobile ## Step 2: Customization with URL Parameters Add optional parameters to your Meld Widget URL for customization: ```javascript theme={null} // Your complete Meld Widget URL with optional parameters const dashboardUrl = 'meldcrypto.com/?publicKey=your_public_key'; // From Meld Dashboard Developer tab const customizedUrl = `${dashboardUrl}&sourceAmount=100&destinationCurrencyCode=BTC&countryCode=US`; window.location.href = customizedUrl; ``` ### Key Parameters: * **sourceAmount** - Pre-fill purchase amount (e.g., 100) * **destinationCurrencyCode** - Pre-select cryptocurrency (e.g., BTC, ETH\_ETHEREUM) * **walletAddress** - Pre-fill destination wallet if known * **countryCode** - Override auto-detected country (rarely needed) ➡️ **[Complete parameter reference](/docs/stablecoins/meld-checkout-integration/meld-checkout-guide/url-parameters)** ## Step 3: Testing Your Integration ### Sandbox Testing Always test in sandbox environment first: ```javascript theme={null} // Use your sandbox Meld Widget URL (from Developer tab in sandbox environment) const sandboxUrl = 'meldcrypto.com/?publicKey=your_public_key'; ``` ### Test Workflow: 1. **Redirect to sandbox widget** with your parameters 2. **Complete the test transaction** using the [sandbox testing credentials](/docs/stablecoins/sandbox-guide/test) 3. **Verify webhook reception** (if implemented) 4. **Test all user flows** (buy/sell as applicable) ## Step 6: Mobile Implementation ### React Native Example: ```javascript theme={null} import { Linking } from 'react-native'; const openMeldWidget = () => { const widgetUrl = `meldcrypto.com/?publicKey=your_public_key&redirectUrl=myapp://crypto-complete`; Linking.openURL(widgetUrl); }; ``` ### Deep Link Return: ```javascript theme={null} // Handle return from widget const handleDeepLink = (url) => { if (url.includes('crypto-complete')) { // User completed transaction, update UI navigateToCryptoSuccess(); } }; ``` ### Mobile Considerations: * **Use redirectUrl** with deep links to return users to your app * **Consider theme parameter** to match your app's appearance * **Test on both iOS and Android** for compatibility ## Step 4: Go Live ### Production Checklist: * ✅ **Use production Meld Widget URL** (from Developer tab in production environment) * ✅ **Test with small real transaction** * ✅ **Verify webhook endpoints** are working * ✅ **Monitor first transactions** for issues ### Production URL: ```javascript theme={null} const productionUrl = 'meldcrypto.com/?publicKey=your_public_key'; ``` ## Troubleshooting Common Issues ### Widget Won't Load * ✅ Verify Meld Widget URL is correct and from the right environment * ✅ Check for browser popup blockers * ✅ Ensure iframe permissions are set correctly ### Parameters Not Working * ✅ URL encode parameter values * ✅ Check parameter spelling and case sensitivity * ✅ Verify parameter combinations are valid ### Mobile Issues * ✅ Test deep link return flow * ✅ Verify redirectUrl format is correct * ✅ Check mobile browser compatibility ## Error Handling ### JavaScript Error Handling: ```javascript theme={null} try { window.location.href = widgetUrl; } catch (error) { console.error('Failed to open widget:', error); // Fallback: show error message to user alert('Unable to open crypto purchase. Please try again.'); } ``` ### Iframe Error Handling: ```javascript theme={null} const iframe = document.getElementById('meld-widget'); iframe.onerror = () => { console.error('Widget failed to load'); // Show error message or fallback content }; ``` ## Next Steps ### Optimize your integration * **[URL parameters](/docs/stablecoins/meld-checkout-integration/meld-checkout-guide/url-parameters)** — complete parameter reference * **[Customization](/docs/stablecoins/meld-checkout-integration/meld-checkout-guide/meld-checkout-customization)** — styling and advanced options ### Monitor and improve * **[Webhook events](/docs/stablecoins/for-all-products/webhook-events)** — real-time notifications * **[Transaction statuses](/docs/stablecoins/for-all-products/transaction-statuses)** — understanding transaction states * **[Dashboard data](/docs/stablecoins/additional-information/dashboard-data)** — analytics and reporting *** *Your Widget Integration is now live! Users can purchase crypto directly through your application with Meld's secure, compliant widget.* # Customization Source: https://docs.meld.io/docs/stablecoins/meld-checkout-integration/meld-checkout-guide/meld-checkout-customization This discusses the customization options available to you for the Meld Checkout flow # Meld Checkout Customization & Advanced Configuration Customize your Meld Checkout Integration appearance, behavior, and user experience through dashboard settings and advanced configuration options. ## Meld Support Required Contact Meld for these advanced customizations: ### Account Logo Update your account logo in the top-left corner of the UI. **Process:** Send your logo file (PNG/SVG preferred) to Meld support with specifications: * **Size:** 200x60px recommended * **Format:** PNG with transparent background or SVG * **Colors:** Optimized for both light and dark themes ### Flow Management Control which transaction types are available: * **Disable Buy Flow** - Show only sell/offramp functionality * **Disable Sell Flow** - Show only buy/onramp functionality * **Custom Flow Logic** - Advanced routing based on user profile ### Token Restrictions Limit available cryptocurrencies: * **Chain-specific tokens** - Only show tokens from specific blockchains * **Curated token list** - Custom selection of supported cryptocurrencies * **Partner tokens** - Highlight specific tokens for partnerships ### Quote Management Customize provider quote display: * **Provider prioritization** - Promote specific providers to top of list * **Quote limits** - Control number of quotes shown to users * **Custom provider branding** - Special provider highlighting ### Default Settings Set account-wide defaults: * **Default redirect URL** - Fallback URL for transaction completion * **Theme defaults** - Account-wide light/dark mode preference ## Dashboard Customization Access these options in the **Meld Dashboard → Developer Tab → Preferences**: ### Visual Branding #### Button Color Customize the primary **Buy** or **Sell** CTA button color at the bottom of the UI. ```css theme={null} /* Example color values */ Button Color: #3498db /* Blue */ Button Color: #e74c3c /* Red */ Button Color: #2ecc71 /* Green */ Button Color: #9b59b6 /* Purple */ ``` #### Button Text Color Set the text color inside the CTA button for optimal contrast. ```css theme={null} /* Common combinations */ Button Color: #3498db, Text Color: #ffffff /* Blue background, white text */ Button Color: #ffffff, Text Color: #333333 /* White background, dark text */ Button Color: #2c3e50, Text Color: #ecf0f1 /* Dark background, light text */ ``` ![](https://files.readme.io/ba42d402322fcd88e1fd9fa5c3f3c3070dc7cc8c24e68d67b03a877e0e3762b3-image.png) **Reset Option:** Use the **Reset** button to restore default values at any time. ## Theme Configuration ### Light/Dark Mode Implementation Control the UI appearance with the theme parameter: ```javascript theme={null} // Light theme (default) const lightThemeUrl = `https://meldcrypto.com/?publicKey=${publicKey}&theme=lightMode`; // Dark theme const darkThemeUrl = `https://meldcrypto.com/?publicKey=${publicKey}&theme=darkMode`; // Dynamic theme based on user preference const userTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'darkMode' : 'lightMode'; const dynamicUrl = `https://meldcrypto.com/?publicKey=${publicKey}&theme=${userTheme}`; ``` ### Theme Locking Prevent users from changing the theme: ```javascript theme={null} const lockedThemeUrl = `https://meldcrypto.com/?publicKey=${publicKey}&theme=darkMode&themeLocked=true`; ``` ## Mobile Optimization ### Deep Link Configuration Seamlessly return users to your mobile app after transactions: ```javascript theme={null} // iOS Deep Link const iosUrl = `https://meldcrypto.com/?publicKey=${publicKey}&redirectUrl=myapp://crypto-success`; // Android Deep Link const androidUrl = `https://meldcrypto.com/?publicKey=${publicKey}&redirectUrl=myapp://crypto-success`; // Universal Link const universalUrl = `https://meldcrypto.com/?publicKey=${publicKey}&redirectUrl=https://myapp.com/crypto-success`; ``` ### Responsive Implementation Optimize the UI display for different screen sizes: ```javascript theme={null} // Mobile-first approach function getMobileOptimizedUrl() { const isMobile = window.innerWidth <= 768; const baseUrl = `https://meldcrypto.com/?publicKey=${publicKey}`; if (isMobile) { // Full screen redirect for mobile return `${baseUrl}&redirectUrl=myapp://success`; } else { // Iframe embedding for desktop return baseUrl; } } ``` ### Mobile iframe Considerations ```html theme={null} ``` ## Analytics & Tracking ### Customer Identification Strategy Track users across sessions for analytics: ```javascript theme={null} // Use external customer ID for consistent tracking const analyticsUrl = `https://meldcrypto.com/?publicKey=${publicKey}` + `&externalCustomerId=${userId}` + `&externalSessionId=${sessionId}`; // Example: E-commerce integration function trackCryptoTransaction(userId, orderId) { const trackingUrl = `https://meldcrypto.com/?publicKey=${publicKey}` + `&externalCustomerId=${userId}` + `&externalSessionId=order_${orderId}` + `&redirectUrl=https://shop.com/order-complete?id=${orderId}`; window.location.href = trackingUrl; } ``` ### Conversion Optimization Pre-fill known information to improve conversion rates: ```javascript theme={null} // Optimize for returning customers function getOptimizedCheckoutUrl(userProfile) { let url = `https://meldcrypto.com/?publicKey=${publicKey}`; // Only override country if specifically needed (auto-detection usually works) if (userProfile.Country) { url += `&countryCode=${userProfile.Country}`; } if (userProfile.preferredCrypto) { url += `&destinationCurrencyCode=${userProfile.preferredCrypto}`; } if (userProfile.wallet) { url += `&walletAddress=${userProfile.wallet}&walletAddressLocked=true`; } return url; } ``` ## Advanced Use Cases ### Marketplace Integration Configure the Meld Checkout for NFT marketplace or token sales: ```javascript theme={null} // Token sale configuration function createTokenSaleCheckout(tokenInfo) { return `https://meldcrypto.com/?publicKey=${publicKey}` + `&destinationCurrencyCode=${tokenInfo.currency}` + `&destinationCurrencyCodeLocked=true` + `&sourceAmount=${tokenInfo.price}` + `&sourceAmountLocked=true` + `&theme=darkMode` + `&redirectUrl=https://marketplace.com/purchase-complete`; } // Usage const nftSaleUrl = createTokenSaleCheckout({ currency: 'ETH', price: 150 }); ``` ### Multi-Currency Support Offer multiple cryptocurrency options: ```javascript theme={null} // Dynamic currency selection function createCurrencyCheckout(selectedCrypto) { const cryptoOptions = { 'bitcoin': 'BTC', 'ethereum': 'ETH', 'usdc': 'USDC', 'solana': 'SOL' }; return `https://meldcrypto.com/?publicKey=${publicKey}` + `&destinationCurrencyCode=${cryptoOptions[selectedCrypto]}` + `&theme=lightMode`; } ``` ### Geographic Customization Optimize currency and amounts for specific regions: > **Note:** Country is automatically detected in the Meld Checkout Integration. Use countryCode parameter only when you need to override the detection for special cases. ```javascript theme={null} // Region-specific currency and amount configuration function getRegionalCheckout(overrideCountry = null) { const regionalConfig = { 'US': { currency: 'USD', amount: 100 }, 'GB': { currency: 'GBP', amount: 75 }, 'EU': { currency: 'EUR', amount: 85 }, 'CA': { currency: 'CAD', amount: 130 } }; const config = regionalConfig[overrideCountry] || regionalConfig['US']; let url = `https://meldcrypto.com/?publicKey=${publicKey}` + `&sourceCurrencyCode=${config.currency}` + `&sourceAmount=${config.amount}`; // Only add countryCode if overriding auto-detection if (overrideCountry) { url += `&countryCode=${overrideCountry}`; } return url; } ``` ## Contact Meld For advanced customizations requiring Meld support: **Email/Slack/Telegram:** Contact your integration specialist or support team\ **Documentation:** Reference this guide when making requests ### Request Template: ``` Subject: Meld Checkout Customization Request - [Your Account Name] Customization Type: [Logo Update/Flow Management/Token Restrictions/etc.] Current Setup: [Description of current checkout configuration] Desired Changes: [Specific requirements and use case] Timeline: [When you need changes implemented] ``` *** **Implementation Complete!** Your Meld Checkout Integration is now optimized for your specific use case and user experience requirements. # URL Parameters Source: https://docs.meld.io/docs/stablecoins/meld-checkout-integration/meld-checkout-guide/url-parameters Review the URL params you can use to prefill the Meld Checkout UI # Meld Checkout URL Parameters Reference Complete technical reference for all Meld Checkout Integration URL parameters. Use these parameters to prefill information and customize the user experience. ## Transaction Parameters ### transactionType Default transaction type to display first. If not passed in, the Meld Checkout will default to the Buy flow. ``` &transactionType=BUY # Default to buy flow (default) &transactionType=SELL # Default to sell flow ``` ### sourceAmount Amount of fiat currency to exchange. ``` &sourceAmount=50 # $50 &sourceAmount=100 # $100 (default) &sourceAmount=1000 # $1000 ``` ### sourceCurrencyCode Fiat currency for the transaction. Defaults to country's primary currency if not provided. ``` &sourceCurrencyCode=USD # US Dollars &sourceCurrencyCode=EUR # Euros &sourceCurrencyCode=GBP # British Pounds ``` ### destinationCurrencyCode Cryptocurrency to purchase or sell. ``` &destinationCurrencyCode=BTC # Bitcoin &destinationCurrencyCode=ETH # Ethereum &destinationCurrencyCode=USDC_BASE # USDC on Base &destinationCurrencyCode=SOL # Solana ``` ## Wallet Parameters ### walletAddress User's wallet address for the destination cryptocurrency. ``` &walletAddress=bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh # Bitcoin &walletAddress=0x742c8f2e0ce07Dd3f7E78A31E5A97D45c50fF2c8 # Ethereum &walletAddress=So11111111111111111111111111111111111111112 # Solana ``` ### walletTag Commonly known as wallet tag, destination tag, or memo, this field is only used for certain cryptocurrencies (EOS, STX, XLM, XMR, XRP, BNB, XEM, and HBAR) and must be passed in on top of the wallet address for these cryptocurrencies for the transaction to succeed. ``` &walletAddress=rDsbeomae4FXwgQjRq9bCVFeVbU8c65pNF&walletTag=123456789 ``` ## Location Parameters ### countryCode ISO 2-letter country code to override auto-detected country. > **Note:** Country is automatically detected based on user location in the Meld Checkout Integration. Use this parameter only when you need to override the automatic detection. ``` &countryCode=US # United States &countryCode=GB # United Kingdom &countryCode=CA # Canada &countryCode=DE # Germany &countryCode=AU # Australia ``` ## Payment Parameters ### paymentMethodType How the user plans to pay for cryptocurrency. ``` &paymentMethodType=CREDIT_DEBIT_CARD # Credit/Debit Card &paymentMethodType=BANK_TRANSFER # Bank Transfer &paymentMethodType=APPLE_PAY # Apple Pay &paymentMethodType=GOOGLE_PAY # Google Pay ``` ## User Identification ### customerId Meld's internal customer ID. Do not use with externalCustomerId. ``` &customerId=cust_abc123def456 ``` ### externalCustomerId Your internal customer ID. Do not use with customerId. ``` &externalCustomerId=user_789 &externalCustomerId=customer_12345 ``` ### externalSessionId Your internal session ID for tracking and analytics. ``` &externalSessionId=session_abc123 &externalSessionId=checkout_456 ``` ## UI Parameters ### theme Meld Checkout color theme. ``` &theme=lightMode # Light theme (default) &theme=darkMode # Dark theme ``` ### redirectUrl URL to redirect users after transaction completion. Supports deep links for mobile apps.
> **Note:** Meld does not pass transaction completion data or status through redirect parameters. The redirectUrl is used only to navigate users back to your application. Use webhooks to track transaction status and completion. ``` &redirectUrl=https://yourapp.com/success # Website &redirectUrl=myapp://transaction-complete # Mobile deep link &redirectUrl=https://yourapp.com/crypto/complete # Custom success page ``` ## Field Locking Lock any parameter to prevent user editing by passing the parameter with the word "Locked" after it. Locking a field will make it so that the user cannot edit the values passed into those fields. Field locking only applies to the Meld Checkout interface. However, if you lock the `sourceCurrencyCode`/`destinationCurrencyCode` or `walletAddress` field in the Meld Checkout widget, that field will also be locked in the partner's widget for the providers that support it. See [White-Label customization → Locking fields](/docs/stablecoins/white-label-api-integration/whitelabel-api-guide/white-label-customization) for the list of providers. ### Lockable Parameters: The locked params are as follows: * `countryCodeLocked` - Pass the country code value (e.g., `countryCodeLocked=US`) * `paymentMethodTypeLocked` - Pass the payment method value (e.g., `paymentMethodTypeLocked=CREDIT_DEBIT_CARD`) * `sourceAmountLocked` - Pass the amount value (e.g., `sourceAmountLocked=100`) * `sourceCurrencyCodeLocked` - Pass the currency code value (e.g., `sourceCurrencyCodeLocked=USD`) * `destinationCurrencyCodeLocked` - Pass the crypto code value (e.g., `destinationCurrencyCodeLocked=BTC`) * `walletAddressLocked` - Pass the wallet address value (e.g., `walletAddressLocked=bc1qxy2k...`) * `themeLocked` - Pass the theme value (e.g., `themeLocked=darkMode`) ### Important Rules: * If you pass in both the unlocked and locked version of a param (for example `countryCode` and `countryCodeLocked`), the locked param will be used * Locked parameters take the actual value, not `=true` ### Field Locking Example: ``` meldcrypto.com/?publicKey=your_public_key&destinationCurrencyCodeLocked=BTC&walletAddressLocked=bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh ``` ## Complete Examples ### Basic Bitcoin Purchase ``` meldcrypto.com/?publicKey=your_public_key&sourceAmount=100&destinationCurrencyCode=BTC&countryCode=US ``` ### Locked Ethereum Purchase ``` meldcrypto.com/?publicKey=your_public_key&destinationCurrencyCodeLocked=ETH&walletAddressLocked=0x742c8f2e0ce07Dd3f7E78A31E5A97D45c50fF2c8&themeLocked=darkMode ``` ### Mobile App Integration ``` meldcrypto.com/?publicKey=your_public_key&externalCustomerId=user_12345&externalSessionId=session_abc789&redirectUrl=myapp://crypto-success&theme=lightMode ``` ### Locked BONK Purchase on Solana ``` meldcrypto.com/?publicKey=your_public_key&destinationCurrencyCodeLocked=BONK_SOLANA&walletAddressLocked=6chn9n4CLhNdyBpiLLj9ouUUUmEQYBLWfpUMMuHB9K3k&themeLocked=darkMode ``` ### Marketplace Token Sale ``` meldcrypto.com/?publicKey=your_public_key&destinationCurrencyCodeLocked=USDC&sourceAmountLocked=50&countryCodeLocked=US ``` ## Parameter Validation Rules ### Required Combinations: * **walletTag** requires **walletAddress** for supported cryptocurrencies (EOS, STX, XLM, XMR, XRP, BNB, XEM, and HBAR) * **customerId** and **externalCustomerId** are mutually exclusive - do not pass in both ### Important Parameter Rules: * **Locked parameters override unlocked versions**: If you pass in both the unlocked and locked version of a param (for example `countryCode` and `countryCodeLocked`), the locked param will be used * **Provider UI locking**: If you lock the sourceCurrencyCode/destinationCurrencyCode or walletAddress field in the Meld Checkout, then that field will also be locked in the partner's widget for the providers that support it * **Locked parameters take actual values**: Pass the actual value, not `=true` (e.g., `walletAddressLocked=6chn9n...` not `walletAddressLocked=true`) ### Invalid Parameter Handling: * **Unrecognized parameters** are ignored silently * **Invalid values** fall back to defaults * **Any param with unrecognized or unsupported values** will be ignored ## URL Encoding Always URL encode parameter values that contain special characters: ```javascript theme={null} const walletAddress = 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh'; const redirectUrl = 'https://myapp.com/success?transaction=complete'; const meldCheckoutUrl = 'meldcrypto.com/?publicKey=your_public_key'; const url = `${meldCheckoutUrl}&walletAddress=${encodeURIComponent(walletAddress)}&redirectUrl=${encodeURIComponent(redirectUrl)}`; ``` ## Testing Parameters Focus on successful transaction flows in sandbox environment: ``` meldcrypto.com/?publicKey=your_public_key&sourceAmount=10&destinationCurrencyCode=BTC&countryCode=US ``` Use small amounts and focus on green path flows to validate your integration. *** **Next:** [Customization Options](/docs/stablecoins/meld-checkout-integration/meld-checkout-guide/meld-checkout-customization) for advanced styling and configuration. # Quickstart Source: https://docs.meld.io/docs/stablecoins/meld-checkout-integration/meld-checkout-quickstart This is the 5 minute quickstart guide for executing the Meld checkout flow for the first time. This quickstart is for product teams and developers who want to launch Meld Checkout for the first time. You will get the hosted Meld Checkout URL from the dashboard and embed or redirect to it from your app so users can buy or sell crypto without any UI work on your side. # Meld Checkout Integration Quick Start **Time required:** 5 minutes **Difficulty:** No coding required ## Before you begin * Access to the Meld dashboard (sandbox and production are separate) * A page in your app where you can render a link or trigger a redirect **Sandbox vs production:** Each environment has its own Meld Checkout URL. Sandbox URLs only work with test data; production URLs handle real money. Always finish testing in sandbox first. ## Step 1: Get Your Meld Checkout URL The Meld Dashboard provides a complete, ready-to-use Meld Checkout URL. ### Process: 1. **Check your email** for the Meld dashboard invitation 2. **Click the invitation link** and log in 3. In the dashboard, **navigate to Developer tab** 4. **Find "Meld Checkout URL"** - this is your complete URL 1. Note that you will need Meld to enable this key for you. If you don't see it in the Meld dashboard, please reach out to your Meld account manager to request this being added. 5. **Copy and save** your Meld Checkout URL securely ### **Note:** You get a complete URL from the dashboard, not just a key. This URL is ready to use immediately! ## Step 2: Use Your Meld Checkout URL ### Your Dashboard URL (Ready to Use): ``` [Copy the complete URL from your Meld Dashboard Developer tab] ``` ### Add Optional Parameters for Customization: **See the [URL parameters guide](/docs/stablecoins/meld-checkout-integration/meld-checkout-guide/url-parameters) for all available parameters and options.** **See the [Customization guide](/docs/stablecoins/meld-checkout-integration/meld-checkout-guide/meld-checkout-customization) for all available styling options.** ``` meldcrypto.com/?publicKey=your_meld_checkout_key&sourceAmount=100&destinationCurrencyCode=BTC&countryCode=US ``` ### Integration Examples: **HTML Link:** ```html theme={null} Buy Crypto ``` **JavaScript Redirect:** ```javascript theme={null} function buyCrypto() { window.location.href = 'meldcrypto.com/?publicKey=your_meld_checkout_key'; } ``` ## Step 3: Test Your Integration ### Sandbox Testing: 1. **Use your Meld Checkout URL** (get from Developer tab in sandbox environment) 2. **Open the URL** in your browser 3. **Complete the test transaction** using [sandbox test credentials](/docs/stablecoins/sandbox-guide/test) 4. **Verify in dashboard** - check Transactions tab ## Step 4: Go Live ### Production Setup: 1. **Use your Meld Checkout URL** (get from Developer tab in production environment) 2. **Test with small real transaction** 3. **Start directing users** to the Meld Checkout widget! ✅ **Meld Checkout Integration Complete!** Users can now buy digital assets directly through your application. ➡️ **[Meld Checkout Customization Guide](/docs/stablecoins/meld-checkout-integration/meld-checkout-guide/meld-checkout-customization)** — styling and advanced options ## Next steps If you're ready to customize and complete your integration, see the end-to-end [Meld Checkout Guide](/docs/stablecoins/meld-checkout-integration/meld-checkout-guide).
# Sandbox Testing Guide Source: https://docs.meld.io/docs/stablecoins/sandbox-guide/index # Sandbox Environment Guide This guide is for developers and QA testing their Meld crypto integration before going live. It covers what the sandbox is for, what it cannot do, which providers have usable sandboxes, and the supported test combinations. Note that the countries, tokens, fiat currencies, and payment methods supported in sandbox are a small subset of the ones supported in production. We recommend you only test in sandbox with the below combinations for the best results: **Tokens** * BTC * ETH * USDC **Countries** * US * Europe **Payment Methods** * CREDIT\_DEBIT\_CARD * APPLE\_PAY * GOOGLE\_PAY **Fiat Currencies** * USD * EUR In production you will be able to use thousands of other combinations — see [Digital asset coverage](/docs/stablecoins/digital-asset-coverage).
## ⚠️ Important: Sandbox Purpose and Limitations ### What Sandbox is For The sandbox environment is designed for: * **Exploring transaction flows** and understanding how the process works * **Testing UI integration** and user experience * **Familiarizing yourself** with provider interfaces * **Initial development** and basic functionality testing * **Happy path combinations** of the most widely used tokens ### What Sandbox is NOT For **🚨 Critical Warning:** The sandbox environment should **NOT** be relied upon for: * **Coding solutions related to calculations** * **Production data accuracy validation** * **Finalizing business logic** based on API responses * **Performance testing** with accurate response times * **Coverage testing** because onramp sandboxes are very limited in the tokens they support **API responses in the sandbox environment may lack complete data accuracy** and should only be used for understanding flow structure, not for building calculation logic. ## 🌍 Environment Access | Environment | Widget URL | API Base URL | | -------------- | --------------------------- | ------------------------ | | **Sandbox** | `https://sb.meldcrypto.com` | `https://api-sb.meld.io` | | **Production** | `https://meldcrypto.com` | `https://api.meld.io` | ## 🏦 Sandbox-Friendly Providers ### ✅ Recommended for Testing (Full Sandbox Support) These providers offer reliable sandbox environments ideal for testing: * **Unlimit** - Complete sandbox functionality with test cards * **Simplex** - Reliable test environment with test wallet support * **TransFi** - Good sandbox with specific test credentials * **Transak** - Full-featured sandbox with test SSN/PAN numbers * **Sardine** - Phone-based testing with predictable OTP system ### ⚠️ Limited Sandbox Support These providers have sandbox limitations: * **Banxa** - Sandbox often rejects KYC/transactions; production testing recommended * **Stripe** - Functions like production; requires real payment for full testing * **Paybis** - Basic sandbox functionality * **BTC Direct** - Limited sandbox features ### ❌ Production Only These providers do not offer sandbox environments: * **Robinhood** - Production environment only * **Coinbase Pay** - Production environment only * **Blockchain.com** - Production environment only * **Alchemy Pay** - Production-like sandbox requiring real payments ## 🔄 Flow Testing Capabilities ### Buy Flow (Onramp) * ✅ **Fully testable** end-to-end in sandbox * Complete transaction simulation available * Test cards and credentials work for most providers * **Recommended starting point** for all integrations ### Sell Flow (Offramp) * ⚠️ **Limited testing** - quote generation only * Cannot complete full transactions in sandbox * Real cryptocurrency/fiat required for end-to-end testing * Use for UI flow testing only ### Transfer Flow * ⚠️ **Limited testing** - configuration and quotes only * Cannot execute actual transfers in sandbox * Real wallet addresses required for testing * Currently available in **White-Label API only** ## 🚀 Testing Best Practices ### 1. Start with Buy Flow * Begin all integrations with buy flow testing * Use recommended providers (Unlimit, Simplex, TransFi) * Complete full transaction flow before moving to other flows ### 2. Provider Selection for Testing * Choose providers from "Recommended for Testing" list * Avoid production-only providers during initial development * Test with multiple providers to understand variations ### 3. Sandbox Data Handling * **Never build calculation logic** based on sandbox responses * Use sandbox only for flow understanding and UI testing * Validate all calculations and business logic in production ### 4. Progressive Testing Approach ``` Sandbox (Flow Testing) → Staging (Integration Testing) → Production (Final Validation) ``` ## 🔧 Integration Testing Workflow 1. **Sandbox Flow Testing** * Test UI integration * Understand provider interfaces * Validate basic functionality 2. **Production Validation** * Test with real (small) transactions * Validate calculation accuracy * Confirm webhook functionality 3. **Go Live** * Deploy with confidence * Monitor real transaction data * Iterate based on production insights ## 📞 Support For sandbox-related questions or issues: * Check provider-specific testing credentials in the detailed testing guide * Review flow limitations before troubleshooting * Contact Meld support for integration guidance Remember: **Sandbox is for learning the flow, production is for validating the logic.** # White-Label API and Meld Checkout Testing Credentials Source: https://docs.meld.io/docs/stablecoins/sandbox-guide/test # Testing Overview This page is for developers and QA running sandbox transactions through Meld's White-Label API or Meld Checkout flows. It lists test cards, wallet addresses, KYC triggers, and quirks for each onramp's sandbox. ## Quick start **New to testing?** Start with the [Sandbox Environment Guide](/docs/stablecoins/sandbox-guide) to understand sandbox limitations and best practices. **Ready to test?** This page contains specific testing credentials for each provider. # Provider testing credentials Before testing, review the [Sandbox Environment Guide](/docs/stablecoins/sandbox-guide) to understand sandbox limitations. ## Testing Notes * Most providers have relaxed KYC requirements in sandbox * Some providers validate wallet addresses match the cryptocurrency type * Use test credentials provided below for each provider * Start with **Buy Flow testing** as it's fully supported in sandbox ## Unlimit Use the following test cards when testing in [Unlimit's](https://docs.gatefi.com/) sandbox. | Card Type | Card Number | Expiration | CVV | | :--------- | :------------------ | :--------- | --- | | Visa | 4000 0000 0000 0085 | 10/30 | 123 | | Mastercard | 5100 0000 0000 0065 | 10/30 | 123 | Use a real wallet address when testing, for example when trying to buy `ETH_GOERLI` (ETH on TestNet) use a real `ETH` wallet address. ## Banxa **While Banxa's sandbox does sometimes allow users to complete transactions, often KYC or transactions are rejected. Therefore Meld recommends testing in Banxa's production rather than its sandbox.** In Banxa's sandbox, the OTP is **7203**. Use the following test cards when testing in [Banxa's](https://docs.banxa.com/docs/testing-information) sandbox. | Card Type | Info | Card Number | Expiration | CVV | | :-------- | :--- | :------------------ | :--------- | --- | | Visa | US | 4111 1111 1111 1111 | 01/30 | 555 | Banxa allows real wallet addresses while testing in their sandbox. ## BTC Direct Use the following test cards when testing in [BTC Direct's](https://docs.adyen.com/development-resources/testing/) sandbox. | Card Type | Card Number | Expiration | CVV | | :--------- | :------------------ | :--------- | --- | | Visa | 4111 1111 4555 1142 | 03/30 | 737 | | Mastercard | 2222 4000 7000 0005 | 03/30 | 737 | BTC Direct allows real wallet addresses while testing in their sandbox. ## TransFi Use the following test cards when testing in [TransFi's](https://docs.transfi.com/docs) sandbox. | Card Name | Card Number | Expiration | CVV | | :-------- | :------------------ | :--------- | --- | | FL-BRW1 | 4000 0278 9138 0961 | 11/30 | 123 | Test in TransFi's sandbox using `BTC` and the wallet address `2N3oefVeg6stiTb5Kh3ozCSkaqmx91FDbsm`. ## Transak Use the following test cards when testing in [Transak's](https://docs.transak.com/docs/test-credentials) sandbox. | Card Type | Card Number | Expiration | CVV | | :-------- | :------------------ | :--------- | --- | | Visa | 4111 1111 1111 1111 | 10/33 | 123 | | Visa | 4485 1415 2054 4212 | 10/33 | 100 | For US: Use SSN number `000000001` to create a test account.\ For India: Use PAN number `ABCDE1234A` to place an INR order. Transak requires a wallet address that matches the token passed in, for example if you are testing with `BTC_TESTNET` then use wallet address `2N3oefVeg6stiTb5Kh3ozCSkaqmx91FDbsm`. ## Onramp Money In Onramp.Money's Sandbox, to get past the phone number screen, you can use the phone number `+91 8660031402` and the passcode `112233`. Use any real wallet address when testing, and Meld recommends testing with the token ETH. ## Fonbnk Fonbnk requires a real wallet address for whatever token is selected in their sandbox. Also, there is no test card available, since the payment methods Fonbnk supports are Bank Transfer and Airtime. Use Bank Transfer for testing purposes. ## Paybis Use the following test cards when testing in Paybis' sandbox. | Card Type | Card Number | Expiration | CVV | | :-------- | :------------------ | :--------- | --- | | Visa | 4111 1111 1111 1111 | 10/30 | 123 | Paybis requires a wallet address that matches the token passed in, for example if you are testing with `BTC_TESTNET` then use wallet address `2N3oefVeg6stiTb5Kh3ozCSkaqmx91FDbsm`. ## Koywe When testing in Koywe's sandbox, a single user will only be able to make transactions in 1 particular country. Koywe also requires real ID for it's sandbox, but it doesn't have to match the country of the transaction (for example a US ID will work even when making a Chile transaction). We recommend testing with Chile and the payment method Khipu in order for transactions to be completed successfully. Use the following values to complete a transaction. | Amount | Payment Method | RUT | Khipu Password | Khipu Secondary Codes | Bank Account | | :--------------------------------------------- | :------------- | :----------- | -------------- | :-------------------- | :----------- | | 20000 CLP (Khipu won't work if its over 25000) | Khipu | 28.666.939-5 | 1234 | 11, 22, 33 | Any | ## Robinhood Robinhood does not have a sandbox, only a production environment. Therefore you will have to make a real payment to fully test Robinhood's onramp. ## Coinbase Pay Coinbase Pay does not have a sandbox, only a production environment. Therefore you will have to make a real payment to fully test Coinbase Pay's onramp. ## Blockchain.com Blockchain.com does not have a sandbox, only a production environment. Therefore you will have to make a real payment to fully test Blockchain.com's onramp. ## Stripe Use the following test values when testing in [Stripe's](https://docs.stripe.com/crypto/integrate-the-onramp#test-mode-values) sandbox. | OTP | SSN | Address Line 1 | Card Number | Expiration | CVV | | :----- | :-------- | :------------------- | ------------------- | :--------- | :-- | | 000000 | 000000000 | address\_full\_match | 4242 4242 4242 4242 | 10/30 | 123 | If you are bringing over your own Stripe account: 1. please reach out to Stripe to enable `preferred_payment_method` and `source_total_amount` for your account, or else you will see errors. 2. When setting up Stripe webhooks, do the following steps in order: 1. Grab your Stripe API keys from the Stripe dashboard and put them in the Meld dashboard, putting in any value for the Webhook Secret. 2. Take the Meld webhook url from the Meld dashboard and go to the Stripe dashboard and set up webhooks to Meld. Select all events. 3. Take the generated Stripe webhook secret and go back to the Meld dashboard and edit that value to add that in so that webhooks work properly. ## Alchemy Pay Alchemy Pay's sandbox functions like a real production environment. Therefore you will have to make a real payment to fully test Alchemy Pay's onramp. ## Topper Use the following test information when testing [Topper](https://docs.topperpay.com/environments/) in sandbox. You can use rw2ciyaNshpHe7bCHo4bRWq6pqqynnWKQg for the token XRP\_XRPLEDGER and if prompted for a wallet tag use 224333255 | Card Type | Card Number | Expiration | CVV | | :-------- | :--------------- | :-------------- | ----------- | | Visa | 4921817844445119 | Any future date | Any 3 digit | ## Mercuryo Use the following test values when testing in Mercuryo's sandbox. | Card Type | Cardholder Name | Card Number | Expiration | CVV | | :-------- | :-------------- | :------------------ | :-------------- | --------------------------------- | | Visa | Cardholder Name | 4444 4444 4444 3333 | any future date | 123 | | Visa | Cardholder Name | 5555 4444 3333 1111 | any future date | CVV 123 for success, 555 for fail | Your IP address must be whitelisted by Mercuryo in order to launch their sandbox widget. ## Kryptonim Use the following test values when testing in Kryptonim's sandbox. | Card Type | Cardholder Name | Card Number | Expiration | CVV | | :-------- | :-------------- | :--------------- | :--------- | --- | | Visa | any name | 4929111260419572 | 05/2028 | 790 | ## Swapped Use the following test values when testing in Swapped's sandbox. | Card Type | Cardholder Name | Card Number | Expiration | CVV | | :-------- | :-------------- | :------------------ | :--------- | --- | | Visa | any name | 4929 4205 7359 5709 | 10/31 | 123 |
# Product 3: Virtual Account and Payouts Source: https://docs.meld.io/docs/stablecoins/virtual-account-integration/index Overview of the Virtual Account Flow This page is for product and engineering teams evaluating Meld's Virtual Account product. It explains what virtual accounts are, how onramp vs offramp flows work, and when this product is the right choice. For a step-by-step integration, jump to the [Virtual Account Quickstart](/docs/stablecoins/virtual-account-integration/virtual-account-quickstart). ## What are virtual accounts? A virtual account is a dedicated set of receiving details — a bank account number, IBAN, or crypto address — assigned to a specific customer. When funds arrive at those details, the provider automatically converts and settles them to the destination you configured (e.g. a crypto wallet for onramp, or a bank account for offramp). **Onramp (fiat to crypto):** The customer receives unique bank details (ACH routing + account number, SEPA IBAN, etc.). They deposit fiat from their bank; the provider converts it and delivers stablecoin to the specified wallet address. **Offramp (crypto to fiat):** The customer receives a crypto deposit address. They send stablecoin from their wallet; the provider converts and pays out fiat to the bank details on file. In this flow, typically a virtual account is not created per business / user unless necessary. Instead the onramp sends money directly from the provider's funding account to the business' / user's end bank account. Because each virtual account is unique to a customer, deposits are automatically attributed — no manual reference matching required. Meld orchestrates the provider integration, KYC, quoting, and settlement tracking so you work with a single API surface regardless of which provider (Noah, Due, etc.) handles the underlying rails. Please reach out to Meld for more information about virtual accounts and how to enable them.
# Full Integration Guide Source: https://docs.meld.io/docs/stablecoins/virtual-account-integration/virtual-account-guide This is the full end to end integration guide for integrating the virtual account flow. This is the end-to-end Virtual Account integration guide for developers. It walks through the complete API sequence — customer creation, KYC, quoting, order creation, and webhook tracking — that you'll need to build a production-grade onramp or offramp. ## Before you begin * You have completed the [Virtual Account Quickstart](/docs/stablecoins/virtual-account-integration/virtual-account-quickstart) end to end at least once in sandbox * You have a Meld API key with virtual-account access (sandbox first, production for go-live) * You have a webhook endpoint configured in **Developer → Webhooks** in the dashboard * If you are KYCing end users, you have at least one virtual-account provider (Noah, Due, or Brale) enabled on your account # Notes * This flow is supported for both businesses and individuals. Businesses will have to KYB with Meld, which is manual. Users will have to KYC via Meld with the onramp, which can be done via API, and is described below. * While both Noah and Due support this flow for both businesses and users, Brale only supports this flow for businesses at this time. Brale also requires an additional *redemption* step in the onramp flow in order for the business to receive their crypto. # Full Integration Guide This is the end-to-end guide for executing virtual account transactions (onramp and offramp) through the Meld API. > **API compatibility:** New fields may appear in responses without a version bump. Use flexible JSON parsing and do not reject unknown properties. Breaking changes ship under a dated `Meld-Version`. *** ## How it works 1. Create a **customer** in the Account API for the individual or business that will transact. 2. **KYB** your business with Meld. 3. If your end users will be making transactions, then **KYC** each customer with the provider (e.g. Noah, Due). Meld asks the provider to verify the customer; the provider responds asynchronously. 4. Get a **quote** from the Payment API for the currency pair and amount. 5. Create an **onramp order** (fiat to crypto) or **offramp order** (crypto to fiat). 6. The business or individual completes the fiat or crypto leg (bank transfer or wallet send). 7. Track progress via **webhooks** and the Transactions API. *** ## 1. Create a Customer API Endpoint: `POST /accounts/customers` **Request:** ```json theme={null} { "externalId": "your-internal-user-id-123", "name": { "firstName": "John", "lastName": "Doe" }, "email": "john@example.com", "phone": "+14155551234", "dateOfBirth": "1990-03-15", "type": "INDIVIDUAL" // or BUSINESS if you are a business } ``` **Response (abbreviated):** ```json theme={null} { "id": "WmYYgvN8ukpV62N3m4u3ee", "accountId": "W2aRZnYGPwhBWB94iFsZus", "externalId": "your-internal-user-id-123", "name": { "firstName": "John", "lastName": "Doe" }, "email": "john@example.com", "phone": "+14155551234", "dateOfBirth": "1990-03-15", "status": "ACTIVE", "type": "INDIVIDUAL", "serviceProviderCustomers": [], "addresses": [] } ``` Save the returned `id` — you will pass it as `customerId` on every subsequent call. If you plan on using `DUENETWORK` later for executing transactions, you must also specify the customer's address by calling `POST /accounts/customers/{customerId}/addresses`. > **Note:** Order and transaction requests also accept optional `subaccountCustomerId` and `externalSubaccountCustomerId` fields. Use these to tag which sub-account or business grouping a transaction belongs to for reporting and filtering purposes. *** ## 2. KYC / KYB If you are transacting as a business, you will KYB with Meld. If your end users are transacting, each will have to KYC once with each onramp they use. KYC happens via Sumsub. If you have already KYCed your users with Sumsub, you can pass in the KYC token — see [You KYC the user](/docs/stablecoins/unified-kyc/you-kyc-the-user). If you'd prefer Meld to KYC the user, that process is described in [Meld KYCes the user](/docs/stablecoins/unified-kyc/meld-kyces-the-user). To test KYC in the sandbox, follow the steps in [KYC testing](/docs/stablecoins/sandbox-guide/kyc-testing). **Initiate KYC:** API endpoints: * `POST /accounts/customers/{customerId}/kyc/initiate` * `PATCH /accounts/customers/{customerId}/kyc/initiate` **Request:** ```json theme={null} { "serviceProvider": "SUMSUB", "mode": "HOSTED_URL" } ``` Alternatively, if you already have a user who has passed [KYC on your Sumsub account](/docs/stablecoins/unified-kyc/you-kyc-the-user), you can import it into Meld by sharing the Sumsub applicant token (`TOKEN_IMPORT` mode). To import the KYC of the user, call `POST /accounts/customers/{customerId}/kyc/initiate`. The `customerId` is in the path, and your request body looks like this: ```json theme={null} { "serviceProvider": "SUMSUB", "mode": "TOKEN_IMPORT", "serviceProviderDetails": { "kycToken": "_act-sbx-jwt-eyJhbGciOiJub25lIn0.eyJqdGkiOiJfYWN0LXNieC1mNGFlNWYwYy1iYjFmLTQwZmUtYTk1YS1jZWM0MmY3OTBjYmIiLCJ1cmwiOiJodHRwczovL2FwaS5zdW1zdWIuY29tIn0.", "applicantId": "Sumsub applicant id" } } ``` **Response:** ```json theme={null} { "customerId": "WmYYgvN8ukpV62N3m4u3ee", "serviceProvider": "SUMSUB", "status": "PENDING", "url": "https://kyc-provider.example.com/verify/abc123" } ``` Direct the user to the `url` to complete verification. If you are not passing in a token, Sumsub will ask the user for all required fields, and if the token is passed in, Sumsub will only prompt the user for any missing fields. KYC completes **asynchronously** — the provider notifies Meld via webhook, and Meld publishes a `CUSTOMER_KYC_STATUS_CHANGE` webhook to your endpoint. **Sample KYC webhook payload:** ```json theme={null} { "eventType": "CUSTOMER_KYC_STATUS_CHANGE", "eventId": "BBxyzABC123456defGHIjk", "timestamp": "2025-02-24T14:20:00.000000Z", "accountId": "WQ5RyhdFzE45qjsomdzQ1u", "version": "2025-03-01", "payload": { "customerId": "WmYYgvN8ukpV62N3m4u3ee", "status": "APPROVED" } } ``` **Poll for KYC Status:** API Endpoint: `GET /accounts/customers/{customerId}` Check `serviceProviderCustomers[].kyc.status`. Values: `PENDING`, `APPROVED`, `REJECTED`, `EXPIRED`, `UNKNOWN`. Wait for the kyc.status to be `APPROVED` before proceeding. The customer must be KYC-approved before any transactions can be created. In both KYC initiation modes (**HOSTED\_URL** and **TOKEN\_IMPORT**), you can optionally specify Virtual Account providers you want to share the user's KYC with after Sumsub approves submission, via the `kycShareProviders` field. Alternatively, you can add share providers later by calling `PATCH /accounts/customers/{customerId}/kyc/initiate`. ```json theme={null} { "serviceProvider": "SUMSUB", "mode": "HOSTED_URL", "kycShareProviders": ["NOAH", "DUENETWORK"] } ``` ```json theme={null} { "serviceProvider": "SUMSUB", "mode": "TOKEN_IMPORT", "kycShareProviders": ["NOAH", "DUENETWORK"], "serviceProviderDetails": { "kycToken": "_act-sbx-jwt-eyJhbGciOiJub25lIn0.eyJqdGkiOiJfYWN0LXNieC1mNGFlNWYwYy1iYjFmLTQwZmUtYTk1YS1jZWM0MmY3OTBjYmIiLCJ1cmwiOiJodHRwczovL2FwaS5zdW1zdWIuY29tIn0.", "applicantId": "Sumsub applicant id" } } ``` After Sumsub verifies the user's submission, the KYC token will be automatically shared with all specified share Virtual Account providers, effectively onboarding the user on Virtual Account's platforms. The Virtual Account provider may require additional KYC for users in case the data shared by Sumsub is insufficient. To track this, you can either listen for `CUSTOMER_KYC_STATUS_CHANGE` webhooks. **Supported share providers and their requirements** * Noah * Due Network. Requires customer country to be specified — call `POST /accounts/customers/{customerId}/addresses` to add an address. **Expected Webhook Response** ```json theme={null} { "eventType": "CUSTOMER_KYC_STATUS_CHANGE", "eventId": "customerId", "timestamp": "2022-02-24T16:36:41.717262Z", "payload": { "accountId": "WeP9eoFziQX4yXE5abcfec", "customerId": "customerId", "serviceProvider": "SUMSUB", "status": "APPROVED", "kycRecepient": { "serviceProvider": "Virtual account provider", "status": "PENDING" // PENDING, REJECTED, APPROVED }, "statusUpdatedAt": "2022-02-24T16:36:41.717262Z" } } ``` After receiving a webhook event, you can call `GET /accounts/customers` for more details. For example, when the Virtual Account provider requires additional KYC on their platform, you will receive a webhook event with `kycRecipient.status=PENDING`. Then, when calling `GET /accounts/customers`, you will receive the following response: ```json theme={null} { "id": "customerId", "accountId": "accountId", "serviceProviderCustomers": [ { "serviceProvider": "Virtual Account Provider", "id": "provider customer id", "kyc": { "status": "PENDING", "updatedAt": "2026-04-08T20:04:20.457281Z", "additionalInfo": { "HostedURL": "https://virtual-provider-paltform.com/additionalKycHostedURL" // addtional provider specific kyc fields, like addtional requirements (documents, questionnaire, etc.) }, "onboardingMethod": { "kycProvider": "SUMSUB", "onboardingType": "KYC_TOKEN_SHARE" } } } ], "status": "ACTIVE", "type": "INDIVIDUAL" } ``` You will need to direct your user to `HostedURL` so they can pass additional KYC on the Virtual Account provider's platform. After users complete additional KYC and the Virtual Account provider verifies it, you will receive another `CUSTOMER_KYC_STATUS_CHANGE` event with `kycRecipient.status=APPROVED`. The new status will also be reflected in `GET /accounts/customers` responses. **Errors:** * Calling `initiate` again for the same customer + provider returns **409**. *** ## 3. Configure webhooks In the Meld dashboard (**Developer > Webhooks**), add your endpoint URL and subscribe to at minimum: * `TRANSACTION_CRYPTO_PENDING` * `TRANSACTION_CRYPTO_TRANSFERRING` * `TRANSACTION_CRYPTO_COMPLETE` * `TRANSACTION_CRYPTO_FAILED` * `CUSTOMER_KYC_STATUS_CHANGE` You can find all webhook events in [Webhook events](/docs/stablecoins/for-all-products/webhook-events). Verify webhook signatures per [Webhook authentication](/docs/stablecoins/for-all-products/webhooks-authentication). *** ## 4. Get a Quote API Endpoint: `POST /payments/virtual-account/crypto/quote` Note that the provider Brale doesn't support quotes. All Brale transactions are 1:1, aka for \$100 you will receive 100 USDC. **Request:** ```json theme={null} { "countryCode": "US", "sourceAmount": 100, "sourceCurrencyCode": "USD", "destinationCurrencyCode": "USDC", "paymentMethodType": "ACH", "customerId": "WmYYgvN8ukpV62N3m4u3ee" } ``` For a **sell** quote, swap the currency codes: `sourceCurrencyCode` is the crypto, `destinationCurrencyCode` is the fiat. **Response (abbreviated):** ```json theme={null} { "quotes": [ { "transactionType": "CRYPTO_PURCHASE", "sourceAmount": 100, "sourceCurrencyCode": "USD", "destinationAmount": 99.10, "destinationCurrencyCode": "USDC", "totalFee": 0.90, "networkFee": 0.00, "transactionFee": 0.90, "exchangeRate": 1.0, "paymentMethodType": "ACH", "serviceProvider": "NOAH" } ] } ``` **Key fields per quote:** | Field | Meaning | | ------------------- | ---------------------------------------------------- | | `sourceAmount` | What the user spends (fiat for buy, crypto for sell) | | `destinationAmount` | What the user receives | | `totalFee` | Total fees in the fiat currency | | `serviceProvider` | Provider backing this quote | | `transactionType` | `CRYPTO_PURCHASE` (buy) or `CRYPTO_SELL` (sell) | Present quotes to the user. Capture the selected `serviceProvider` for the next step. *** ## 5. Create an Order ### Buy (onramp) — fiat to crypto API Endpoint: `POST /payments/virtual-account/ramp/onramp/order` Use the `customerId` from Step 1. The `serviceProvider` comes from the quote the user selected. **Request:** ```json theme={null} { "customerId": "WmYYgvN8ukpV62N3m4u3ee", "sourceAmount": 100, "sourceCurrencyCode": "USD", "destinationCurrencyCode": "USDC", "destinationWalletAddress": "0x51FB80013111111111111112121111111", "paymentMethodType": "ACH", "serviceProvider": "NOAH" } ``` **Response:** ```json theme={null} { "orderId": "WeP9eoFziQX4yXE5abcfec", "paymentMethodType": "ACH", "receivingBankInformation": { "accountNumber": "9876543210", "routingNumber": "021000021" }, "serviceProviderDetails": {} } ``` The user sends fiat to the returned bank details from their banking app. The provider settles crypto to `destinationWalletAddress` after receiving the funds. ### Sell (offramp) — crypto to fiat API Endpoint: `POST /payments/virtual-account/ramp/offramp/order` **Request:** ```json theme={null} { "customerId": "WmYYgvN8ukpV62N3m4u3ee", "sourceAmount": 100, "sourceCurrencyCode": "USDC", "destinationCurrencyCode": "USD", "sourceWalletAddress": "0x51FB80013111111111111112121111111", "paymentMethod": { "type": "ACH", "owner": "John Doe", "details": { "accountType": "CHECKING", "routingNumber": "1111111111", "accountNumber": "22222222", "bankName": "Chase Bank", "beneficiaryAddress": { "street_line_1": "123 Market St.", "street_line_2": null, "city": "San Francisco", "state": "CA", "postalCode": "94115", "country": "US" }, "bankAddress": { "street_line_1": "270 Park Avenue", "street_line_2": null, "city": "New York", "state": "NY", "postalCode": "10017", "country": "US" } } }, "serviceProvider": "NOAH" } ``` **Offramp `paymentMethod.type` options:** | Type | Details schema | | ----------------------------- | ------------------------------------------------------------------------------------------------ | | `ACH` / `LOCAL_BANK_TRANSFER` | `accountType`, `routingNumber`, `accountNumber`, `bankName`, `beneficiaryAddress`, `bankAddress` | | `SEPA` | `iban` | **Response:** ```json theme={null} { "orderId": "WfR7abcDEF12345xyz9876", "paymentMethodType": "ACH", "walletAddress": "0xABCDEF1234567890ABCDEF1234567890ABCDEF12" } ``` The user sends crypto to `walletAddress` from their wallet. The provider pays out fiat to the bank details after receiving the crypto. Make sure the user sends the correct amount of crypto as in the Create Sell Order API call. *** ## 6. Track the Transaction ### Webhooks Meld sends webhooks as the transaction progresses. Correlate to your order using `virtualAccountRampOrderId` in the payload (matches the `orderId` from the order response). Find more information on the various webhook event types in [Webhook events](/docs/stablecoins/for-all-products/webhook-events). ### Sample webhook payload (buy) ```json theme={null} { "eventType": "TRANSACTION_CRYPTO_PENDING", "eventId": "AAsuLXHXD3mS1cjNBuHHzv", "timestamp": "2025-02-24T16:36:41.717262Z", "accountId": "WQ5RyhdFzE45qjsomdzQ1u", "profileId": "W9ka8vLE4ufBkSg3BEciZb", "version": "2025-03-01", "payload": { "virtualAccountRampOrderId": "WeP9eoFziQX4yXE5abcfec", "paymentTransactionId": "WePZCYJW7cdXR7SxUMp8mE", "customerId": "WmYYgvN8ukpV62N3m4u3ee", "externalCustomerId": "your-internal-user-id-123", "paymentTransactionStatus": "PENDING", "transactionType": "CRYPTO_PURCHASE", "sessionId": "WePjVaT4iBHPpqW49F419x" } } ``` For sell transactions, `transactionType` is `CRYPTO_SELL`. The `paymentTransactionStatus` field shows the current status of the transaction. Find the list of transaction statuses and their meanings in [Transaction statuses](/docs/stablecoins/for-all-products/transaction-statuses). ### Fetch the full transaction Extract `paymentTransactionId` from the webhook and call: API Endpoint: `GET /payments/transactions/{paymentTransactionId}` **Sample response:** ```json theme={null} { "id": "WePZCYJW7cdXR7SxUMp8mE", "accountId": "W2aRZnYGPwhBWB94iFsZus", "transactionType": "CRYPTO_PURCHASE", "status": "SETTLED", "sourceAmount": 100.00, "sourceCurrencyCode": "USD", "destinationAmount": 99.10, "destinationCurrencyCode": "USDC", "paymentMethodType": "ACH", "serviceProvider": "NOAH", "serviceTransactionId": "noah-tx-abc123", "orderId": "WeP9eoFziQX4yXE5abcfec", "customer": { "id": "WmYYgvN8ukpV62N3m4u3ee", "externalId": "your-internal-user-id-123" }, "countryCode": "US", "sessionId": "WePjVaT4iBHPpqW49F419x", "externalSessionId": null, "createdAt": "2025-02-24T16:36:41.000000Z", "updatedAt": "2025-02-24T17:05:12.000000Z" } ``` The `orderId` on the transaction matches `virtualAccountRampOrderId` in the webhook and the `orderId` from your order response, tying all three together. *** ## Sequence diagram ![Virtual Account Integration Flow](https://www.plantuml.com/plantuml/png/tLJHZjf657ttLrnjNn8fb03B2aNQAiRO17KNN61JhPeA3VO5HfWPPsOCfQkL-jGFgFg5-PBE3AQsPQ9kNcjBFCmvzzvpxXdpNYeYLBPB7BcbB2M2x42WJ3cJU8zIaZNsCU47LmX-02KoB16N9Dgk1SzOxF642_WkyCrROiWDYVc1iZMiI2BHAKFuEKCM8Jmv0BPztgHZd_DXm9cQqTyHMgtoKSwzjey6xkWAxoZ3FXSnYXprx5D6QuAxvdKq9IH2qOYcXAbAZ_RWndFRLKAjZTyBMa6lIYgfRBcTGTUhmpDrJ12MF8chM4ZYsEoirNQCvqXnKT7KzSnxXkgctfht39j1rSrgPAZvwIMVEVG1IOpbqi0-PxY-W7xG2TnwjKakjp6WUwfFBm_DmLhVmOr_4tGBGdsf1LHTQ7vUbV5Sle3855L7U_qgedhsCQUrnVQ2jxwvcIKPNU_QRRkcSre425UFF1FW9LGFkG8YWJA5Qq4K5mungS2Lps9ua2Wid4aKrGPk5Ed92jnmP1aaYbILyUEZ1w7WrgIbR8zHbH6IumvBFL9oT7BNLtt2jj04RqCeiw0WDBdWuMTSRZZVVc9QRyffRkUmSPpRMvWfJA45N_xu6po1H25ucLHyXm9kRnsdC0sD7wm3U0VXP3Q79ZdPayqeBv2EnHkQM9Hf-XFG-1sSMQCOdel9RJlPxE4Z2hgY4L6Ki_yONKniFj7ukQctrgICAaeKVbL39f8t81LHcTHj_F5WD1uQ9IOtCq0tqJoFxz845SA-B6TFYQbt3ATH4fD71n594lDF2g-8FBBeTI7Yi35D7sRpoRq_NMYVYzdSdJ_C5ju7TppqBviS-ZMFdvYU_Vr9ljuEk64udNpq1yKcEtQJGrJmKyOLrbTexiw_6znJeJAIDF8GcvzDZMCOjTLPIFxoQp7gcjXP4oR8BeLNAqeKVFdpBuZ4CLNyjHrjlhhK_glq_3xqiwC3y78vtBPks6_Q0KEz5Ac5H_4R41WXtLEsDf_4YAsusDdWJBu0USBsyz1rCsGnMCsAGpuObheGykaEdAYDNAGfKIf1yo6MnDXevrDtF7Ez-MWoNdZJnt0-MUHtwmLCUnJuq-dBwDxaFhppv_x5JUQtHWd298cCUNrnr6-Z-DdHValPBK_PhLQdy4wPcVZiJYhX1K7GyTzgzqdoRUcPvjyGtu4a2IetZWLA-VNCSgcALml-Zt6zrp_PBla7) ## Testing Provider sandboxes often cannot complete full settlement — you may only reach virtual bank account creation. See [Virtual account flow testing credentials](/docs/stablecoins/sandbox-guide/virtual-account-flow-testing-credentials) for provider-specific sandbox values (Noah, Due, Brale). ## Next steps ### Testing your implementation * [**Virtual account testing credentials**](/docs/stablecoins/sandbox-guide/virtual-account-flow-testing-credentials) — provider-specific sandbox values ### Advanced features * [**Webhook events**](/docs/stablecoins/for-all-products/webhook-events) — real-time updates * [**Dashboard data**](/docs/stablecoins/additional-information/dashboard-data) — track performance ### Production deployment * [**Service-provider setup**](/docs/stablecoins/for-all-products/service-provider-setup) — configure additional onramps *** ## Support & resources * [**Postman collection**](https://www.postman.com/meldeng/workspace/meld-io-public-api-collection) — test APIs directly * [**FAQ**](/docs/stablecoins/faq) — common questions answered
# Product 1: White-Label API Source: https://docs.meld.io/docs/stablecoins/white-label-api-integration/index Overview of the White-Label API Integration Flow The White-Label API integration is for developers and product teams who want to build their own UI on top of Meld's APIs to let users buy, sell, or transfer crypto. The transaction itself — including KYC and payment — happens in the onramp's UI; you control everything before and after. This is the most popular choice due to its UI flexibility. 1. For the 30-minute quickstart guide that gets you through the flow for the first time, see the [White-Label Quickstart](/docs/stablecoins/white-label-api-integration/whitelabel-quickstart). 2. Once you have completed the quickstart, see the [end-to-end White-Label API Guide](/docs/stablecoins/white-label-api-integration/whitelabel-api-guide) for the full integration walkthrough. If you're still deciding between products, the [Overview](/docs/stablecoins/crypto-overview_) compares White-Label API, Meld Checkout, and Virtual Account side by side. # Full Integration Guide Source: https://docs.meld.io/docs/stablecoins/white-label-api-integration/whitelabel-api-guide/index This is the full end to end integration guide for integrating the white-label API flow. # Build Your Own UI with Meld APIs This guide is for developers building a custom cryptocurrency interface on top of Meld's White-Label API. You'll learn the exact sequence of API calls — from detecting the user's country to launching the provider widget and tracking the transaction — needed to ship a production-ready buy/sell/transfer flow with your own UI. ## Before you begin * ✅ **API key** from the Meld dashboard (see [White-Label Quickstart](/docs/stablecoins/white-label-api-integration/whitelabel-quickstart) Step 1) * ✅ **Webhook endpoint** configured (recommended; you can also poll the API) * ✅ **Basic understanding** of REST APIs and JSON * ✅ **Sandbox environment** — base URL `https://api-sb.meld.io` Sandbox and production credentials, base URLs, and widget URLs are separate. Develop against sandbox until your flow is ready, then switch credentials to go live. **Important:** This is White-Label API Integration, NOT Meld Checkout 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 ## Technical Architecture ``` ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Your UI │◄──►│ Meld APIs │◄──►│ Provider UI │───►│ Your UI │ │ │ │ │ │ (Widget) │ │ │ ├─────────────┤ ├─────────────┤ ├─────────────┤ ├─────────────┤ │ • Quotes │ │ • Pricing │ │ • Payment │ │ • Status │ │ • Selection │ │ • Sessions │ │ • KYC │ │ • Results │ │ • Launch │ │ • Webhooks │ │ • Compliance│ │ • Tracking │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ ``` ## 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): ```javascript theme={null} // 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: ```javascript theme={null} // Sample Code: Get supported countries list with flags GET /service-providers/properties/countries?accountFilter=true ``` ### For US Users - State Requirements: ```javascript theme={null} // 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 ``` ![](https://files.readme.io/f8d4f0f7f888b118578747b0f9b21352d1478ef730a2eba53e1476a492c0cdb7-ezgif-38c25fa2d98f23.gif) *Screenshot: Meld Widget showing automatic country detection with manual override option* ### API Response Example: ```json theme={null} { "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: ```javascript theme={null} // Sample Code: Get default currency and payment method for country GET /service-providers/properties/defaults/by-country?countries={countryCode} ``` **Response:** ```json theme={null} [ { "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: ```javascript theme={null} // Sample Code: Get all available fiat currencies for the country GET /service-providers/properties/fiat-currencies?countries={countryCode}&accountFilter=true ``` ### Get Payment Methods: ```javascript theme={null} // Sample Code: Get payment methods available for selected fiat currency GET /service-providers/properties/payment-methods?fiatCurrencies={fiatCurrency}&accountFilter=true ``` ### UI Implementation: ```javascript theme={null} // 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); ``` ![](https://files.readme.io/79ce5b8f16fe7ced3dafc545083e92b7cf374c3e1d3c643d83e409c6e36245e9-Screenshot_2025-09-02_at_2.08.10_PM.png) *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: ```javascript theme={null} // Sample Code: Get available cryptocurrencies for the country GET /service-providers/properties/crypto-currencies?countries={countryCode}&accountFilter=true ``` ### Response Example: ```json theme={null} { "cryptoCurrencies": [ { "currencyCode": "BTC", "currencyName": "Bitcoin", "networkCode": "BTC", "networkName": "Bitcoin" }, { "currencyCode": "ETH_ETHEREUM", "currencyName": "Ethereum", "networkCode": "ETHEREUM", "networkName": "Ethereum" } ] } ``` ### UI Implementation: ```javascript theme={null} // 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) })); } ``` ![](https://files.readme.io/d8047750901e8d2f687baaac9e44fac18736c63dc14d64583e7fbb8a3b799db3-ezgif-34fa46603b5728.gif) *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: ```javascript theme={null} // Sample Code: Get purchase limits for validation GET /service-providers/limits/fiat-currency-purchases?accountFilter=true ``` ### Response Example: ```json theme={null} { "limits": { "USD": { "minAmount": 20, "maxAmount": 20000, "dailyLimit": 5000, "monthlyLimit": 20000 } } } ``` ### User Input Validation: ```javascript theme={null} // 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 }; } ``` ![](https://files.readme.io/d13ccd4e7660f6c04d85a78f18419d945245c65ceff5e2ed4b2c7e39cf7c3896-Screenshot_2025-09-02_at_2.12.41_PM.png) *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. It is important that you tie each user to a Meld customerId or externalCustomerId (they map 1:1). This will ensure that the rampIntelligence suggests the best quote for the user. ### API Call: ```javascript theme={null} // Sample Code: Get real-time quotes from multiple providers POST /payments/crypto/quote ``` ### Request Body: ```json theme={null} { "externalCustomerId": "user_123", "sourceAmount": "200", "sourceCurrencyCode": "USD", "destinationCurrencyCode": "ETH", "countryCode": "US", "walletAddress": "0xfCFAa8059080D01b27ccA2B1fA086df0853397E6" } ``` ### Response Example: ```json theme={null} { "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, "partnerFee": 2, "destinationAmount": 0.04308219, "destinationCurrencyCode": "ETH", "exchangeRate": 4642.290, "paymentMethodType": "CREDIT_DEBIT_CARD", "serviceProvider": "UNLIMIT", "rampIntelligence": { "rampScore": 20.00, "lowKyc": false } }, { "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, "partnerFee": 1.96, "destinationAmount": 0.042577, "destinationCurrencyCode": "ETH", "exchangeRate": 4697.4, "paymentMethodType": "CREDIT_DEBIT_CARD", "serviceProvider": "ROBINHOOD", "rampIntelligence": { "rampScore": 19.76, "lowKyc": false } } ], "message": null, "error": null, "timestamp": null } ``` ### Key Response Fields Explained: * **`rampScore`**: Meld's recommendation score (higher = better conversion likelihood) * **`lowKyc`**: Whether provider requires minimal identity verification ![](https://files.readme.io/5e8d9532c381286e3ea1edeff72364e228c23ea704a8f6ce985fce53394d4966-ezgif-352c80746562fc.gif) *Screenshot: Meld Widget showing multiple provider quotes with ranking* ## 🏆 **MELD RECOMMENDED QUOTE RANKING** **Use Meld's`rampScore` for optimal conversion rates!** Meld provides a `rampScore` 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: ```javascript theme={null} // Sample Code: Use Meld's recommended ranking for best results function rankQuotesByMeldScore(quotes) { return quotes.sort((a, b) => { // Primary: Sort by rampScore (higher = better) if (a.rampScore !== b.rampScore) { return b.rampScore - a.rampScore; } // Secondary: Sort by destinationAmount (more crypto = better) return b.destinationAmount - a.destinationAmount; }); } ``` ### **Best Practice:** Always display the highest `rampScore` quote first to maximize transaction success rates and user satisfaction. 📊 **Learn More:** See [Ramp Intelligence](/docs/stablecoins/white-label-api-integration/whitelabel-api-guide/ramp-intelligence) 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: ```javascript theme={null} // Sample Code: Create widget session to get provider URL POST /crypto/session/widget ``` ### Request Body: ```json theme={null} { "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: ```json theme={null} { "id": "WePLapZetkn1hfeKFScf3T", "externalSessionId": "session_456", "externalCustomerId": "user_123", "customerId": "WXEX4DsAX7cp6Ch78oq2w3", "widgetUrl": "https://meldcrypto.com?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "serviceProviderWidgetUrl": "https://transak.com/?sessionId=asjknakjnjknwejksdnjkdsjkfnsd", "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." } ```
Note that the `widgetUrl` returns a Meld url, which iframes the onramp's url (if allowed) or does a full redirect to the onramp's url for the onramps that block iframing. The `serviceProviderWidgetUrl` directly returns the url of the onramp. You can choose to launch either one, based on your preference. We recommend using the `serviceProviderWidgetUrl` instead of the `widgetUrl` **Recommendation:** ### Provider UI Launch Implementation: #### Desktop Implementation: ```javascript theme={null} // Sample Code: Launch provider UI in popup window function launchProviderUI(sessionResponse, options = {}) { const popup = window.open( sessionResponse.serviceProviderWidgetUrl, '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: ```javascript theme={null} // Sample Code: Launch provider UI on mobile function launchMobileProviderUI(sessionResponse) { // For webviews if (isMobileApp()) { window.ReactNativeWebView?.postMessage( JSON.stringify({ type: 'OPEN_WEBVIEW', url: sessionResponse.serviceProviderWidgetUrl }) ); } else { // Mobile browser - full redirect window.location.href = sessionResponse.widgetUrl; } } ``` ![](https://files.readme.io/c36806bc626f1b6cdbc3d19d59c327e5857af4e9afefb645d7cbd00d871e2302-ezgif-358045d7123d01.gif) *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. You will receive webhooks from Meld every time a transaction is created or updated. To learn more about the various Meld webhook events and see payload examples, see [Webhook events](/docs/stablecoins/for-all-products/webhook-events). ### Webhook Integration: ```javascript theme={null} // 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: ```javascript theme={null} // 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.'; } ``` ### **Status Reference:** See [Transaction Statuses](/docs/stablecoins/for-all-products/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): ```javascript theme={null} 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): ```javascript theme={null} // Always fetch fresh: - Crypto quotes - Widget session creation - Transaction details - Transaction status ``` ### Implementation Example: ```javascript theme={null} 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: ```javascript theme={null} 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 * **[Sandbox testing credentials](/docs/stablecoins/sandbox-guide/test)** — test cards, wallets, and KYC triggers ### Advanced features * **[Webhook events](/docs/stablecoins/for-all-products/webhook-events)** — real-time updates * **[Dashboard data](/docs/stablecoins/additional-information/dashboard-data)** — track performance * **[Ramp Intelligence](/docs/stablecoins/white-label-api-integration/whitelabel-api-guide/ramp-intelligence)** — improve success rates ### Production deployment * **[Service-provider setup](/docs/stablecoins/for-all-products/service-provider-setup)** — configure additional onramps *** ## Support & resources * **[FAQ](/docs/stablecoins/faq)** — common questions answered *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.* # Ramp Intelligence Source: https://docs.meld.io/docs/stablecoins/white-label-api-integration/whitelabel-api-guide/ramp-intelligence # Ramp Intelligence Meld's intelligent quote ranking system maximizes conversion rates by guiding users toward the most suitable payment providers. Through advanced algorithms analyzing user history, provider performance, and KYC requirements, Meld helps developers achieve significantly higher success rates. ### **Proven Results** Meld's rampScore and lowKyc optimization features have enabled developers to achieve **69-94% end-to-end conversion rates** - a dramatic improvement over industry averages. ## Overview Every quote response includes intelligent ranking data to help you: * **Prioritize familiar providers** where users have succeeded before * **Identify low-friction options** requiring minimal verification * **Optimize for user success** based on historical performance * **Reduce abandonment rates** through smart provider selection ### **Scope**: These optimization features apply to buy transactions only and are included automatically in all quote responses. ## Understanding KYC Impact **Know Your Customer (KYC)** verification significantly impacts conversion rates. Each provider has different requirements: * **Minimal KYC**: Email, phone, basic details (high conversion) * **Standard KYC**: Document uploads, identity verification (medium conversion) * **Enhanced KYC**: Bank statements, proof of funds (low conversion) Meld's optimization ensures users are directed to providers where they've already completed verification, dramatically reducing friction. ## Key Optimization Parameters Every quote includes these conversion-critical fields: ### `rampScore` (0-100) Meld's proprietary recommendation engine that predicts conversion likelihood for each user-provider pair. Higher scores indicate greater success probability. Meld recognizes users by their wallet address, so this score works across the entire Meld ecosystem, even if your user used the same wallet address via a different wallet before! ### `lowKyc` (boolean) Indicates whether the transaction can complete without document uploads for new users. ### Calculation Factors Meld's algorithm processes multiple data points, updated several times daily: **User History (Primary Factor)** * Previous successful transactions with each provider * Transaction recency and frequency * Failure patterns and recovery behavior * Cross-wallet transaction history within Meld network **Provider Performance** * Historical success rates by geography and payment method * Average processing times and reliability metrics * Fee competitiveness and spread analysis * Payment method diversity and regional support **Market Dynamics** * Real-time provider availability and capacity * Regional regulatory compliance status * Seasonal performance variations ### Score Interpretation | Score Range | Meaning | Recommendation | | ----------- | ------------------- | ------------------------------------------------- | | 70-100 | **Excellent Match** | Multiple recent successes, prioritize prominently | | 30-69 | **Good Match** | Some success history, feature positively | | 0-29 | **New/Unknown** | No significant history, standard positioning | **Implementation Note**: Always include the user's `walletAddress` in quote requests to enable personalized scoring. ### Implementation Strategy **Primary Sorting**: Always rank quotes by `rampScore` (highest first) **Visual Emphasis**: Highlight providers with scores > 30 using: * "⭐ Recommended" badges * Primary button styling * "You've used this before" messaging **Code Example**: ```typescript theme={null} interface Quote { rampScore: number; serviceProvider: string; lowKyc: boolean | null; partnerFee: number | null; sourceAmount: number; destinationAmount: number; totalFee: number; // ... other fields } function sortQuotesByRecommendation(quotes: Quote[]): Quote[] { return quotes.sort((a, b) => b.rampScore - a.rampScore); } function getRecommendationBadge(score: number): string { if (score >= 70) return "🏆 Your Best Choice"; if (score >= 30) return "⭐ Recommended"; return ""; } ``` ### **Best Practice**: Never hide or de-emphasize high-scoring options, even if they have higher fees. User familiarity typically outweighs small price differences in conversion impact. ## Low KYC Optimization The `lowKyc` flag identifies providers where new users can complete transactions without document uploads, significantly reducing friction and improving conversion rates. ### What Low KYC Means **✅ Low KYC Requirements:** * Basic information: email, phone, name * Simple verification: SMS codes, email confirmation * Optional: SSN or tax ID (varies by country) * **No document uploads required** **❌ Standard KYC Requirements:** * Government ID photos (license, passport) * Proof of address documents * Bank statements or financial verification * Selfie verification ### Response Values | Value | Meaning | User Experience | | ------- | ---------------------------------------------------- | ----------------------------------- | | `true` | No documents needed for this amount | ⚡ Fast completion (2-5 minutes) | | `false` | Documents required | 📋 Standard process (10-30 minutes) | | `null` | Provider doesn't specify or amount exceeds threshold | 🤷 Unknown requirements | ### Implementation Guidelines **Badge Messaging**: * `lowKyc: true` → "⚡ No Documents Required" * `lowKyc: false` → Standard presentation * `lowKyc: null` → No special treatment **Code Example**: ```typescript theme={null} function getLowKycBadge(lowKyc: boolean | null): string { return lowKyc === true ? "⚡ No Documents Required" : ""; } function shouldHighlightForSpeed(quote: Quote): boolean { return quote.lowKyc === true; } ``` ### **Conversion Impact**: Low KYC options can improve completion rates by 40-60% for new users, making them valuable for customer acquisition. ## Implementation Example Here's a realistic quote response showing how optimization parameters work together: ```json theme={null} { "quotes": [ { "transactionType": "CRYPTO_PURCHASE", "sourceAmount": 200.00, "sourceAmountWithoutFees": 195.83, "destinationAmount": 0.04308219, "destinationCurrencyCode": "ETH", "exchangeRate": 4642.29, "paymentMethodType": "CREDIT_DEBIT_CARD", "serviceProvider": "UNLIMIT", "partnerFee": 2, "rampIntelligence": { "rampScore": 78.5, "lowKyc": null } }, { "transactionType": "CRYPTO_PURCHASE", "sourceAmount": 200.00, "sourceAmountWithoutFees": 193.09, "destinationAmount": 0.04176125, "destinationCurrencyCode": "ETH", "exchangeRate": 4789.13, "paymentMethodType": "CREDIT_DEBIT_CARD", "serviceProvider": "KRYPTONIM", "partnerFee": 2, "rampIntelligence": { "rampScore": 45.2, "lowKyc": true } }, { "transactionType": "CRYPTO_PURCHASE", "sourceAmount": 200.00, "sourceAmountWithoutFees": 190.07, "destinationAmount": 0.041154, "destinationCurrencyCode": "ETH", "exchangeRate": 4859.80, "paymentMethodType": "CREDIT_DEBIT_CARD", "serviceProvider": "STRIPE", "partnerFee": 2, "rampIntelligence": { "rampScore": 12.1, "lowKyc": false } } ] } ``` ### Analysis & UI Recommendations **1. UNLIMIT (Score: 78.5)** * 🏆 **Primary Recommendation**: Highest customer score indicates strong success history * **UI Treatment**: Featured position, "Your Best Choice" badge * **Conversion Expectation**: Very high (user familiarity) **2. KRYPTONIM (Score: 45.2)** * ⚡ **Speed Advantage**: `lowKyc: true` means fast completion for new users * **UI Treatment**: "No Documents Required" badge * **Conversion Expectation**: Good for new users, moderate for returning **3. STRIPE (Score: 12.1)** * **Standard Treatment**: Lowest score, no special advantages * **UI Treatment**: Regular presentation * **Conversion Expectation**: Lower (unknown user history) ### Optimal Implementation Strategy ```typescript theme={null} // Sort quotes optimally function optimizeQuoteDisplay(quotes: Quote[]): Quote[] { return quotes .sort((a, b) => b.rampScore - a.rampScore) .map(quote => ({ ...quote, recommendationBadge: getRecommendationBadge(quote.rampScore), speedBadge: getLowKycBadge(quote.lowKyc), isPrimary: quote.rampScore >= 70, isRecommended: quote.rampScore >= 30 })); } ``` ### **Key Principle**: Rank by `rampScore` first, then enhance with `lowKyc` badges. Never let low KYC override strong user history - familiarity typically wins over convenience. ## Summary Meld's conversion optimization combines user history intelligence with friction analysis to maximize transaction success rates. By implementing proper quote ranking and visual emphasis, developers can achieve industry-leading conversion performance. **Implementation Checklist:** * ✅ Always include `walletAddress` in quote requests * ✅ Sort quotes by `rampScore` (highest first) * ✅ Add visual badges for scores > 30 * ✅ Highlight `lowKyc: true` options with speed messaging * ✅ Never override high rampScore with other factors * ✅ Monitor conversion metrics and iterate # Supporting Multiple Downstream Applications Source: https://docs.meld.io/docs/stablecoins/white-label-api-integration/whitelabel-api-guide/supporting-multiple-downstream-applications This page is for platforms and aggregators whose Meld integration serves multiple downstream partners, applications, or developers. It explains the two architectural options Meld supports and how each affects fees, onramp coverage, and reporting. If your use case involves supporting multiple downstream partners / applications / developers, there are 2 ways to handle that. 1. Having 1 Meld account 2. Having multiple Meld accounts In both approaches, you should pass in an `externalCustomerId` that corresponds to the end user making the transaction. This is just a reference id you provide, and can have any `string` value. ## Option 1: A single Meld account Can your downstream applications use the same set of onramps with the same fee set by you? If so, then the easiest way to support this is to use a single Meld account. * Meld (and you) will still need to know which application is responsible for each transaction. When you call `POST /crypto/session/widget`, use the `externalSubaccountCustomerId` field to pass in an identifier corresponding to the application that made the call. The `externalSubaccountCustomerId` you pass in will be tied to the transaction the user ends up making. This is useful for compliance, metrics, and revenue calculation. * Several providers require whitelisting the domains that users will get redirected to after completing a transaction. If you will have many applications, it may not make sense to whitelist each new domain with the provider. In this case, it would make sense for you to have an intermediary redirect url with a domain that you whitelist, that then automatically redirects to the actual target url. An example would be [https://yourcompanyurl.com/?redirectUrl=https://yourapplicationurl.com](https://yourcompanyurl.com/?redirectUrl=https://yourapplicationurl.com). Here you would only have to whitelist [https://yourcompanyurl.com](https://yourcompanyurl.com), but you would build an automatic redirect so that the above redirect sends the user to [https://yourapplicationurl.com](https://yourapplicationurl.com). ## Option 2: Multiple Meld accounts If your downstream applications need different sets of service provider and want to charge different partner fees, you may need multiple Meld accounts. Meld will need to create each account for you. Contact Meld about this. If you use 1 Meld account per downstream application, you do not need to pass in an `externalSubaccountCustomerId`. The `externalSubaccountCustomerId` will be returned in the transaction webhooks and you can also query `GET /payments/transactions` with this value. This allows you to see all transactions made by a particular downstream application. ## Implementation Instructions Steps to using `externalSubaccountCustomerId`: First, create a customer with `POST /accounts/customers`, using the string value as the `externalId`. A sub-account customer must be created before it can be referenced on a widget session. Sample request: ```json theme={null} { "externalId": "business1", "type": "BUSINESS" } ``` When you call `POST /crypto/session/widget`, pass in the `externalSubaccountCustomerId` corresponding to the downstream app in the body of the call. Sample request: ```json theme={null} { "sessionData": { "walletAddress": "2N3oefVeg6stiTb5Kh3ozCSkaqmx91FDbsm", "countryCode": "US", "sourceCurrencyCode": "USD", "sourceAmount": "100", "destinationCurrencyCode": "USDC", "serviceProvider": "TRANSAK" }, "externalCustomerId": "user1", "externalSubaccountCustomerId": "business1" } ``` Track which transaction was made by which `externalSubaccountCustomerId` by reading that value in the webhooks, the `GET /payments/transactions` response, and the dashboard CSV export. # Customization Source: https://docs.meld.io/docs/stablecoins/white-label-api-integration/whitelabel-api-guide/white-label-customization # Locking fields This page is for developers who want their users to land in the onramp's widget with specific fields pre-filled and non-editable. Some service providers support locking the token and wallet address fields within their widgets. To do this, pass the `lockFields` parameter to the `POST /crypto/session/widget` endpoint with one or both of `destinationCurrencyCode`/`sourceCurrencyCode` (depending on buy vs sell flow) and `walletAddress`. The table below shows which service providers support locking which fields.
Locking cryptocurrency
Locking wallet address
**Banxa**
Not supported
Not supported
**BTC Direct**
Supported
Supported
**Onramp Money**
Not supported
Always locked when passed in
**Transak**
Supported
Supported
**Binance Connect**
Always locked
Not supported
**TransFi**
Always locked when passed in
Always locked when passed in
**Unlimit**
Supported
Supported
**Paybis**
Not supported
Not supported
**Fonbnk**
Not supported
Supported
**Koywe**
Always locked when passed in
Always locked when passed in
**Robinhood**
Always locked when passed in
Always locked when passed in
**Coinbase**
Not supported
Not supported
**Blockchain**
Always locked when passed in
Always locked when passed in
**Kryptonim**
Supported
Supported
**Topper**
Supported
Supported
**Mercuryo**
Supported
Not Supported
**Swapped**
Not Supported
Not Supported
**Guardarian**
Not Supported
Supported
Here's a sample buy request with both the token and wallet address fields locked: ```json theme={null} { "sessionData": { "walletAddress": "2N3oefVeg6stiTb5Kh3ozCSkaqmx91FDbsm", "countryCode": "US", "sourceCurrencyCode": "USD", "sourceAmount": "100", "destinationCurrencyCode": "USDC", "serviceProvider": "TRANSAK", "lockFields": ["destinationCurrencyCode","walletAddress"] }, "externalCustomerId": "test1", "externalSessionId": "test1" } ```
# Quickstart Source: https://docs.meld.io/docs/stablecoins/white-label-api-integration/whitelabel-quickstart This is the 30 minute quickstart guide for executing the white-label API flow for the first time. # Integration Quickstart This quickstart is for developers integrating Meld's White-Label API for the first time. You'll go from zero credentials to a successfully completed sandbox transaction in about 30 minutes. **Time required:** 30 minutes **Difficulty:** API knowledge required ## Before you begin * A Meld dashboard invitation (check your email) * Ability to make authenticated REST calls (curl, Postman, or your language of choice) * A publicly reachable webhook URL (optional; you can poll the API instead) **Sandbox base URL:** `https://api-sb.meld.io`. Production credentials and URLs are separate — sandbox credentials will not work in production and vice versa. ## Step 1: Get Your API Key Your API key is required for all Meld API calls. ### Process: 1. **Check your email** for the Meld dashboard invitation 2. **Click the invitation link** and log in 3. In the dashboard [https://dashboard.meld.io](https://dashboard.meld.io) , **navigate to Developer > API Keys** 4. **Click "Reveal Key"** 5. **Copy and save** your API key securely **Important:** Always add `BASIC` before your API key in all requests **Example:** `BASIC W9kZTT7332okCEc1A9aqAq:3sYKoXQv6oHVHSts7G2agw9vTCXz` *** ## Step 2: Set Up Webhooks Webhooks notify your server when transactions are created and updated. This step can be completed later. ### Requirements: * **A publicly accessible URL** (localhost will not work) * For testing, consider using [ngrok](https://ngrok.com/), [webhook.site](https://webhook.site/) or similar services ### Setup Process: 1. Navigate to **Developer > Webhooks** in the dashboard 2. Click **"Add Endpoint"** 3. Enter your **webhook URL** (must start with http/https) 4. Give your webhook a **descriptive name** 5. Select **"Subscribe to all events"** 6. Click **"Add endpoint"** ### **Note:** You will receive notifications to this URL for all transaction creations and updates. *** ## Step 3: Test Your API Connection Verify your API key by requesting a price quote. ### API Call Details: * **Endpoint:** `POST /payments/crypto/quote` * **Authorization:** `BASIC [your-api-key]` ### Request Body: ```json theme={null} { "countryCode": "US", "sourceCurrencyCode": "USD", "destinationCurrencyCode": "BTC", "paymentMethodType": "CREDIT_DEBIT_CARD", "sourceAmount": 100 } ``` ### Testing Notes: * **Use the interactive docs** at the endpoint URL ### Expected Response: ✅ **200 status code** with quote details Here is an example of a single quote. You can expect multiple quotes, 1 per onramp, in the response. ```json theme={null} { "quotes": [ { "transactionType": "CRYPTO_PURCHASE", "sourceAmount": 100.00, "sourceAmountWithoutFees": 95.82, "fiatAmountWithoutFees": 95.82, "destinationAmountWithoutFees": null, "sourceCurrencyCode": "USD", "countryCode": "US", "totalFee": 4.18, "networkFee": 0.28, "transactionFee": 2.9, "partnerFee": 1, "destinationAmount": 0.00105423, "destinationCurrencyCode": "BTC", "exchangeRate": 94856.0, "paymentMethodType": "CREDIT_DEBIT_CARD", "serviceProvider": "TOPPER", "rampIntelligence": { "rampScore": 21.66, "lowKyc": false } } ] } ``` *** ## Step 4: Create a Test Transaction Create a test transaction without using real money. ### API Call Details: * **Endpoint:** `POST /crypto/session/widget` * **Authorization:** `BASIC [your-api-key]` ### Request Body: ```json theme={null} { "countryCode": "US", "sourceCurrencyCode": "USD", "destinationCurrencyCode": "BTC", "paymentMethodType": "CREDIT_DEBIT_CARD", "sourceAmount": 100, "serviceProvider": "TRANSAK", "walletAddress": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh" } ``` ### **Note:** Replace `walletAddress` with your own or use the test address provided. ### Expected Response: ✅ **200 status code** with onramp url details ```json theme={null} { "id": "WePjVaT4iBHPpqW49F419x", "externalSessionId": "testSession", "externalCustomerId": "testCustomer", "customerId": "WePZCYZjAK97cJWokfH3Jc", "widgetUrl": "https://meldcrypto.com?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtZWxkLmlvIiwiaWF0IjoxNzY1MjM2MjExLCJzdWIiOiJjcnlwdG8iLCJleHAiOjE3NjUyMzgwMTEsImFjY291bnRJZCI6IldRNVJ5aGRGekU0NXFqc29tZHpRMXUiLCJzZXNzaW9uSWQiOiJXZVBqVmFUNGlCSFBwcVc0OUY0MTlhIn0.TH6P9KVKN4GNu4CNsDAN9uicjMBashgA9QY7jiMiDEF", "serviceProviderWidgetUrl": "https://topper.com?apiKey=1234&sessionId=eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJvdHQiOiIxZDFjOTM1ODY0YTQ0NGM0YTJiMmQ5ODJkYjc0Njg1NCIsImlhdCI6MTc2NTIzNjIxMiwiZXhwIjoxNzY1MjM2NTEyfQ.6ZnmR5FAxzr9bG3n_I54L1EmHYljfhPJNeqp97WZPI7GUm9VCksbKv_rWK4KiB6YkAAJR6_C1Xsnn-fliUrABC", "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtZWxkLmlvIiwiaWF0IjoxNzY1MjM2MjExLCJzdWIiOiJjcnlwdG8iLCJleHAiOjE3NjUyMzgwMTEsImFjY291bnRJZCI6IldRNVJ5aGRGekU0NXFqc29tZHpRMXUiLCJzZXNzaW9uSWQiOiJXZVBqVmFUNGlCSFBwcVc0OUY0MTlhIn0.TH6P9KVKN4GNu4CNsDAN9uicjMBashgA9QY7jiMiDEF" } ``` ### Complete the Test Flow: 1. **Copy the`widgetUrl`** from the API response 2. **Open the URL** in your browser 3. **For KYC testing:** * **SSN:** Use any fake number * **ID Upload:** Any image file works 4. **For payment testing:** * **Card Number:** `4111 1111 1111 1111` * **Expiry:** Any future date * **CVV:** Any 3 digits 📚 **Full test data reference:** [Sandbox testing credentials](/docs/stablecoins/sandbox-guide/test) *** ## Step 5: Verify Your Transaction ### Fetch Transaction Details after Receiving Webhooks 1. **Check your webhook endpoint** for a webhook that the transaction has been created. ### Expected Webhook Response ```json theme={null} { "eventType": "TRANSACTION_CRYPTO_PENDING", "eventId": "AAsuLXHXD3mS1cjNBuHHzv", "timestamp": "2022-02-24T16:36:41.717262Z", "accountId": "WQ5RyhdFzE45qjsomdzQ1u", "profileId": "W9ka8vLE4ufBkSg3BEciZb", "version": "2025-03-01", "payload": { "requestId": "f07f1accb7404aec9bd9a5d64975eed1", "accountId": "W2aRZnYGPwhBWB94iFsZus", "paymentTransactionId": "WePZCYJW7cdXR7SxUMp8mE", "customerId": "WePZCYZjAK97cJWokfH3Jc", "externalCustomerId": "testCustomer", "externalSessionId": "testSession", "paymentTransactionStatus": "PENDING", "transactionType": "CRYPTO_PURCHASE", "sessionId": "WePjVaT4iBHPpqW49F419x" } } ``` 2. **Extract the`paymentTransactionId`** from the webhook payload 3. **Call** `GET /payments/transactions/{transactionId}` ### Expected Response: ✅ **200 status code** with transaction details ```json theme={null} { "transaction": { "id": "WePZCYJW7cdXR7SxUMp8mE", "parentPaymentTransactionId": null, "accountId": "WQ5RyhdFzE45qjsomdzQ1u", "isPassthrough": false, "passthroughReference": null, "isImported": false, "customer": { "id": "WePZCYZjAK97cJWokfH3Cc", "accountId": "WQ5RyhdFzE45qjsomdzQ1u", "externalId": "YC100" }, "transactionType": "CRYPTO_PURCHASE", "status": "SETTLED", "sourceAmount": 100.00, "sourceCurrencyCode": "USD", "destinationAmount": 0.00105423, "destinationCurrencyCode": "BTC", "paymentMethodType": "CREDIT_DEBIT_CARD", "serviceProvider": "TOPPER", "serviceTransactionId": "1f0d470c-f5c2-6fc2-a54d-b6cdd6b6f014", "orderId": null, "description": null, "externalReferenceId": "testSession", "serviceProviderDetails": { **raw details from the onramp** }, "multiFactorAuthorizationStatus": null, "createdAt": "2025-12-08T20:03:07.173223Z", "updatedAt": "2025-12-08T20:08:08.877106Z", "countryCode": "US", "sessionId": "WePZCbr96gAUxgu7Auvm4q", "externalSessionId": "testSession", "paymentDetails": null, "externalCustomerId": "testCustomer", "fiatAmountInUsd": 100.00, "sessionClientTags": null, "serviceProviderTransactionUrl": null, "serviceProviderCreatedAt": "2025-12-08T20:02:22Z", "cryptoDetails": { "sourceWalletAddress": null, "destinationWalletAddress": "0xd72cc3468979360e31bc83b84f0887deccfd81d5", "sessionWalletAddress": "0xd72cc3468979360e31bc83b84f0887deccfd81d5", "totalFee": 4.18, "networkFee": 0.28, "transactionFee": 2.9, "partnerFee": 1, "totalFeeInUsd": 4.18, "networkFeeInUsd": 0.28, "transactionFeeInUsd": 2.9, "partnerFeeInUsd": 1, "blockchainTransactionId": "0x553d295955a978ed3e9fc1717b5bcb903c69577e49c8ad255abece945ffa9ba0", "institution": null, "chainId": "1" } } } ``` ### Dashboard Verification: 1. Navigate to the **Transactions tab** 2. If your transaction isn't visible: * Click the **Status dropdown** * Select **"Select All"** * Your transaction should appear ✅ **White-Label API Integration Complete!** You can now build custom crypto experiences. ➡️ **[Build Custom UI Guide](/docs/stablecoins/white-label-api-integration/whitelabel-api-guide)** — complete implementation guide ## Next steps If you're ready to begin your integration, follow the end-to-end [White-Label API Guide](/docs/stablecoins/white-label-api-integration/whitelabel-api-guide). *** ## Troubleshooting ### Common Issues and Solutions: #### 🚫 401 Unauthorized * Ensure `BASIC` is added before your API key * Check for extra spaces or incorrect formatting #### 🚫 Webhook Not Received * Verify URL is publicly accessible * Check firewall settings * Ensure webhook endpoint returns 200 status #### 🚫 Transaction Not Visible * Change status filter to "Select All" in dashboard * Wait 30 seconds and refresh * Check if using correct environment (sandbox vs production) # Build with AI Source: https://docs.meld.io/docs/welcome/build-with-ai Meld's Stablecoins & Digital Assets docs are AI-friendly. Plug them into Claude, Cursor, ChatGPT, or any LLM tool to ship your crypto integration faster. The Meld Stablecoins documentation is published in an agent-ready format so coding assistants and AI agents can read, search, and reason over it directly — without copy-pasting pages or relying on stale training data. This page covers four ways to bring Meld docs into your AI workflow: Connect Claude, Cursor, and other tools to a live, searchable index of the docs. Drop-in context files that summarize or inline the entire docs site. Get any page as clean Markdown by appending `.md` to its URL. Machine-readable capability file for agents that act on Meld's APIs. All endpoints below serve the full Meld documentation, including the Stablecoins & Digital Assets, Meld API, and API reference sections. ## MCP server The Meld docs are exposed as a hosted [Model Context Protocol](https://modelcontextprotocol.io) server. Once connected, your AI tool can search the docs and read full pages on demand instead of guessing. **Server URL** ``` https://docs.meld.io/mcp ``` **Tools the server exposes** * `search_meld` — semantic search across every page; returns titles, snippets, and links. * `query_docs_filesystem_meld` — shell-style read access (`ls`, `tree`, `cat`, `head`, `rg`) over the docs as a virtual filesystem, including the OpenAPI specs. ### Add it to your tool Run from your project: ```bash theme={null} claude mcp add --transport http meld-docs https://docs.meld.io/mcp ``` Then in a session, ask: *"Use the meld-docs MCP to find the White-Label API quickstart and summarize the request payload."* Add to `claude_desktop_config.json`: ```json theme={null} { "mcpServers": { "meld-docs": { "type": "http", "url": "https://docs.meld.io/mcp" } } } ``` Restart Claude Desktop. The `meld-docs` tools will appear in the tool picker. Add to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` in your project: ```json theme={null} { "mcpServers": { "meld-docs": { "url": "https://docs.meld.io/mcp" } } } ``` Add to your workspace `.vscode/mcp.json`: ```json theme={null} { "servers": { "meld-docs": { "type": "http", "url": "https://docs.meld.io/mcp" } } } ``` Open a chat and attach the Meld docs MCP under **Connectors → Add connector** (available on Plus, Pro, Business, and Enterprise). Use: ``` https://docs.meld.io/mcp ``` No auth required. A discovery endpoint is published at `https://docs.meld.io/.well-known/mcp` so MCP-aware tools can find the server automatically. ## llms.txt The docs are also published as flat text files designed for direct LLM consumption — useful when you want to drop the whole site into a context window, a RAG pipeline, or a fine-tuning corpus. | File | Use it when | | -------------------------------------------------------------------------- | ------------------------------------------------------------ | | [`https://docs.meld.io/llms.txt`](https://docs.meld.io/llms.txt) | You want a structured index of every page with descriptions. | | [`https://docs.meld.io/llms-full.txt`](https://docs.meld.io/llms-full.txt) | You want the entire docs site inlined as one Markdown file. | **Example — load the full docs into a Claude API request:** ```python theme={null} import anthropic, urllib.request docs = urllib.request.urlopen("https://docs.meld.io/llms-full.txt").read().decode() client = anthropic.Anthropic() msg = client.messages.create( model="claude-opus-4-8", max_tokens=1024, system=[ {"type": "text", "text": "You are a Meld integration assistant."}, {"type": "text", "text": docs, "cache_control": {"type": "ephemeral"}}, ], messages=[{"role": "user", "content": "How do I create a crypto quote for a USD → USDC buy?"}], ) print(next((b.text for b in msg.content if b.type == "text"), "")) ``` Use prompt caching (as shown above) when you call repeatedly — `llms-full.txt` is large, and caching cuts cost and latency dramatically. ## Markdown export Any page on `docs.meld.io` can be fetched as raw Markdown — no HTML, no nav chrome — by appending `.md` to its URL. **Examples** ``` https://docs.meld.io/docs/stablecoins/crypto-overview_/quickstart.md https://docs.meld.io/docs/stablecoins/white-label-api-integration/whitelabel-quickstart.md https://docs.meld.io/api-reference/crypto/retail-ramp/crypto-quote-get.md ``` You can also: * Send `Accept: text/markdown` on any docs request to get the Markdown variant. * Press Cmd+C (Ctrl+C on Windows) on any page to copy it as Markdown. * Use the **Copy as Markdown** / **View as Markdown** options in the page's contextual menu. API reference pages include the full OpenAPI operation in the Markdown export, which is what most coding agents need to generate a correct request. ## skill.md For agents that *act* on Meld (not just read about it), the docs publish an [agentskills.io](https://agentskills.io/specification)–compatible capability file: ``` https://docs.meld.io/skill.md ``` It describes what an agent can accomplish with the Meld API, the inputs each capability needs, and the constraints that apply. MCP-connected agents discover it automatically as a resource on the server above; you can also add it manually: ```bash theme={null} npx skills add https://docs.meld.io ``` ## Recommended setup for crypto integrations Add `https://docs.meld.io/mcp` to your editor or chat tool. This is the single best upgrade — your assistant can now look up exact request shapes, status codes, and webhook payloads on demand. Bookmark these Markdown URLs and feed them to your agent at the start of a session: * `https://docs.meld.io/docs/stablecoins/crypto-overview_/index.md` — product overview and which integration to pick * `https://docs.meld.io/docs/stablecoins/crypto-overview_/quickstart.md` — first API call * `https://docs.meld.io/docs/stablecoins/for-all-products/webhook-events.md` — webhook event catalog When you want a clean-slate model (no MCP) to do something self-contained — a one-off script, a code review, a migration — paste `llms-full.txt` into context with caching enabled. The MCP filesystem includes the OpenAPI JSON for every product. Ask your agent to validate generated requests against `/openapi/crypto-20260203.json` before you ship. ## Questions or feedback Found a docs gap your agent stumbled on, or want a new endpoint covered? Reach out to your Meld contact — agent-readability is a first-class goal for this section of the docs. # Overview Source: https://docs.meld.io/docs/welcome/overview Welcome to Meld This page introduces what Meld does and the use cases it supports. It is for developers and product teams evaluating Meld, and by the end you should know whether Meld's Digital Assets or Bank Linking products fit your needs and where to go next. # Meld empowers you to 1. Buy, sell, or transfer digital assets, including stablecoins globally. 2. Have your users connect to their banks and pull their banking information.
## Common Digital Assets Use Cases 1. You are a wallet or fintech app that wants your users to be able to buy or sell crypto anywhere. 2. You are an employer who needs to pay out employees / contractors across the globe. 3. You are a marketplace that needs to collect payment globally using digital assets. 4. You currently support one or a few onramps but need many more for global coverage and better conversion.
## Common Bank Linking Use Cases 1. You offer your customers financial planning and need their transaction history and current balance. 2. You need your customers to verify their identity by connecting to their bank. 3. You need your customer's iban information (or account number / routing number) to facilitate global bank transfers.
## Terminology | Term | Meaning | | :------------- | :--------------- | | Onramps | Network Partners | | Digital Assets | Crypto | | Onramping | Buying crypto | | Offramp | Selling crypto | These terms are used consistently across all Meld documentation. If you see "onramp" or "network partner" in a guide, they refer to the same thing.
Next: View [Meld's products](/docs/welcome/see-all-products) to see which ones are right for you.
# Reference Source: https://docs.meld.io/docs/welcome/reference This page is a quick reference for environments, authentication, status codes, errors, security, and date formats. It is for developers who have already started integrating Meld and need to look up exact values while coding. If you have not started the integration process, you should skip this page and start with the [Overview](/docs/welcome/overview). # Meld Environments Meld offers both production and sandbox environments. The below table contains the URL for each environment: | Environment | API Base URL | | :---------- | :----------------------------------------------- | | Sandbox | [https://api-sb.meld.io](https://api-sb.meld.io) | | Production | [https://api.meld.io](https://api.meld.io) | Sandbox and Production use separate API keys and separate data. A key issued for one environment will not work against the other. Test transactions in Sandbox do not move real funds. To obtain your API key for either Production or Sandbox environments, work with your Meld contact. Your API Key is a secret, treat it as such. Do not share it or send it through a front end call. # Authentication Meld uses API keys to authenticate requests. These keys carry many privileges such as authorizing payments and accessing financial accounts data. It is important to keep them private and secure during both storage and transmission. Authentication is handled via HTTP headers, using the `Authorization` header. Example request: ```bash theme={null} curl --location --request \ GET 'https://api.meld.io/' \ --header 'Authorization: BASIC {{Your API Key}}' ``` Example successful response: ```json theme={null} { "id": "abc123", "status": "OK" } ``` "BASIC" Authorization When submitting your API key for authentication, you must specify "BASIC " before the key value pair. Note the trailing space between `BASIC` and your key. To help keep your API keys secure, follow these best practices: * Do not embed API keys directly in code, because they can be accidentally exposed to the public. Instead, store them in environment variables or in files outside of your application's source tree. * Do not store your API keys in files inside your application's source tree. If you must store API keys in files, keep the files outside your source tree to ensure your keys do not end up in your source code control system, especially if you use a public one such as GitHub. * Delete unneeded API keys to minimize exposure to attacks. * Review your code before publicly releasing it. Ensure that it does not contain API keys or any other private information before you make it publicly available. # API Status Codes The following table lists the status code you will receive from our APIs.
Status code Description
200 Successful, with response data as defined by the `Content-Type` header
201 Successful, with response data as defined by the `Content-Type` header
400 Bad Request
401 Unauthorized
403 Forbidden
404 No Resource Found
422 Input Validation Failed
425 Failure. TOO\_EARLY You might see this error when the same idempotent key is used twice and the first transaction is still being processed.
429 Too Many Requests
500 Unexpected Issue

# API Error Schema Any status code of `400` or higher returns an error payload. Inspect the `code` and `errors` fields to determine how to handle the failure, and surface `requestId` when contacting Meld support so we can trace the exact call. All errors are returned in the form of JSON and contain the following data: | Key | Description | | :---------- | :-------------------------------------------------------------------------------------------------------------------- | | `code` | A categorization of the error | | `message` | A developer-friendly representation of the error code. This may change over time and is not safe for programmatic use | | `errors` | A user-friendly representation of the error code. This may change over time and is not safe for programmatic use. | | `requestId` | The request Id | | `timestamp` | The date and time when the request was made | Below is a sample error response: ```json theme={null} { "code": "BAD_REQUEST", "message": "Bad request", "errors": [ "[amount] Must be a decimal value greater than zero" ], "requestId": "eb6aaa76bd7103cf6c5b090610c31913", "timestamp": "2022-01-19T20:32:30.784928Z" } ``` # Security 1. CORS -- Meld does not need to whitelist any of our customer's URL or IPs for them to call our public Production & Sandbox APIs. You can use whichever URL you desire, as we authenticate via your Meld API Key. 2. For security reasons, Meld recommends using your backend server to make the calls to Meld's API. If you make these calls from your frontend instead, it may not work and you may get back a CORS error. This is because making calls to Meld APIs requires that you pass in an `Authorization` header with the API Key we issued you. It is insecure to keep this API Key hardcoded in your mobile app or web app. 3. All our customers need to treat the Meld API Key they've been issued like any other password. It is an extremely sensitive credential that needs to be protected at all cost. The security measures you need to ensure are: a) strict controls to the backend server (as it has access to your Meld API Key), b) a way to authenticate your FE/app to your backend server, c) reject/ignore all other calls to your backend server. Never call Meld APIs directly from a browser, mobile app, or any other untrusted client. Calls must originate from your backend server so your API key is never exposed. # Dates All Meld dates and timestamps returned via Meld's API are in UTC time and formatted using ISO 8601 (for example, `2022-01-19T20:32:30.784928Z`). # See All Products Source: https://docs.meld.io/docs/welcome/see-all-products List of Meld Products This page lists every product Meld offers so you can pick the right integration path before you start building. It is for developers comparing flows, and by the end you should know which product (or combination) matches your use case and where its quickstart lives. You may choose to integrate more than one product. It is important to understand the distinction between the products before proceeding. 1. [Digital Assets: White Label API Flow](/docs/stablecoins/white-label-api-integration/index) 1. Enables your users to buy, sell, and transfer crypto using onramps. Users KYC and complete the transaction within the onramp's UI. Meld powers the flow and offers recommendation of which onramp is best for the user. You can initiate this flow either from Meld's UI (which you can embed in your app / site) or build your own UI on top of Meld's APIs. 2. Use this flow if you are a wallet or fintech app that wants your users to be able to buy or sell any digital asset, and **you want to build your own UI**. Examples of customers using this flow are Phantom Wallet, Uniswap Wallet, and Metamask Wallet. 3. Start building with the [White Label API flow](/docs/stablecoins/white-label-api-integration/whitelabel-quickstart). 2. [Digital Assets: Meld Checkout Flow](/docs/stablecoins/meld-checkout-integration/index) 1. Enables your users to buy, sell, and transfer crypto using onramps. 2. Receive your unique Meld url that you can embed / redirect to in your app. The entire flow will happen in the Meld UI. Setup happens in minutes. 3. Use this flow if you are a wallet or fintech app that wants your users to be able to buy or sell any digital asset, and **you want to use Meld's UI**. 4. Start building with the [Meld Checkout Ramps flow](/docs/stablecoins/meld-checkout-integration/meld-checkout-quickstart). 3. [Digital Assets: Virtual Account Flow](/docs/stablecoins/virtual-account-integration/index) 1. Enables you to have a white-label API driven flow to complete buying and selling crypto. The onramp will create a virtual bank account for you to send crypto to (for buying) and receive payment from (for selling). 2. Use this flow if **you want onramps to create a virtual bank account for you and each of your users**. This unlocks being able to purchase or sell large amounts of digital assets, especially stablecoin, globally. 3. Start building with the [Virtual Account flow](/docs/stablecoins/virtual-account-integration/virtual-account-quickstart). 4. [Bank Linking](/docs/bank-linking/bank-linking-quickstart/index) 1. Allows you to have your users connect their bank account and then pull any relevant information you need, such as transaction history, balance, and more. 2. Use this flow if **you need information about your customers' bank accounts**. 3. Start building with the [Bank Linking Quickstart](/docs/bank-linking/bank-linking-quickstart/index). Do not use the Bank Linking product if your business is digital asset related. Use one of the Digital Assets flows above instead. Not sure which flow to pick? Start with the [Overview](/docs/welcome/overview) to map your use case to a product, then come back here for the quickstart link.
# Create processor token Source: https://docs.meld.io/api-reference/bank-linking/accounts/bank-linking-accounts-create-processor-token /openapi/banklinking-20231219.json post /bank-linking/accounts/{financialAccountId}/processors/create-token The token is used by the processor to access the information on this account via a service provider. Depending on the service provider and/or processor, this token represents whatever token/code/key is needed to provide access. # Get a financial account Source: https://docs.meld.io/api-reference/bank-linking/accounts/bank-linking-accounts-get /openapi/banklinking-20231219.json get /bank-linking/accounts/{financialAccountId} Use this endpoint to retrieve account information of a specific financial account. # Search financial accounts Source: https://docs.meld.io/api-reference/bank-linking/accounts/bank-linking-accounts-search /openapi/banklinking-20231219.json get /bank-linking/accounts Use this endpoint to filter financial accounts by various parameters. # Create a connection Source: https://docs.meld.io/api-reference/bank-linking/connect/bank-linking-connect-create /openapi/banklinking-20231219.json post /bank-linking/connect/start Use this endpoint to obtain a connect token, which can be used to initialize the Meld widget and start a bank linking connection. # Repair a connection Source: https://docs.meld.io/api-reference/bank-linking/connect/bank-linking-connect-repair /openapi/banklinking-20231219.json post /bank-linking/connect/repair A Service Provider's connection to the institution may sometimes degrade or new financial account(s) may be discovered for the connection. In order to fix the connection or add these newly discovered account(s), the customer must reconnect. This endpoint generates a new connect token for the connection to enable this reconnection process. This connect token can be used to invoke a new bank linking flow where the user can enter the service provider's UI to fix the connection # Update a connection Source: https://docs.meld.io/api-reference/bank-linking/connections/bank-linking-connection-update /openapi/banklinking-20231219.json post /bank-linking/connections/{connectionId}/update Update a connection and its products. Adds new products retroactively to an existing connection and triggers a refresh with this updated set of products. The products provided must not already be present on the connection and be supported by the underlying service provider and institution in order to be successful. # Delete an institution connection Source: https://docs.meld.io/api-reference/bank-linking/connections/bank-linking-connections-delete /openapi/banklinking-20231219.json delete /bank-linking/connections/{connectionId} Use this endpoint to delete a connection and all of its financial accounts. This will also delete the connection with the service provider used to make the connection, thus stopping any updates for the connection and its financial accounts. Listen for the associated [BANK_LINKING_CONNECTION_DELETED](https://docs.meld.io/docs/webhook-events-bank-linking#bank_linking_connection_deleted) webhook for acknowledgement that the connection was deleted. *Note:* Financial accounts may belong to multiple connections. This can occur if a customer connects to their institution multiple times in separate sessions, either through the same or a different service provider. If a connection is deleted but its financial accounts still belong to another active connection, then they won't be deleted yet. Not until all the connections that a financial account is associated with are deleted, will the financial account then be deleted. *Note:* In some cases, connections will be deleted before ever calling this endpoint. The aforementioned webhook will notify when this occurs, and calling this endpoint for the connection will no longer be necessary. This can occur if a customer stops granting access to the service provider directly through their institution. *Note:* Duplicate provider connections occur when the same customer connects to the same institution using the same login credentials through the *same* service provider. In such cases, only the most recently refreshed of these duplicates is actively maintained and the rest are considered inactive. When deleting duplicate connections, all of the inactive duplicates must be deleted prior to deleting the main duplicate. If an attempt is made to delete the active duplicate prior to the inactive duplicates being deleted, then the next most recently aggregated duplicate will become the active one. Only once all duplicates are deleted will this sever the connection with the service provider and cease billing. Duplicate connections made through *different* service providers do not have this restriction, however. # Get an institution connection Source: https://docs.meld.io/api-reference/bank-linking/connections/bank-linking-connections-get /openapi/banklinking-20231219.json get /bank-linking/connections/{connectionId} Retrieve details of a specific institution connection. # Import existing connections with a service provider Source: https://docs.meld.io/api-reference/bank-linking/connections/bank-linking-connections-import /openapi/banklinking-20231219.json post /bank-linking/connections/import Importing is done asynchronously. Webhooks will be sent for each connection as they are imported, the same as if the connection was added via the connect widget. # Refresh accounts belonging to a connection Source: https://docs.meld.io/api-reference/bank-linking/connections/bank-linking-connections-refresh /openapi/banklinking-20231219.json post /bank-linking/connections/{connectionId}/refresh Trigger a refresh of all financial accounts for a given institution connection with the latest information from the service provider. This is a premium service for most service providers and should be used sparingly for requesting up-to-date data as of the time of the call to this endpoint. Transaction and balance data is updated daily by service providers, and Meld's webhooks will notify of these updates so that product data does not become stale. *Note:* By default, historical transactions are loaded upon initial connection for all providers. This is a premium service for Finicity and MX, so if this is not desired please reach out to Meld support to disable this feature. After doing so, then historical transactions will only be loaded after the first time a refresh is requested for a Finicity/MX connection that has transactions as an available product. *Note:* Duplicate provider connections occur when the same customer connects to the same institution using the same login credentials through the *same* service provider. In such cases, only the most recently refreshed of these duplicates is actively maintained and the rest are considered inactive. Choosing to refresh an inactive duplicate will cause it to become the new active duplicate and it will assume the same connection status as the previous active duplicate. The previous active duplicate will now be considered inactive. # Search institution connections Source: https://docs.meld.io/api-reference/bank-linking/connections/bank-linking-connections-search /openapi/banklinking-20231219.json get /bank-linking/connections Retrieve institution connections filtered by various parameters. # Get institution Source: https://docs.meld.io/api-reference/bank-linking/institutions/bank-linking-institutions-get /openapi/banklinking-20231219.json get /institutions/{institutionId} This endpoint allows you to get an institution by id, as well as the service provider metadata such as name, URL, and logos for each service provider configured to your account that support this institution # Search institutions Source: https://docs.meld.io/api-reference/bank-linking/institutions/bank-linking-institutions-search /openapi/banklinking-20231219.json get /institutions Allows you to search across the list of Institutions supported by your service providers. This endpoint is only available in production. # Get a financial account investment holding Source: https://docs.meld.io/api-reference/bank-linking/investments/bank-linking-investment-holdings-get /openapi/banklinking-20231219.json get /bank-linking/investments/holdings/{investmentHoldingId} Use this endpoint to retrieve investment holding information of a specific financial account investment holding. # Search financial account investment holdings Source: https://docs.meld.io/api-reference/bank-linking/investments/bank-linking-investment-holdings-search /openapi/banklinking-20231219.json get /bank-linking/investments/holdings Use this endpoint to filter financial account investment holdings by various parameters. # Get a financial account investment transaction Source: https://docs.meld.io/api-reference/bank-linking/investments/bank-linking-investment-transactions-get /openapi/banklinking-20231219.json get /bank-linking/investments/transactions/{investmentTransactionId} Use this endpoint to retrieve investment transaction information of a specific financial account investment transaction. # Search financial account investment transactions Source: https://docs.meld.io/api-reference/bank-linking/investments/bank-linking-investment-transactions-search /openapi/banklinking-20231219.json get /bank-linking/investments/transactions Use this endpoint to filter financial account investment transactions by various parameters. # Get a financial account transaction Source: https://docs.meld.io/api-reference/bank-linking/transactions/bank-linking-transactions-get /openapi/banklinking-20231219.json get /bank-linking/transactions/{transactionId} Use this endpoint to retrieve transaction information of a specific financial account transaction. # Search financial account transactions Source: https://docs.meld.io/api-reference/bank-linking/transactions/bank-linking-transactions-search /openapi/banklinking-20231219.json get /bank-linking/transactions Use this endpoint to filter financial account transactions by various parameters. # Create a crypto quote Source: https://docs.meld.io/api-reference/crypto/retail-ramp/crypto-quote-get /openapi/crypto-20250304.json post /payments/crypto/quote Use this endpoint to request the current exchange rate of the selected fiat currency-cryptocurrency pair, and the required fees. Enter a fiat currency as the sourceCurrencyCode to buy crypto and enter a crypto currency in that field to sell crypto. # Create a crypto widget Source: https://docs.meld.io/api-reference/crypto/retail-ramp/crypto-session-widget-create /openapi/crypto-20231219.json post /crypto/session/widget Use this endpoint to create a crypto widget for a session to buy or sell crypto. # Get transaction by id Source: https://docs.meld.io/api-reference/crypto/retail-ramp/payments-transactions-get /openapi/crypto-20231219.json get /payments/transactions/{id} Search transaction by its Meld identifier # Search transactions Source: https://docs.meld.io/api-reference/crypto/retail-ramp/payments-transactions-search /openapi/crypto-20231219.json get /payments/transactions Search a list of transactions you've previously created. The transactions are ordered by the date of creation. # Fetch a transaction from the provider (for offramp use) Source: https://docs.meld.io/api-reference/crypto/retail-ramp/payments-transactions-sessions-get /openapi/crypto-20231219.json get /payments/transactions/sessions/{sessionId} Fetch a transaction from the service provider using the session ID. This endpoint is primarily used for offramp scenarios where the transaction needs to be retrieved from the provider after the user completes their action in the widget. # Create a crypto virtual account ramp quote Source: https://docs.meld.io/api-reference/crypto/virtual-account/crypto-virtual-account-quote-get /openapi/crypto-20231219.json post /payments/virtual-account/quote Use this endpoint to request the current exchange rate of the selected fiat currency-cryptocurrency pair, and the required fees. Enter a fiat currency as the sourceCurrencyCode to buy crypto and enter a crypto currency in that field to sell crypto. # Create a virtual account offramp order Source: https://docs.meld.io/api-reference/crypto/virtual-account/payments-virtual-account-ramp-offramp-order-create /openapi/crypto-20231219.json post /payments/virtual-account/offramp-order Create a virtual account offramp order to initiate crypto to fiat transfer # Create a virtual account onramp order Source: https://docs.meld.io/api-reference/crypto/virtual-account/payments-virtual-account-ramp-onramp-order-create /openapi/crypto-20231219.json post /payments/virtual-account/onramp-order Create a virtual account onramp order to receive bank details for fiat transfer # Add an address to a customer Source: https://docs.meld.io/api-reference/customer/customers/accounts-customers-addresses-create /openapi/customer-20231219.json post /accounts/customers/{customerId}/addresses # Create a new customer or retrieve a customer by its external id Source: https://docs.meld.io/api-reference/customer/customers/accounts-customers-create /openapi/customer-20231219.json post /accounts/customers # Delete a customer Source: https://docs.meld.io/api-reference/customer/customers/accounts-customers-delete /openapi/customer-20231219.json delete /accounts/customers/{customerId} Use this endpoint to delete a customer with Meld and/or with any of the service providers it is linked to. This endpoint should only be used for customers using the Bank-Linking stack, as it is not yet compatible with the Payments or Crypto stack. If a customer is deleted that is linked to a Payments or Crypto service provider customer, it will still be deleted with Meld, but not with the provider. ***Note:*** For customers using the Bank-Linking stack, this will delete all of the customer's connections and financial accounts as well. # Initiate customer KYC Source: https://docs.meld.io/api-reference/customer/customers/accounts-customers-kyc-initiate /openapi/customer-20231219.json post /accounts/customers/{customerId}/kyc/initiate Retrieve a url that when launched displays a UI that commences KYC for your user # Search customers Source: https://docs.meld.io/api-reference/customer/customers/accounts-customers-search /openapi/customer-20231219.json get /accounts/customers Returns a list of customers that match the query parameters. The customers are sorted by external id. # Update a customer Source: https://docs.meld.io/api-reference/customer/customers/accounts-customers-update /openapi/customer-20231219.json patch /accounts/customers/{customerId} # List supported routes Source: https://docs.meld.io/api-reference/network-partners/supported/find-supported-routes /openapi/networkpartner-20260203.json get /network-partner/supported/routes/{category}/{countryCode}/{sourceCurrencyCode}/{destinationCurrencyCode} Searches supported partner routes by category, country, optional subdivision, source currency, destination currency, and payment method or payment type. When both `paymentMethod` and `paymentType` are provided, `paymentMethod` takes precedence. Provider scope rules: - No Basic Auth and no `Meld-Account-Id`: results come from the global dataset. - Basic Auth only: results are restricted to the authenticated identity's `providers` claim. - `Meld-Account-Id` only: results are restricted to providers configured for that account. - Both Basic Auth and `Meld-Account-Id`: results are restricted to the intersection of both provider sets. - Explicit partner filters are intersected with the resolved provider scope. Explicit `partners` filtering is intersected with the resolved provider scope. If the effective provider scope contains no providers, this collection endpoint returns `200` with an empty result. # List supported countries Source: https://docs.meld.io/api-reference/network-partners/supported/list-supported-countries /openapi/networkpartner-20260203.json get /network-partner/supported/countries Returns countries supported across live providers with flag URLs and supported subdivision codes, optionally filtered by category and partners. Provider scope rules: - No Basic Auth and no `Meld-Account-Id`: results come from the global dataset. - Basic Auth only: results are restricted to the authenticated identity's `providers` claim. - `Meld-Account-Id` only: results are restricted to providers configured for that account. - Both Basic Auth and `Meld-Account-Id`: results are restricted to the intersection of both provider sets. - Explicit partner filters are intersected with the resolved provider scope. Explicit `partners` filtering is intersected with the resolved provider scope. If the effective provider scope contains no providers, this collection endpoint returns `200` with an empty result. # Search supported provider fiat limits Source: https://docs.meld.io/api-reference/network-partners/supported/list-supported-fiat-limits /openapi/networkpartner-20260203.json get /network-partner/supported/fiat-limits Searches provider-supplied fiat amount limits across live providers. Returns only loaded provider fiat limit rows; default currency fallback limits are not included. When both `paymentMethod` and `paymentType` are provided, `paymentMethod` takes precedence. Provider scope rules: - No Basic Auth and no `Meld-Account-Id`: results come from the global dataset. - Basic Auth only: results are restricted to the authenticated identity's `providers` claim. - `Meld-Account-Id` only: results are restricted to providers configured for that account. - Both Basic Auth and `Meld-Account-Id`: results are restricted to the intersection of both provider sets. - Explicit partner filters are intersected with the resolved provider scope. Explicit `partners` filtering is intersected with the resolved provider scope. If the effective provider scope contains no providers, this collection endpoint returns `200` with an empty result. # Search supported provider KYC limits Source: https://docs.meld.io/api-reference/network-partners/supported/list-supported-kyc-limits /openapi/networkpartner-20260203.json get /network-partner/supported/kyc-limits Searches admin-curated provider KYC limits across live providers. Provider scope rules: - No Basic Auth and no `Meld-Account-Id`: results come from the global dataset. - Basic Auth only: results are restricted to the authenticated identity's `providers` claim. - `Meld-Account-Id` only: results are restricted to providers configured for that account. - Both Basic Auth and `Meld-Account-Id`: results are restricted to the intersection of both provider sets. - Explicit partner filters are intersected with the resolved provider scope. Explicit `partners` filtering is intersected with the resolved provider scope. If the effective provider scope contains no providers, this collection endpoint returns `200` with an empty result. # List live partners supporting a route Source: https://docs.meld.io/api-reference/network-partners/supported/list-supported-partners /openapi/networkpartner-20260203.json get /network-partner/supported/partners Searches live partners that support a route defined by category, country, optional subdivision, fiat currency, crypto currency, and payment method or payment type. Provider scope rules: - No Basic Auth and no `Meld-Account-Id`: results come from the global dataset. - Basic Auth only: results are restricted to the authenticated identity's `providers` claim. - `Meld-Account-Id` only: results are restricted to providers configured for that account. - Both Basic Auth and `Meld-Account-Id`: results are restricted to the intersection of both provider sets. - Explicit partner filters are intersected with the resolved provider scope. Explicit `partner` filtering is intersected with the resolved provider scope. If the effective provider scope contains no providers, this collection endpoint returns `200` with an empty result. # Search supported payment methods Source: https://docs.meld.io/api-reference/network-partners/supported/list-supported-payment-methods /openapi/networkpartner-20260203.json get /network-partner/supported/payment-methods Searches payment methods supported across providers. Results are deduplicated across providers and returned as canonical payment-method entries with logo URLs for the requested filters. Provider scope rules: - No Basic Auth and no `Meld-Account-Id`: results come from the global dataset. - Basic Auth only: results are restricted to the authenticated identity's `providers` claim. - `Meld-Account-Id` only: results are restricted to providers configured for that account. - Both Basic Auth and `Meld-Account-Id`: results are restricted to the intersection of both provider sets. - Explicit partner filters are intersected with the resolved provider scope. Explicit `partners` filtering is intersected with the resolved provider scope. If the effective provider scope contains no providers, this collection endpoint returns `200` with an empty result. # Search supported currencies Source: https://docs.meld.io/api-reference/network-partners/supported/search-supported-currencies /openapi/networkpartner-20260203.json get /network-partner/supported/currencies Searches currencies supported across providers and returns canonical currency metadata including symbol image URLs. Defaults to `type=CRYPTO`; use `type=FIAT` to search fiat support. `network` is only valid for crypto searches. `providerCurrencyCode` can filter both crypto and fiat provider-specific codes. Provider scope rules: - No Basic Auth and no `Meld-Account-Id`: results come from the global dataset. - Basic Auth only: results are restricted to the authenticated identity's `providers` claim. - `Meld-Account-Id` only: results are restricted to providers configured for that account. - Both Basic Auth and `Meld-Account-Id`: results are restricted to the intersection of both provider sets. - Explicit partner filters are intersected with the resolved provider scope. Explicit `partner` filtering is intersected with the resolved provider scope. If the effective provider scope contains no providers, this collection endpoint returns `200` with an empty result. # Import an external payment transaction Source: https://docs.meld.io/api-reference/payments/external-transactions/payments-external-transactions-import /openapi/payments-20231219.json post /payments/external/import This is the endpoint to import a transaction on any of the supported providers. Use this endpoint if you have performed transactions on one or more providers outside of Meld, and want to import those transactions into Meld's system. Note that when performing an [external refund](https://docs.meld.io/docs/crypto-supported-service-providers-assets), the initial transaction is automatically imported into Meld's system. # Refund an external payment transaction Source: https://docs.meld.io/api-reference/payments/external-transactions/payments-external-transactions-refund /openapi/payments-20231219.json post /payments/external/refund This is the endpoint to refund an external transaction on any of the supported providers. Use this endpoint if you have created a transaction with any of these providers outside of Meld's system, but want to perform the refund using Meld. Note that this will also cause the original transaction to be imported into Meld's system. Both partial and full refunds are supported. # Create a risk analysis Source: https://docs.meld.io/api-reference/payments/risk/payments-transactions-risk-analysis /openapi/payments-20231219.json post /payments/risk-analysis Perform an ACH risk analysis and create associated records # Create a Meld payment token Source: https://docs.meld.io/api-reference/payments/transactions/payment-token-post /openapi/payments-20231219.json post /payments/tokens Use this endpoint to create long-lasting Meld payment tokens that can be used across service providers. These tokens can replace the payment method provided for future transactions. [For more details see this page](https://docs.meld.io/docs/payment-methods-and-tokenization). # Capture a payment transaction Source: https://docs.meld.io/api-reference/payments/transactions/payments-transactions-capture /openapi/payments-20231219.json post /payments/transactions/{id}/capture See [here](https://docs.meld.io/docs/authorization-capture) to learn more about authorization and capture. # Create a payment transaction Source: https://docs.meld.io/api-reference/payments/transactions/payments-transactions-create /openapi/payments-20231219.json post /payments/transactions Use this endpoint to create a payment transaction. You can find sample code in our public Postman collection, or build it in the adjacent API explorer. The link to our postman collection is: [https://www.postman.com/meldeng/workspace/meld-io-public-api-collection](https://www.postman.com/meldeng/workspace/meld-io-public-api-collection) **IDEMPOTENT REQUESTS**: Please refer to [Idempotency Key](https://docs.meld.io/docs/idempotency-key) **PASSTHROUGH ENABLED REQUESTS**: Please refer to [Passthrough Headers](https://docs.meld.io/docs/credential-passthrough) **ERRORS**: Please refer to [Error Responses](https://docs.meld.io/docs/fiat-error-responses) # Get transaction by id Source: https://docs.meld.io/api-reference/payments/transactions/payments-transactions-get /openapi/payments-20231219.json get /payments/transactions/{id} Search transaction by its Meld identifier # Refund a payment transaction Source: https://docs.meld.io/api-reference/payments/transactions/payments-transactions-refund /openapi/payments-20231219.json post /payments/transactions/{id}/refund This is the endpoint to refund a transaction on any of the supported providers. Full as well as partial refunds are supported, so it is not mandatory to inform the total value of the original transaction in the case of a full refund. Note: While the parameters within the body are optional, you must at least pass in a valid JSON body, e.g. { }. # Search transactions Source: https://docs.meld.io/api-reference/payments/transactions/payments-transactions-search /openapi/payments-20231219.json get /payments/transactions Search a list of transactions you've previously created. The transactions are ordered by the date of creation. # Create a payout transaction Source: https://docs.meld.io/api-reference/payments/transactions/payouts-transactions-create /openapi/payments-20231219.json post /payments/transactions/payout Use this endpoint to create a payout transaction. You can find sample code in our public Postman collection, or build it in the adjacent API explorer. The link to our postman collection is: [https://www.postman.com/meldeng/workspace/meld-io-public-api-collection](https://www.postman.com/meldeng/workspace/meld-io-public-api-collection) **IDEMPOTENT REQUESTS**: Please refer to [Idempotency Key](https://docs.meld.io/docs/idempotency-key) **ERRORS**: Please refer to [Error Responses](https://docs.meld.io/docs/fiat-error-responses) # Get crypto sell limits for crypto currencies Source: https://docs.meld.io/api-reference/service-providers/service-provider/service-providers-limits-crypto-currency-sells-get /openapi/serviceproviders-20231219.json get /service-providers/limits/crypto-currency-sells Returns a list of limits (minimums and maximums) in terms of crypto tokens for selling crypto. # Search countries Source: https://docs.meld.io/api-reference/service-providers/service-provider/service-providers-properties-country-search /openapi/serviceproviders-20231219.json get /service-providers/properties/countries Returns a list of properties which meet the search criteria. # Search crypto currencies Source: https://docs.meld.io/api-reference/service-providers/service-provider/service-providers-properties-crypto-currency-search /openapi/serviceproviders-20231219.json get /service-providers/properties/crypto-currencies Returns a list of properties which meet the search criteria. # Search defaults by country Source: https://docs.meld.io/api-reference/service-providers/service-provider/service-providers-properties-defaults-by-country-search /openapi/serviceproviders-20231219.json get /service-providers/properties/defaults/by-country Returns a list of default fiat currencies and payment methods per country. # Search fiat currencies Source: https://docs.meld.io/api-reference/service-providers/service-provider/service-providers-properties-fiat-currency-search /openapi/serviceproviders-20231219.json get /service-providers/properties/fiat-currencies Returns a list of properties which meet the search criteria. # Search payment methods Source: https://docs.meld.io/api-reference/service-providers/service-provider/service-providers-properties-payment-method-search /openapi/serviceproviders-20231219.json get /service-providers/properties/payment-methods Returns a list of properties which meet the search criteria. # Search service providers Source: https://docs.meld.io/api-reference/service-providers/service-provider/service-providers-search /openapi/serviceproviders-20231219.json get /service-providers Returns a list of service providers which meet the search criteria. # FAQ Source: https://docs.meld.io/docs/bank-linking/bank-linking-faq Answers to the most common questions developers ask while integrating Meld Bank Linking. If you don't see your question here, check the [Activity Log](/docs/bank-linking/testing-and-debugging/debugging-activity-log) for connection-level detail or reach out to Meld support. Follow the [Creating Connections](/docs/bank-linking/creating-connections) guide for end-to-end instructions on making a connection. There are several likely candidates: * The provider doesn't support the institution for all of the products specified. * Your account with that provider may not be configured for a particular product or for OAuth institutions. * If you are using Smart Routing, Meld may know that the connection between that provider and institution is currently unstable and is therefore routing you to a different provider. Assuming the institution is supported by one of the providers you have enabled, the likely issue is that the institution doesn't support all of the products you requested when calling [/connect/start](/api-reference/bank-linking/connect/bank-linking-connect-create). Meld filters the institutions returned in the picker to only the ones that support all products requested. To check if this is the issue, try making a request for only `BALANCES` and `TRANSACTIONS` and see if your institution shows up. The most likely reason is your connection is in a status other than `ACTIVE`. Check the status of your connection — it might be `RECONNECT_REQUIRED` (the user must log in again in the repair flow) or `PARTIALLY_ACTIVE` (for example, because it's a duplicate). See [Connection Statuses and Errors](/docs/bank-linking/get-connection-data/connection-statuses-and-errors) for the full list. Plaid forces the user to select their institution in the Plaid picker even if it was already selected in the Meld picker. No other provider does this. The best Meld can do is have the institution you picked in the Meld picker be the first one in the list of the Plaid picker, which it does. Yes. Pass `"accountPreferenceOverride": {"selectionStyle": "TILE"}` as part of your [/connect/start request](/docs/bank-linking/creating-connections/create-a-connect-token). No. You can skip the Meld picker by passing in the ID of the institution the user will connect to. See [Create a Connect Token](/docs/bank-linking/creating-connections/create-a-connect-token) for how to prepopulate the picker. # Importing Connections Source: https://docs.meld.io/docs/bank-linking/manage-connection-status/importing-connections If you already have connections with a service provider — for example, because you previously integrated directly with Plaid or Akoya — you can bring them into Meld. Importing a connection does not involve the customer; there is no widget flow and no re-authentication required. This page is for developers migrating from a direct provider integration to Meld. ## Before you begin * You have provider-level credentials (Plaid `access_token`, Akoya equivalent) for each connection you want to import * The corresponding service provider is enabled on your Meld account * You have decided what `externalCustomerId` to associate with each imported connection **Importing is currently supported for Plaid and Akoya only.** For other providers, the customer must complete the standard widget flow. ## How to import a connection Call the [import connections endpoint](/api-reference/bank-linking). Use the `externalCustomerId` field to tie each imported connection to an id of your choice for tracking. Importing a connection does the following: 1. Adds the connection and all its financial accounts to Meld. 2. Ties the connection to your `externalCustomerId`, if provided. 3. Tells the underlying provider to send webhooks for that connection to Meld going forward (instead of to your servers). 4. Refreshes the connection at the time of import. 5. Sends you the standard aggregation webhooks (`BANK_LINKING_ACCOUNTS_UPDATING`, `BANK_LINKING_ACCOUNTS_UPDATED`, `BANK_LINKING_TRANSACTIONS_AGGREGATED`) just like a fresh connection. Once a connection is imported, the provider will stop sending webhooks to your old endpoint. Make sure your Meld webhook profile is configured before importing in production. For the full request and response schema, refer to the [Bank Linking API reference](/api-reference/bank-linking). ## Common errors | Symptom | Likely cause | Developer action | | :------------------------------------------ | :---------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------- | | `400 Bad Request` — provider not supported | Importing only works for Plaid and Akoya | Use the standard widget flow for other providers. | | `401` or `403` from the underlying provider | Provider-level credentials supplied are invalid or expired | Verify the `access_token` is valid for the environment; re-issue if needed. | | Import succeeds but no webhooks arrive | Meld webhook profile not configured for bank-linking events | Configure the webhook profile per [Webhook Quickstart](/docs/bank-linking/onboarding/step-5-setting-up-webhooks/webhook-quickstart). | # Finicity Balances Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/finicity/finicity-balances-mapping This data comes from Finicity's [/aggregation/v1/customers /\{customerId}/institutionLogins /\{institutionLoginId}/accounts/](https://api-reference.finicity.com/#/rest/api-endpoints/accounts/get-customer-accounts-by-institution-login) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Meld uses the cached balance [GetCustomerAccountsByInstitutionsLogin](https://developer.mastercard.com/open-banking-us/documentation/api-reference/?view=api#GetCustomerAccountsByInstitutionLogin) the vast majority of the time (for daily refreshes). If you force refresh just balances, Meld calls [GetAvailableBalanceLive](https://developer.mastercard.com/open-banking-us/documentation/api-reference/?view=api#GetAvailableBalanceLive) (because it's quicker and cheaper), but if you refresh balances and transactions Meld calls [RefreshCustomerAccountsByInstitutionLogin](https://developer.mastercard.com/open-banking-us/documentation/api-reference/?view=api#RefreshCustomerAccountsByInstitutionLogin). Example values: `currency: "USD"`, `currentAmount: 1543.22`, `availableAmount: 1487.10`, `updatedAt: "2024-08-12T14:33:00Z"`.
Meld Description Finicity Response Field
currency The currency of the account balance currency
currentAmount The current amount in the account balance
availableAmount The available amount in the account Assigned in the following order if non-null: detail.availableBalanceAmount\ detail.availableCashBalance\ balance
updatedAt The last time balances were updated balanceDate
# Finicity Identifiers Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/finicity/finicity-identifiers-mapping This data comes from Finicity's [/aggregation/v1/customers /`{customerId}`/accounts /`{accountId}`/details](https://api-reference.finicity.com/#/rest/api-endpoints/payments/get-account-ach-details) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example values: `ach.accountNumber: "1234567890"`, `ach.routingNumber: "021000021"`. Finicity returns ACH identifiers only. `eft.*`, `international.iban`, `international.bic`, and `bacs.*` fields are not populated. | Meld | Description | Finicity Response Field | | :------------------- | :--------------------------------------------------------------------------------------------------- | :---------------------- | | ach | ACH data object | | |    accountNumber | The account number | realAccountNumber | |    routingNumber | The account routing number. Reference [Routing number](https://en.wikipedia.org/wiki/Routing_number) | routingNumber | | eft | EFT data object | | |    accountNumber | The account number | *None* | |    institutionNumber | The account institution number | *None* | |    branchNumber | The institution branch number | *None* | | international | International data object | | |    iban | The International Bank Account Number (IBAN) for the financial account | *None* | |    bic | The Bank Identifier Code (BIC) for the financial account | *None* | | bacs | The BACS data object | | |    accountNumber | BACS account number | *None* | |    sortCode | BACS sort code | *None* |
# Finicity Investment Holdings Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/finicity/finicity-investment-holdings-mapping This data comes from Finicity's [/aggregation/v1/customers/\{customerId}/institutionLogins/\{institutionLoginId}/accounts](https://developer.mastercard.com/open-banking-us/documentation/api-reference/?view=api#GetCustomerAccountsByInstitutionLogin) endpoint and is returned from Meld's [/bank-linking/investments/holdings](/api-reference/bank-linking/investments/bank-linking-investment-holdings-search) endpoint. Example values: `symbol: "AAPL"`, `quantity: 10`, `currentValue: 1820.50`, `costBasis: 150.25`, `currencyCode: "USD"`, `isin: "US0378331005"`, `cusip: "037833100"`, `type: "STOCK"`. Finicity does not provide `description` or `closePrice` for investment holdings. | Meld Field | Description | Finicity Response Field | | :----------- | :------------------------------------------------------------------------- | :---------------------- | | symbol | The symbol of the security | symbol | | quantity | The number of shares of the security | units | | currentValue | The total current value of the holding | marketValue | | costBasis | The purchase price of the holding, per share | cost\_basis | | currencyCode | The ISO currency code that was used to purchase the holding | securityCurrency | | updatedAt | The last time the details of this holding were updated | currentPriceDate | | description | A description of the holding | **Not Available** | | closePrice | The price of the security at last market close | **Not Available** | | isin | The global ISO number for an individual security. | Securities.isin | | cusip | A shortened version of the isin used for North American securities. | cusipNo | | type | The type of holding (ex: stock, etf). This is normalized across providers. | securityType | # Finicity Investment Transactions Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/finicity/finicity-investment-transactions-mapping This data comes from Finicity's [/investments/transactions/get](https://api-reference.finicity.com/#/rest/models/structures/transactions) endpoint and is returned from Meld's [/bank-linking/investments/transactions](/api-reference/bank-linking/investments/bank-linking-investment-transactions-search) endpoint. Example values: `symbol: "AAPL"`, `quantity: 5`, `amount: -912.50`, `currency: "USD"`, `description: "BUY AAPL"`, `status: "POSTED"`, `transactionDate: "2024-08-09"`, `postedDate: "2024-08-10"`, `type: "BUY"`. Finicity does not provide `cashBalance` for investment transactions.
Meld Field Description Finicity Response Field
symbol The symbol of the security symbol
quantity The number of shares of the security unitQuantity
costBasis The purchase price of the holding, per share costBasis
cashBalance The cash balance of the account after the transaction **Not Available**
accountId The Id of the financial account accountId
amount The total currency involved in the transaction amount
currency The ISO currency code that was used to purchase the holding currencySymbol
description A description of the transaction description
status The status of the transaction status\
  - If returns `Active`, Meld will populate the value as `POSTED`
  - If returns `Pending`, Meld will populate the value as `PENDING`
  - If returns `Shadow`, Meld will populate the value as `PENDING`\ **Note:**\ Some institutions continue to modify or delete investment transactions long after they are first posted to the institution’s data feed. This practice can cause Finicity transactions to appear as duplicates, or to continue to appear in the Finicity data after they have disappeared from the institution’s current website.\ Finicity has added the ability to identify transactions that were found in an earlier aggregation of an account, but are not found in the institution’s current data source. These `SHADOW` transactions are identified in the `transaction record`.
transactionDate The date the transaction was initiated transactionDate
postedDate The date the transaction was finalized postedDate
type The type of investment transaction investmentTransactionType
# Finicity Owners Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/finicity/finicity-owners-mapping This data comes from Finicity's [/aggregation/v1/customers /`{customerId}`/accounts /`{accountId}`/owner](https://api-reference.finicity.com/#/rest/api-endpoints/account-owner/get-account-owner) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example values: `names: ["Jane Doe"]`, `addresses.data.full: "123 Main St, Springfield, IL 62701"`. Finicity returns owner address as a single unparsed string in `ownerAddress`, mapped to `addresses.data.full`. Parsed sub-fields (`street`, `city`, `region`, `postalCode`, `country`) are not provided. Email and phone number data are also not provided.
Meld Field Description Finicity Response Field
addresses The address(es) associated with this owner
   data The address data object
      street The street and residence number *None*
      city The city *None*
      region The region/state *None*
      postalCode The postal or zip code *None*
      country The ISO 3166-1 alpha-2 country code *None*
      full The full unparsed address ownerAddress
   primary Indicates if this is the owner's primary residence Does not provide.
Meld will\ default\ to`Primary.UNKNOWN`
emails The email(s) associated with this owner
   data The email address *None*
   primary Indicates if this is the owner's primary email *None*
names The name(s) of this owner ownerName
phoneNumbers The phone number(s) associated with this owner
   data The phone number *None*
   primary Indicates if this is the owner's primary phone number *None*
# Finicity to Meld Overall Mappings Source: https://docs.meld.io/docs/bank-linking/partner-details/finicity/finicity-to-meld-mappings You can always compare the raw responses from Finicity in the `serviceProviderDetails` section of a financial account. [Financial Account Base Fields](#financial-account-base-fields) [Balances](./finicity-balances-mapping) [Identifiers](./finicity-identifiers-mapping) [Owners](./finicity-owners-mapping) [Transactions](./finicity-transactions-mapping) [Investment Holdings](./finicity-investment-holdings-mapping) [Investment Transactions](./finicity-investment-transactions-mapping) [Normalized Account Types and Subtypes](#normalized-account-types-and-subtypes) ## Financial Account Base Fields This data comes from Finicity's [/aggregation/v1/customers /`{customerId}`/institutionLogins /`{institutionLoginId}`/accounts/](https://api-reference.finicity.com/#/rest/api-endpoints/accounts/get-customer-accounts-by-institution-login) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint.
Meld Description Finicity Response Field
name The account name name
truncated\ AccountNumber The last 4 digits of the account number realAccountNumberLast4 * If null:\_ accountNumberDisplay
status The real-time status of the account status
Possible values:
- `PENDING`
- `ACTIVE`
type The type of the account. Mapped to a Meld standardized type type
subtype The subtype of the account. Mapped to a Meld standardized subtype based on the type * None\_. Meld subtype\ determined from Finicity type.
## Normalized Account Types and Subtypes # Finicity Transactions Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/finicity/finicity-transactions-mapping This data comes from Finicity's [/aggregation/v3/customers /`{customerId}`/accounts /`{accountId}`/transactions](https://api-reference.finicity.com/#/rest/api-endpoints/transactions/get-customer-account-transactions) endpoint and is returned from Meld's [/bank-linking/transactions](/api-reference/bank-linking/transactions/bank-linking-transactions-search) endpoint. To fetch transaction data, Meld calls [GetCustomerAccountTransactions](https://developer.mastercard.com/open-banking-us/documentation/api-reference/?view=api#GetCustomerAccountTransactions) the vast majority of the time, Meld only uses the paid endpoint [LoadHistoricTransactionsForCustomerAccount](https://developer.mastercard.com/open-banking-us/documentation/api-reference/?view=api#LoadHistoricTransactionsForCustomerAccount) for historical transactions (1 time per connection on the initial load). Forced refreshes are done via [RefreshCustomerAccountsByInstitutionLogin](https://developer.mastercard.com/open-banking-us/documentation/api-reference/?view=api#RefreshCustomerAccountsByInstitutionLogin). Finicity's transaction signs (positive and negative) are inverted when mapped to make the behavior consistent with other providers. You can find more information on signage [here](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-transactions#data-normalization). Example values: `amount: -42.50`, `currency: "USD"`, `description: "STARBUCKS #1234"`, `status: "POSTED"`, `transactionDate: "2024-08-10"`, `postedDate: "2024-08-11"`. Finicity may continue to surface transactions as `SHADOW` after the institution has removed them from its current data feed. Treat `SHADOW` transactions accordingly when reconciling.
Meld Description Finicity Response Field
amount The amount of the transaction amount
currency The currency used in the transaction currencySymbol * If null:\_ Meld will default\ to `USD`
description The transaction's description description
status The status of the transaction status
  - If returns `Active`, Meld will populate the value as `POSTED`
  - If returns `Pending`, Meld will populate the value as `PENDING`
  - If returns `Shadow`, Meld will populate the value as `SHADOW` **Note:**\ Some institutions continue to modify or delete transactions long after they are first posted to the institution’s data feed. This practice can cause Finicity transactions to appear as duplicates, or to continue to appear in the Finicity data after they have disappeared from the institution’s current website. Finicity has added the ability to identify transactions that were found in an earlier aggregation of an account, but are not found in the institution’s current data source. These `SHADOW` transactions are identified in the `transaction record`.
transactionDate The date and time the transaction was made transactionDate * If null:\_ postedDate
postedDate The date and time the transaction was posted postedDate
# Finicity Partner Details Source: https://docs.meld.io/docs/bank-linking/partner-details/finicity/index Finicity (a Mastercard company) is a US-focused open banking provider. Meld supports Finicity for Balances, Identifiers, Owners, Transactions, Investment Holdings, and Investment Transactions across United States and Canadian institutions. Do not attempt to set up webhooks from Finicity to Meld. Meld passes Finicity the correct webhook URL when initiating the Finicity widget (in any environment). ## Supported Countries Meld supports Finicity institutions in the United States (US) and Canada (CA). ## Configuration Settings If you have your own [Finicity](https://www.finicity.com/) account that you wish to integrate with Meld, you will need to provide the following credentials securely on the Meld Integrations Tab on the dashboard: * *App Key* * *Partner Id* * *Partner Secret* * *Experience Id* ![](https://files.readme.io/b593fe1-image.png) ## Special considerations Finicity refreshes existing connections once a day with the institution. They do not notify when these updates occur, so Meld polls daily to check for updates. Finicity bills extra for aggregating historical transactions. Meld loads them by default on the initial connection. Contact Meld if you would instead like to configure Finicity connections to only load historical transactions once the first forced refresh is triggered for the connection. # Partner Details Source: https://docs.meld.io/docs/bank-linking/partner-details/index Meld routes bank linking traffic through several service providers. Each provider has its own institution coverage, product support, and data shape. The pages in this section document how Meld maps each partner's raw data into Meld's normalized models so you can predict exactly what your application will receive. Use these pages when you need to: * Confirm which products a given provider supports. * Trace a Meld field back to the underlying provider field. * Diagnose differences between connections sourced from different providers. ## Supported partners Largest US institution coverage. Strong support for balances, transactions, identifiers, owners, and investments. Broad US coverage with a user-friendly widget and reliable identifier and balance data. Mastercard Open Banking provider with strong US bank coverage and detailed transaction data. Open Banking provider with strong international coverage, especially across Europe. Meld also supports Salt Edge Partners as a separate routing option for white-label and partner-managed Salt Edge integrations. See the Salt Edge Partners section in the sidebar for those mappings. # MX Partner Details Source: https://docs.meld.io/docs/bank-linking/partner-details/mx/index [MX](https://www.mx.com/) is a bank linking aggregator that Meld integrates with to power account aggregation in the United States and Canada. Through MX, Meld supports the following products: account base data, balances, identifiers (ACH/EFT), owners (identity), transactions, investment holdings, and investment transactions. This page covers supported countries, configuration settings, and MX webhooks. For field-by-field mappings, see the per-product mapping pages linked from [MX to Meld Overall Mappings](/docs/bank-linking/partner-details/mx/mx-to-meld-mappings). MX does not return international identifiers (IBAN/BIC) or BACS identifiers. If your use case requires those formats, see [Plaid](/docs/bank-linking/partner-details/plaid/plaid-identifiers-mapping) or [Salt Edge](/docs/bank-linking/partner-details/salt-edge). ## Supported Countries Meld supports MX institutions in the United States (US) and Canada (CA). ## Configuration Settings If you have your own [MX](https://www.mx.com/) account that you wish to integrate with Meld, you will need to provide the following credentials securely on the Meld Integrations Tab on the dashboard: * *Client ID* * *API Key* * *Webhook Username* * *Webhook Password* ![](https://files.readme.io/af21d64-image.png) ## MX Webhooks MX specifically requires a webhook username and password when setting up webhooks to Meld. However, Meld needs your MX API Key and Client ID before we can generate a url for you to use set up webhooks in the MX dashboard. Therefore when setting up an MX integration, the recommended order of operations is for you to do the following: 1. Add your API Key and Client Id for MX in the dashboard and click Add Integration. 2. Grab your Meld webhook url from the dashboard and use that to go and set up a webhook profile on MX's dashboard. You should set up webhooks for Aggregation, Balance, Connection Status, and History. 3. While still on MX's dashboard, for each webhook, choose the security type basic and set up a webhook username and password. You should use the same username and password for all webhook types within an environment. 4. Come back to Meld's dashboard and add the username and password to the MX integration credentials. ## Special Considerations * In MX production, they require whitelisting the IP addresses that will be hitting their API. This means you will need to request that Meld's IP's be whitelisted on their dashboard. Depending on the Meld environment your MX production account is pointing to, will require different IP's. They are as follows: * Meld Sandbox: `3.12.70.233`, `18.188.161.75`, `52.14.94.218` * Meld Production: `23.20.254.181`, `44.195.151.201`, `44.196.135.166`, `54.158.91.174`, `54.173.48.67`, `184.73.192.20` # MX Balances Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/mx/mx-balances-mapping This data comes from MX's [/users/\{user\_guid}/accounts](https://docs.mx.com/api#core_resources_accounts_list_accounts) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example: MX `currency_code: "USD"`, `balance: 1203.45`, `available_balance: 1150.20` maps to Meld `currency: "USD"`, `currentAmount: 1203.45`, `availableAmount: 1150.20`. | Meld Field | Description | MX Response Field | | :-------------- | :---------------------------------- | :----------------- | | currency | The currency of the account balance | currency\_code | | currentAmount | The current amount in the account | balance | | availableAmount | The available amount in the account | available\_balance | | updatedAt | The last time balances were updated | updated\_at | # MX Identifiers Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/mx/mx-identifiers-mapping This data comes from MX's [/users/`{user_guid}`/members/`{member_guid}`/account\_numbers](https://docs.mx.com/api#verification_mx_widgets_list_account_numbers_by_account) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example: MX `account_number: "1111222233330000"` and `routing_number: "011401533"` map to Meld `ach.accountNumber` and `ach.routingNumber` respectively. MX does not return international identifiers (IBAN, BIC) or BACS (UK) account details. Those fields will be `null` in the Meld response when sourced from MX. | Meld Field | Description | MX Response Field | | :------------------- | :--------------------------------------------------------------------------------------------------- | :------------------ | | ach | ACH data object | | |    accountNumber | The account number | account\_number | |    routingNumber | The account routing number. Reference [Routing number](https://en.wikipedia.org/wiki/Routing_number) | routing\_number | | eft | EFT data object | | |    accountNumber | The account number | account\_number | |    institutionNumber | The account institution number | institution\_number | |    branchNumber | The institution branch number | transit\_number | | international | International data object | | |    iban | The International Bank Account Number (IBAN) for the financial account | *None* | |    bic | The Bank Identifier Code (BIC) for the financial account | *None* | | bacs | The BACS data object | | |    accountNumber | BACS account number | *None* | |    sortCode | BACS sort code | *None* | # MX Investment Holdings Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/mx/mx-investment-holdings-mapping This data comes from MX's [/users/\{user\_guid}/members/\{member\_guid}/holdings](https://docs.mx.com/api-reference/platform-api/reference/list-holdings-by-member) endpoint and is returned from Meld's [/bank-linking/investments/holdings](/api-reference/bank-linking/investments/bank-linking-investment-holdings-search) endpoint. Example: an MX holding with `symbol: "AAPL"`, `shares: 10`, `market_value: 1850.50`, `cost_basis: 180.00`, `currency_code: "USD"` maps to Meld `symbol: "AAPL"`, `quantity: 10`, `currentValue: 1850.50`, `costBasis: 180.00`, `currencyCode: "USD"`. MX does not return holding `description` or `isin`. Those fields will be `null` in the Meld response when sourced from MX. | Meld Field | Description | Mx Response Field | | :----------- | :------------------------------------------------------------------------- | :--------------------- | | symbol | The symbol of the security | symbol | | quantity | The number of shares of the security | shares | | currentValue | The total current value of the holding | market\_value | | costBasis | The purchase price of the holding, per share | cost\_basis | | currencyCode | The ISO currency code that was used to purchase the holding | currency\_code | | updatedAt | The last time the details of this holding were updated | updated\_at | | description | A description of the holding | **Not Available** | | closePrice | The price of the security at last market close | shares / market\_value | | isin | The global ISO number for an individual security. | **Not Available** | | cusip | A shortened version of the isin used for North American securities. | cusip | | type | The type of holding (ex: stock, etf). This is normalized across providers. | holding\_type | # MX Investment Transactions Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/mx/mx-investment-transactions-mapping This data comes from MX's [/users/\{user\_guid} /accounts/\{account\_guid}/transactions](https://docs.mx.com/api-reference/platform-api/reference/list-transactions-by-account) endpoint and is returned from Meld's [/bank-linking/investments/transactions](/api-reference/bank-linking/investments/bank-linking-investment-transactions-search) endpoint. Example: an MX investment transaction with `account.balance: 5200.10`, `account.guid: "ACT-123"`, `amount: 925.25`, `currency_code: "USD"`, `description: "BUY AAPL"`, `transacted_at: "2024-01-15"`, `posted_at: "2024-01-15"`, `category: "buy"` maps to Meld `cashBalance: 5200.10`, `accountId: "ACT-123"`, `amount: 925.25`, `currency: "USD"`, `description: "BUY AAPL"`, `transactionDate: "2024-01-15"`, `postedDate: "2024-01-15"`, `type: "buy"`. MX does not return per-security details on investment transactions. `symbol`, `quantity`, and `costBasis` are not populated when sourced from MX. | Meld Field | Description | Mx Response Field | | :-------------- | :---------------------------------------------------------- | :---------------- | | symbol | The symbol of the security | **Not available** | | quantity | The number of shares of the security | **Not available** | | costBasis | The purchase price of the holding, per share | **Not available** | | cashBalance | The cash balance of the account after the transaction | account.balance | | accountId | The Id of the financial account | account.guid | | amount | The total currency involved in the transaction | amount | | currency | The ISO currency code that was used to purchase the holding | currency\_code | | description | A description of the transaction | description | | status | The status of the transaction | status | | transactionDate | The date the transaction was initiated | transacted\_at | | postedDate | The date the transaction was finalized | posted\_at | | type | The investment transaction's type | category | # MX Owners Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/mx/mx-owners-mapping This data comes from MX's [/users/`{user_guid}` /members/`{member_guid}` /account\_owners](https://docs.mx.com/api#identification_identity_list_account_owners) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example: MX `address: "2982 Frances Vineyard"`, `city: "San Francisco"`, `state: "CA"`, `postal_code: "94114"`, `country: "US"` maps to Meld `addresses.data.street`, `addresses.data.city`, `addresses.data.region`, `addresses.data.postalCode`, `addresses.data.country`. MX does not indicate which contact item is primary. Meld defaults the `primary` field to `UNKNOWN` for addresses, emails, and phone numbers sourced from MX.
Meld Field Description MX Response Field
addresses The address(es) associated with this owner
   data The address data object
      street The street and residence number address
      city The city city
      region The region/state state
      postalCode The postal or zip code postal\_code
      country The ISO 3166-1 alpha-2 country code country
   primary Indicates if this is the owner's primary residence Does not provide.
Meld will\ default\ to `UNKNOWN`
emails The email(s) associated with this owner
   data The email address email
   primary Indicates if this is the owner's primary email Does not provide.
Meld will\ default\ to `UNKNOWN`
names The name(s) of this owner owner\_name
phoneNumbers The phone number(s) associated with this owner
   data The phone number phone
   primary Indicates if this is the owner's primary phone number Does not provide.
Meld will\ default\ to `UNKNOWN`
# MX to Meld Overall Mappings Source: https://docs.meld.io/docs/bank-linking/partner-details/mx/mx-to-meld-mappings You can always compare the raw responses from MX in the `serviceProviderDetails` section of a financial account. * [Financial Account Base Fields](#financial-account-base-fields) * [Balances](/docs/bank-linking/partner-details/mx/mx-balances-mapping) * [Identifiers](/docs/bank-linking/partner-details/mx/mx-identifiers-mapping) * [Owners](/docs/bank-linking/partner-details/mx/mx-owners-mapping) * [Transactions](/docs/bank-linking/partner-details/mx/mx-transactions-mapping) * [Investment Holdings](/docs/bank-linking/partner-details/mx/mx-investment-holdings-mapping) * [Investment Transactions](/docs/bank-linking/partner-details/mx/mx-investment-transactions-mapping) * [Normalized Account Types and Subtypes](#normalized-account-types-and-subtypes) ## Financial Account Base Fields This data comes from MX's [/users/\{user\_guid}/accounts](https://docs.mx.com/api#core_resources_accounts_list_accounts) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint.
Meld Field Description MX Response Field
name The account name name
truncatedAccount\ Number The last 4 digits of the account number accountNumber
status The real-time status of the account `INACTIVE` if MX fields\`\`\` is\_closed = TRUE \`\`\`or `is_hidden = TRUE`; Otherwise `ACTIVE`
type The type of the account. Mapped to a Meld standardized type type
subtype The subtype of the account. Mapped to a Meld standardized subtype subtype
## Normalized Account Types and Subtypes # MX Transactions Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/mx/mx-transactions-mapping This data comes from MX's [/users/\{user\_guid} /accounts/\{account\_guid}/transactions](https://docs.mx.com/api-reference/platform-api/reference/list-transactions-by-account) endpoint and is returned from Meld's [/bank-linking/transactions](/api-reference/bank-linking/transactions/bank-linking-transactions-search) endpoint. Example: an MX transaction with `amount: 89.40`, `currency_code: "USD"`, `original_description: "Uber 063015 SF**POOL**"`, `status: "POSTED"`, `transacted_at: "2024-01-15"`, `posted_at: "2024-01-15"` maps to Meld `amount: 89.40`, `currency: "USD"`, `description: "Uber 063015 SF**POOL**"`, `status: "POSTED"`, `transactionDate: "2024-01-15"`, `postedDate: "2024-01-15"`. | Meld Field | Description | MX Response Field | | :-------------- | :------------------------------------------- | :-------------------- | | amount | The amount of the transaction | amount | | currency | The currency used in the transaction | currency\_code | | description | The transaction's description | original\_description | | status | The status of the transaction | status | | transactionDate | The date and time the transaction was made | transacted\_at | | postedDate | The date and time the transaction was posted | posted\_at | # Plaid Partner Details Source: https://docs.meld.io/docs/bank-linking/partner-details/plaid/index [Plaid](https://plaid.com/) is a bank linking aggregator that Meld integrates with to power account aggregation in the United States and Canada. Through Plaid, Meld supports the following products: account base data, balances, identifiers (ACH/EFT/BACS/international), owners (identity), transactions, investment holdings, and investment transactions. This page covers supported countries, configuration settings, and Plaid-specific behavior. For field-by-field mappings, see the per-product mapping pages linked from [Plaid to Meld Overall Mappings](/docs/bank-linking/partner-details/plaid/plaid-to-meld-mappings). Do not attempt to set up webhooks from Plaid to Meld. Meld will pass Plaid the correct webhook URL when initiating the Plaid widget (in any environment). ## Supported Countries Meld supports Plaid institutions in the United States (US) and Canada (CA). ## Configuration Settings If you have your own [Plaid](https://plaid.com/) account that you wish to integrate with Meld, you will need to provide the following credentials securely on the Meld Integrations Tab on the dashboard: * *Client ID* * *Client Secret* In addition, you will need to complete Plaid's OAuth Registration form in order to enable OAuth institutions. ![](https://files.readme.io/02425af-image.png) ## Product Initialization Unlike the other service providers, Plaid will filter out any accounts belonging to the customer that do not support all of the products requested, thus limiting which accounts the customer can select for aggregation within Plaid's widget flow. In order to normalize account selection behavior across service providers and reduce customer abandonment due to missing accounts, Meld aims to ensure the largest breadth of accounts is made available as possible. To do so, Meld uses a subset of the products supplied in the [/connect/start](/api-reference/bank-linking/connect/bank-linking-connect-create) request that optimizes for account availability when initializing the connection with Plaid. This means that there may be financial accounts belonging to the connection that don't support every product requested, but this can be expected and replicates the behavior exhibited by all of the other service providers. There are several factors Meld takes into consideration when determining which products requested in the `/connect/start` request will be included in the minimal subset provided to Plaid. This determination is made as follows: 1. The products supported by the widest variety of account types take precedence for inclusion in the subset, and those that aren't supported by many account types are typically excluded. For example,`TRANSACTIONS` is supported by nearly all account types, so it is always provided to Plaid if it is one of the requested products in `/connect/start`. On the other hand, `IDENTIFIERS` (account/routing numbers) is only supported by checking and savings accounts, and thus will not be included in the subset (unless it is the only product requested). 2. Products deemed distinctive enough that their inclusion alone within the `/connect/start` request likely implies their importance to the applications that request them will take precedence for inclusion in the subset. For example, the investment type products (`INVESTMENT_TRANSACTIONS` and `INVESTMENT_HOLDINGS`) are specific enough that when requested, they are most likely critical to an application's use-case, and filtering out account types that don't support investments would be desirable. On the other hand, `OWNERS` is typically a supplementary product for most applications and typically won't be included in the subset. It's nice to have if available, but not important enough that it warrants filtering out accounts that don't have this information. **Note:** This minimizing of the product set *does not* imply that the products not included in the subset Meld provides to Plaid will never be aggregated, but rather that the omitted products are not being used to restrict which accounts the customer can select in Plaid's widget. Meld will still attempt to load all the requested products after connection completion, there is just no guarantee that every linked account supports all of them. You can read more about how Plaid handles product initialization on their own documentation [here](https://plaid.com/docs/link/initializing-products/#required-if-supported-products). ## Special considerations * You must complete several Plaid onboarding steps prior to integrating Plaid with Meld. These steps can be completed on the Plaid dashboard or by reaching out to your account manager. The steps are as follows: * Complete the [Security Questionnaire ](https://dashboard.plaid.com/overview/questionnaire-start)form * Complete the OAuth registration. See [here](https://plaid.com/docs/link/oauth/#complete-the-registration-requirements). * Enable Plaid's `transaction_refresh` product if triggering real-time refreshes is desired. Plaid will still auto-refresh transactions daily, but if forcing refreshes is applicable to your use case, then this is an additional product that must be enabled. You can do so by submitting a [product request form](https://dashboard.plaid.com/overview/request-products) on the plaid dashboard * Plaid is the only provider for which the user must select their institution again in their widget even if already selected previously in the Meld picker * If transactions is a specified product, historical transactions will be loaded by default In Plaid sandbox, use the test credentials `user_good` / `pass_good` to link mock institutions. Production data and behavior may differ — particularly for OAuth institutions, which require full registration before use. # Plaid Balances Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/plaid/plaid-balances-mapping This data comes from Plaid's [/accounts/balance/get](https://plaid.com/docs/api/accounts/) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example: a Plaid `accounts.balances.iso_currency_code` of `"USD"` maps to Meld `currency: "USD"`. A Plaid `accounts.balances.current` of `1203.45` maps to Meld `currentAmount: 1203.45`.
Meld Field Description Plaid Response Field
currency The currency of the account balance accounts.balances\ .iso\_currency\_code
currentAmount The current amount in the account accounts.balances.current
availableAmount The available amount in the account accounts.balances.available
updatedAt The last time balances were updated accounts.balances\ .last\_updated\_datetime
# Plaid Identifiers Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/plaid/plaid-identifiers-mapping This data comes from Plaid's [/auth/get](https://plaid.com/docs/api/products/auth/#auth-get-response-numbers-ach-account) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example: Plaid `numbers.ach.account: "1111222233330000"` and `numbers.ach.routing: "011401533"` map to Meld `ach.accountNumber` and `ach.routingNumber` respectively. | Meld Field | Description | Plaid Response Field | | :------------------- | :--------------------------------------------------------------------------------------------------- | :------------------------- | | ach | ACH data object | | |    accountNumber | The account number | numbers.ach.account | |    routingNumber | The account routing number. Reference [Routing number](https://en.wikipedia.org/wiki/Routing_number) | numbers.ach.routing | | eft | EFT data object | | |    accountNumber | The account number | numbers.eft.account | |    institutionNumber | The account institution number | numbers.eft.institution | |    branchNumber | The institution branch number | numbers.eft.branch | | international | International data object | | |    iban | The International Bank Account Number (IBAN) for the financial account | numbers.international.iban | |    bic | The Bank Identifier Code (BIC) for the financial account | numbers.international.bic | | bacs | The BACS data object | | |    accountNumber | BACS account number | numbers.bacs.account | |    sortCode | BACS sort code | numbers.bacs.sort\_code |
# Plaid Investment Holdings Mappings Source: https://docs.meld.io/docs/bank-linking/partner-details/plaid/plaid-investment-holdings-mappings This data comes from Plaid's [/investments/holdings/get](https://plaid.com/docs/api/products/investments/#investmentsholdingsget) endpoint and is returned from Meld's [/bank-linking/investments/holdings](/api-reference/bank-linking/investments/bank-linking-investment-holdings-search) endpoint. Example: a Plaid holding with `Securities.ticker_symbol: "AAPL"`, `Holdings.quantity: 10`, `Holdings.institution_value: 1850.50`, `Securities.iso_currency_code: "USD"` maps to Meld `symbol: "AAPL"`, `quantity: 10`, `currentValue: 1850.50`, `currencyCode: "USD"`. | Meld Field | Description | Plaid Response Field | | :----------- | :------------------------------------------------------------------------- | :---------------------------------- | | symbol | The symbol of the security | Securities.ticker\_symbol | | quantity | The number of shares of the security | Holdings.quantity | | currentValue | The total current value of the holding | Holdings.institution\_value | | costBasis | The purchase price of the holding, per share | Holdings.cost\_basis | | currencyCode | The ISO currency code that was used to purchase the holding | Securities.iso\_currency\_code | | updatedAt | The last time the details of this holding were updated | Holdings.institution\_price\_as\_of | | description | A description of the holding | Securities.name | | closePrice | The price of the security at last market close | Securities.close\_price | | isin | The global ISO number for an individual security. | Securities.isin | | cusip | A shortened version of the isin used for North American securities. | Securities.cusip | | type | The type of holding (ex: stock, etf). This is normalized across providers. | Securities.type | # Plaid Investment Transactions Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/plaid/plaid-investment-transactions-mapping This data comes from Plaid's [/investments/transactions/get](https://plaid.com/docs/api/products/investments/#investmentstransactionsget) endpoint and is returned from Meld's [/bank-linking/investments/transactions](/api-reference/bank-linking/investments/bank-linking-investment-transactions-search) endpoint. Example: a Plaid investment transaction with `Securities.ticker_symbol: "AAPL"`, `Investment_transactions.quantity: 5`, `Investment_transactions.amount: 925.25`, `Investment_transactions.iso_currency_code: "USD"`, `Investment_transactions.date: "2024-01-15"`, `Investment_transactions.type: "buy"` maps to Meld `symbol: "AAPL"`, `quantity: 5`, `amount: 925.25`, `currency: "USD"`, `transactionDate: "2024-01-15"`, `type: "buy"`. | Meld Field | Description | Plaid Response Field | | :-------------- | :---------------------------------------------------------- | :------------------------------------------- | | symbol | The symbol of the security | Securities.ticker\_symbol | | quantity | The number of shares of the security | Investment\_transactions.quantity | | costBasis | The purchase price of the holding, per share | Investment\_transactions.price | | cashBalance | The cash balance of the account after the transaction | Accounts.balances.current | | accountId | The Id of the financial account | Accounts.account\_id | | amount | The total currency involved in the transaction | Investment\_transactions.amount | | currency | The ISO currency code that was used to purchase the holding | Investment\_transactions.iso\_currency\_code | | description | A description of the transaction | Securities.name | | status | The status of the transaction | **Not Available** (always set to POSTED) | | transactionDate | The date the transaction was initiated | Investment\_transactions.date | | postedDate | The date the transaction was finalized | **Not Available** | | type | The type of investment transaction | Investment\_transactions.type | # Plaid Owners Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/plaid/plaid-owners-mapping This data comes from Plaid's [/identity/get](https://plaid.com/docs/api/products/identity/#identity-get-response-accounts-owners-addresses-data-street) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example: Plaid `accounts.owners.addresses.data.street: "2982 Frances Vineyard"` maps to Meld `addresses.data.street`. Plaid `accounts.owners.emails.primary: true` maps to Meld `emails.primary: true`.
Meld Field Description Plaid Response Field
addresses The address(es) associated with this owner
   data The address data object
      street The street and residence number accounts.owners.addresses\ .data.street
      city The city accounts.owners.addresses\ .data.city
      region The region/state accounts.owners.addresses\ .data.region
      postalCode The postal or zip code accounts.owners.addresses\ .data.postal\_code
      country The ISO 3166-1 alpha-2 country code accounts.owners.addresses\ .data.country
   primary Indicates if this is the owner's primary residence accounts.owners.addresses\ .primary
emails The email(s) associated with this owner
   data The email address accounts.owners.emails.data
   primary Indicates if this is the owner's primary email accounts.owners.emails.primary
names The name(s) of this owner accounts.owners.names
phoneNumbers The phone number(s) associated with this owner
   data The phone number accounts.owners\ .phone\_numbers.data
   primary Indicates if this is the owner's primary phone number accounts.owners\ .phone\_numbers.primary
# Plaid to Meld Overall Mappings Source: https://docs.meld.io/docs/bank-linking/partner-details/plaid/plaid-to-meld-mappings You can always compare the raw responses from Plaid in the `serviceProviderDetails` section of a financial account. * [Financial Account Base Fields](#financial-account-base-fields) * [Balances](/docs/bank-linking/partner-details/plaid/plaid-balances-mapping) * [Identifiers](/docs/bank-linking/partner-details/plaid/plaid-identifiers-mapping) * [Owners](/docs/bank-linking/partner-details/plaid/plaid-owners-mapping) * [Transactions](/docs/bank-linking/partner-details/plaid/plaid-transactions-mapping) * [Investment Holdings](/docs/bank-linking/partner-details/plaid/plaid-investment-holdings-mappings) * [Investment Transactions](/docs/bank-linking/partner-details/plaid/plaid-investment-transactions-mapping) * [Normalized Account Types and Subtypes](#normalized-account-types-and-subtypes) ## Financial Account Base Fields This data comes from Plaid's [/accounts/get](https://plaid.com/docs/api/accounts/#accounts-get-response-accounts-name) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. | Meld Field | Description | Plaid Response Field | | :--------------------- | :---------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------- | | name | The account name | accounts.name | | truncatedAccountNumber | The last 4 digits of the account number | accounts.mask | | status | The real-time status of the account | Due to Plaid only returning active bank accounts, it does not have this field.
Meld will populate the status as `ACTIVE` | | type | The type of the account. Mapped to a Meld standardized type | accounts.type | | subtype | The subtype of the account. Mapped to a Meld standardized subtype | accounts.subtype | ## Normalized Account Types and Subtypes # Plaid Transactions Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/plaid/plaid-transactions-mapping This data comes from Plaid's [/transactions/get](https://plaid.com/docs/api/products/transactions/) endpoint and is returned from Meld's [/bank-linking/transactions](/api-reference/bank-linking/transactions/bank-linking-transactions-search) endpoint. Example: a Plaid transaction with `amount: 89.40`, `iso_currency_code: "USD"`, `name: "Uber 063015 SF**POOL**"`, `pending: false`, `date: "2024-01-15"` maps to Meld `amount: 89.40`, `currency: "USD"`, `description: "Uber 063015 SF**POOL**"`, `status: "POSTED"`, `postedDate: "2024-01-15"`.
Meld Field Description Plaid Response Field
amount The amount of the transaction amount
currency The currency used in the transaction iso\_currency\_code unofficial\_currency\_code (if iso\_currency\_code is null)
description The transaction's description name
status The status of the transaction pending
* If returns `True`, Meld will populate the value as `PENDING`
* If returns `False`, Meld will populate the value as `POSTED`
transactionDate The date and time the transaction was made * For transactions with `PENDING` status: `date`
* For transactions with `POSTED` status: `authorizedDate`
* If the authorized date returns `null`, Meld will use the data from `date`
postedDate The date and time the transaction was posted * For transactions with `PENDING` status, it will return `null`
* For transactions with `POSTED` status: `date`
# Salt Edge Partners - Partner Details Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge-partners/index Salt Edge Partners is Salt Edge's PSD2-compliant open banking product, used for EEA and UK regulated bank connections. Meld supports Salt Edge Partners for Balances, Identifiers, Owners, and Transactions. **Salt Edge Partners vs Salt Edge** — Meld integrates with two distinct Salt Edge products: * **Salt Edge Partners** (this page) uses the PSD2-compliant Partners API ([/api/partners/v1](https://docs.saltedge.com/partners/v1/)) and is the correct choice for EEA-regulated open banking connections. * **[Salt Edge](../salt-edge)** uses Salt Edge's global Account Information API ([/api/v5](https://docs.saltedge.com/account_information/v5/)) and covers a wider set of non-EEA countries. The two products require separate credentials and use separate endpoints. If you have both, double-check which set you are configuring. ## Supported Countries Meld supports Salt Edge Partners institutions in Argentina (AR), Australia (AU), Austria (AT), Belarus (BY), Belgium (BE), Brazil (BR), Bulgaria (BG), Croatia (HR), Cyprus (CY), Czechia (CZ), Denmark (DK), Estonia (EE), Finland (FI), France (FR), Germany (DE), Greece (GR), Hungary (HU), Iceland (IS), Ireland (IE), Italy (IT), Latvia (LV), Liechtenstein (LI), Lithuania (LT), Luxembourg (LU), Malta (MT), Netherlands (NL), Norway (NO), Poland (PL), Portugal (PT), Romania (RO), Slovakia (SK), Slovenia (SI), Spain (ES), Sweden (SE), and the United Kingdom (UK). ## Configuration Settings If you have your own [Salt Edge Partners](https://docs.saltedge.com/partners/v1/) account that you wish to integrate with Meld, you will need to provide the following credentials securely on the Meld Integrations Tab on the dashboard: * *App ID* * *Secret* * *Signature Key* ![](https://files.readme.io/8d1d6a1-image.png) If you have both a Salt Edge and Salt Edge Partners account, the keys are different. Verify you are entering the correct credentials for each integration. ## Salt Edge Partners Webhooks Salt Edge Partners needs several webhook urls, which can be seen in the Meld dashboard: ![](https://files.readme.io/472c5e9-image.png) All of the urls will start with `/webhook/saltedgepartners//` but at the end will have a different event, such as `success`, `fail`, `destroy`, `notify`, `interactive`, or `service`. Here is how they would look in the Salt Edge Partners dashboard: ![](https://files.readme.io/276f837-image.png) ## Special considerations Salt Edge Partners does not support forced refreshes — only daily automatic refreshes. If you need on-demand refresh behavior, use a different provider for the same institution where available. Meld loads the last 90 days of transaction data by default for Salt Edge Partners connections (if transactions is a requested product). # Salt Edge Partners Balance Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge-partners/salt-edge-partners-balance-mapping This data comes from Salt Edge Partners' [api/partners/v1/accounts](https://docs.saltedge.com/partners/v1/#accounts-attributes) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example values: `currency: "EUR"`, `currentAmount: 2410.55`, `availableAmount: 2380.00`, `updatedAt: "2024-08-12T14:33:00Z"`. | Meld Field | Description | SaltEdge Partners Response Field | | :-------------- | :---------------------------------- | :------------------------------- | | currency | The currency of the account balance | currency\_code | | currentAmount | The current amount in the account | balance | | availableAmount | The available amount in the account | extra.available\_amount | | updatedAt | The last time balances were updated | updated\_at | # Salt Edge Partners Identifiers Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge-partners/salt-edge-partners-identifiers-mapping This data comes from Salt Edge Partners' [api/partners/v1/accounts](https://docs.saltedge.com/partners/v1/#accounts-extra) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example values: `international.iban: "DE89370400440532013000"`, `international.bic: "COBADEFFXXX"`, `bacs.accountNumber: "31926819"`, `bacs.sortCode: "601613"`. Salt Edge Partners does not provide US ACH (`ach.*`) or Canadian EFT (`eft.*`) identifiers. Identifier coverage varies per institution within the `extra` object. | Meld Field | Description | SaltEdge Partners Response Field | | :------------------- | :--------------------------------------------------------------------------------------------------- | :------------------------------- | | ach | ACH data object | | |    accountNumber | The account number | *None* | |    routingNumber | The account routing number. Reference [Routing number](https://en.wikipedia.org/wiki/Routing_number) | *None* | | eft | EFT data object | | |    accountNumber | The account number | *None* | |    institutionNumber | The account institution number | *None* | |    branchNumber | The institution branch number | *None* | | international | International data object | | |    iban | The International Bank Account Number (IBAN) for the financial account | extra.iban | |    bic | The Bank Identifier Code (BIC) for the financial account | extra.swift | | bacs | The BACS data object | | |    accountNumber | BACS account number | extra.account\_number | |    sortCode | BACS sort code | extra.sort\_code |
# Salt Edge Partners Owners Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge-partners/salt-edge-partners-owners-mapping This data comes from Salt Edge Partners' [api/partners/v1/holder\_info](https://docs.saltedge.com/partners/v1/#holder_info-show) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example values: `names: ["Jane Doe"]`, `addresses.data: { street: "10 Downing St", city: "London", region: "Greater London", postalCode: "SW1A 2AA", country: "GB" }`, `emails.data: ["jane.doe@example.com"]`. Salt Edge Partners does not indicate whether an address, email, or phone number is the owner's primary record. Meld defaults `addresses.primary`, `emails.primary`, and `phoneNumbers.primary` to `UNKNOWN`. | Meld Field | Description | SaltEdge Partners Response Field | | :--------------- | :---------------------------------------------------- | :---------------------------------------------------- | | addresses | The address(es) associated with this owner | addresses | |    data | The address data object | | |       street | The street and residence number | street | |       city | The city | city | |       region | The region/state | state | |       postalCode | The postal or zip code | post\_code | |       country | The ISO 3166-1 alpha-2 country code | country\_code | |    primary | Indicates if this is the owner's primary residence | Does not provide.
Meld will default to `UNKNOWN` | | emails | The email(s) associated with this owner | emails | |    data | The email address | | |    primary | Indicates if this is the owner's primary email | Does not provide.
Meld will default to `UNKNOWN` | | names | The name(s) of this owner | names | | phoneNumbers | The phone number(s) associated with this owner | phone\_numbers | |    data | The phone number | | |    primary | Indicates if this is the owner's primary phone number | Does not provide.
Meld will default to `UNKNOWN` | # Salt Edge Partners to Meld Overall Mappings Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge-partners/salt-edge-partners-to-meld-mappings You can always compare the raw responses from Salt Edge Partners in the `serviceProviderDetails` section of a financial account. [Financial Account Base Fields](#financial-account-base-fields) [Balances](./salt-edge-partners-balance-mapping) [Identifiers](./salt-edge-partners-identifiers-mapping) [Owners](./salt-edge-partners-owners-mapping) [Transactions](./salt-edge-partners-transactions-mapping) [Normalized Account Types and Subtypes](#normalized-account-types-and-subtypes) ## Financial Account Base Fields This data comes from SaltEdge Partners [api/partners/v1/accounts](https://docs.saltedge.com/partners/v1/#accounts-list) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint.
Meld Field Description SaltEdge Partners Response Field
name The account name name
truncatedAccountNumber The last 4 digits of the account number extra.account\_number
If extra.account\_number is null then:
extra.iban
If extra.iban is null then:
extra.bban
If extra.bban is null then:
extra.cards
status The real-time status of the account extra.status
Possible values:
* `active`
* `inactive`
* `unauthorized`
If null: `active`
type The type of the account. Mapped to a Meld standardized type nature
subtype The subtype of the account. Mapped to a Meld standardized subtype None. Meld subtype determined from SaltEdge Partners type.
## Normalized Account Types and Subtypes # Salt Edge Partners Transactions Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge-partners/salt-edge-partners-transactions-mapping This data comes from Salt Edge Partners' [api/partners/v1/transactions](https://docs.saltedge.com/partners/v1/#transactions-list) endpoint and is returned from Meld's [/bank-linking/transactions](/api-reference/bank-linking/transactions/bank-linking-transactions-search) endpoint. Example values: `amount: -22.40`, `currency: "EUR"`, `status: "POSTED"`, `transactionDate: "2024-08-10"`, `postedDate: "2024-08-11"`.
Meld Field Description SaltEdge Partners Response Field
amount The amount of the transaction amount
currency The currency used in the transaction currency\_code
payee The recipient of the transaction description
status The status of the transaction status
transactionDate The date and time the transaction was made made\_on
postedDate The date and time the transaction was posted extra.posting\_date\ *If extra.posting\_date is null then:*\ made\_on
# Salt Edge Partner Details Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge/index Salt Edge is a global open banking provider with broad coverage across the Americas, EMEA, and APAC. Meld supports Salt Edge for Balances, Identifiers, Owners, and Transactions. **Salt Edge vs Salt Edge Partners** — Meld integrates with two distinct Salt Edge products: * **Salt Edge** (this page) uses Salt Edge's global Account Information API ([/api/v5](https://docs.saltedge.com/account_information/v5/)) and covers a wider set of non-EEA countries. * **[Salt Edge Partners](../salt-edge-partners)** uses the PSD2-compliant Partners API ([/api/partners/v1](https://docs.saltedge.com/partners/v1/)) and is the correct choice for EEA-regulated open banking connections. The two products require separate credentials and use separate endpoints. If you have both, double-check which set you are configuring. ## Supported Countries Meld supports Salt Edge institutions in the United States (US), Argentina (AR), Australia (AU), Austria (AT), Belarus (BY), Brazil (BR), Bulgaria (BG), Canada (CA), Chile (CL), Dominican Republic (DR), Ecuador (EC), Egypt (EG), France (FR), Germany (DE), Hong Kong (HK), Hungary (HU), India (IN), Ireland (IE), Israel (IL), Italy (IT), Malaysia (MY), Mexico (MX), Netherlands (NL), New Zealand (NZ), North Macedonia (MK), Pakistan (PJ), Philippines (PH), Poland (PL), Republic of Moldova (MD), Romania (RO), Saudi Arabia (SA), Singapore (SG), Slovakia (SK), South Africa (ZA), Spain (ES), Switzerland (CH), Thailand (TH), Turkey (TR), Ukraine (UA), United Arab Emirates (AE), and United Kingdom (GB). ## Configuration Settings If you have your own [Salt Edge](https://www.saltedge.com/) (Open Banking) account that you wish to integrate with Meld, you will need to provide the following credentials securely on the Meld Integrations Tab on the dashboard: * *App ID* * *Secret* * *Signature Key* ![](https://files.readme.io/555c2a2-image.png) ## Salt Edge Webhooks Salt Edge needs several webhook urls, which can be seen in the Meld dashboard: ![](https://files.readme.io/936ae11-image.png) All of the urls will start with \/webhook/saltedge/`{accountId}`/ but at the end will have a different event, such as `success`, `fail`, `destroy`, `notify`, `interactive`, or `service`. Here is how they would look in the Salt Edge dashboard: ![](https://files.readme.io/b54472a-image.png) ## Special Considerations Meld loads the last 90 days of transaction data by default for Salt Edge connections (if transactions is a requested product). Salt Edge may require a request signature on certain calls. See [Salt Edge signature documentation](https://docs.saltedge.com/general/v5/#signature) for details. # Salt Edge Balances Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge/salt-edge-balances-mapping This data comes from Salt Edge's [/api/v5/accounts](https://docs.saltedge.com/account_information/v5/#accounts-attributes) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example values: `currency: "EUR"`, `currentAmount: 2410.55`, `availableAmount: 2380.00`, `updatedAt: "2024-08-12T14:33:00Z"`. | Meld Field | Description | SaltEdge Response Field | | :-------------- | :---------------------------------- | :---------------------- | | currency | The currency of the account balance | currency\_code | | currentAmount | The current amount in the account | balance | | availableAmount | The available amount in the account | extra.available\_amount | | updatedAt | The last time balances were updated | updated\_at | # Salt Edge Identifiers Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge/salt-edge-identifiers-mapping This data comes from Salt Edge's [/api/v5/accounts](https://docs.saltedge.com/account_information/v5/#accounts-extra) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example values: `international.iban: "GB29NWBK60161331926819"`, `international.bic: "NWBKGB2L"`, `bacs.accountNumber: "31926819"`, `bacs.sortCode: "601613"`. Salt Edge does not provide US ACH (`ach.*`) or Canadian EFT (`eft.*`) identifiers. Identifier coverage varies per institution within the `extra` object. | Meld Field | Description | SaltEdge Response Field | | :------------------- | :--------------------------------------------------------------------------------------------------- | :---------------------- | | ach | ACH data object | | |    accountNumber | The account number | *None* | |    routingNumber | The account routing number. Reference [Routing number](https://en.wikipedia.org/wiki/Routing_number) | *None* | | eft | EFT data object | | |    accountNumber | The account number | *None* | |    institutionNumber | The account institution number | *None* | |    branchNumber | The institution branch number | *None* | | international | International data object | | |    iban | The International Bank Account Number (IBAN) for the financial account | extra.iban | |    bic | The Bank Identifier Code (BIC) for the financial account | extra.swift | | bacs | The BACS data object | | |    accountNumber | BACS account number | extra.account\_number | |    sortCode | BACS sort code | extra.sort\_code |
# Salt Edge Owners Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge/salt-edge-owners-mapping This data comes from Salt Edge's [/api/v5/holder\_info](https://docs.saltedge.com/account_information/v5/#holder_info-show) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example values: `names: ["Jane Doe"]`, `addresses.data: { street: "10 Downing St", city: "London", region: "Greater London", postalCode: "SW1A 2AA", country: "GB" }`, `emails.data: ["jane.doe@example.com"]`. Salt Edge does not indicate whether an address, email, or phone number is the owner's primary record. Meld defaults `addresses.primary`, `emails.primary`, and `phoneNumbers.primary` to `UNKNOWN`. | Meld Field | Description | SaltEdge Response Field | | :--------------- | :---------------------------------------------------- | :---------------------------------------------------- | | addresses | The address(es) associated with this owner | addresses | |    data | The address data object | | |       street | The street and residence number | street | |       city | The city | city | |       region | The region/state | state | |       postalCode | The postal or zip code | post\_code | |       country | The ISO 3166-1 alpha-2 country code | country\_code | |    primary | Indicates if this is the owner's primary residence | Does not provide.
Meld will default to `UNKNOWN` | | emails | The email(s) associated with this owner | emails | |    data | The email address | | |    primary | Indicates if this is the owner's primary email | Does not provide.
Meld will default to `UNKNOWN` | | names | The name(s) of this owner | names | | phoneNumbers | The phone number(s) associated with this owner | phone\_numbers | |    data | The phone number | | |    primary | Indicates if this is the owner's primary phone number | Does not provide.
Meld will default to `UNKNOWN` | # Salt Edge to Meld Overall Mappings Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge/salt-edge-to-meld-mappings You can always compare the raw responses from Salt Edge in the `serviceProviderDetails` section of a financial account. [Financial Account Base Fields](#financial-account-base-fields) [Balances](./salt-edge-balances-mapping) [Identifiers](./salt-edge-identifiers-mapping) [Owners](./salt-edge-owners-mapping) [Transactions](./salt-edge-transactions-mapping) [Normalized Account Types and Subtypes](#normalized-account-types-and-subtypes) ## Financial Account Base Fields This data comes from SaltEdge's [/api/v5/accounts](https://docs.saltedge.com/account_information/v5/#accounts-attributes) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint.
Meld Field Description SaltEdge Response Field
name The account name name
truncatedAccount\ Number The last 4 digits of the account number extra.account\_number\ If extra.account\_number is null then:\*\ extra.iban\ *If extra.iban is null then:*\ extra.bban\ *If extra.bban is null then:*\ extra.cards
status The real-time status of the account extra.status\
Possible values:
- `active`
- `inactive`
- `unauthorized` * If null:\_ `active`
type The type of the account. Mapped to a Meld standardized type nature
subtype The subtype of the account. Mapped to a Meld standardized subtype * None\_. Meld subtype\ determined from SaltEdge type.
## Normalized Account Types and Subtypes # Salt Edge Transactions Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge/salt-edge-transactions-mapping This data comes from Salt Edge's [/api/v5/transactions](https://docs.saltedge.com/account_information/v5/#transactions-list) endpoint and is returned from Meld's [/bank-linking/transactions](/api-reference/bank-linking/transactions/bank-linking-transactions-search) endpoint. Example values: `amount: -22.40`, `currency: "EUR"`, `status: "POSTED"`, `transactionDate: "2024-08-10"`, `postedDate: "2024-08-11"`.
Meld Field Description SaltEdge Response Field
amount The amount of the transaction amount
currency The currency used in the transaction currency\_code
payee The recipient of the transaction description
status The status of the transaction status
transactionDate The date and time the transaction was made made\_on
postedDate The date and time the transaction was posted extra.posting\_date\ *If extra.posting\_date is null then:*\ made\_on
# Bank Linking Sandbox Testing Guide Source: https://docs.meld.io/docs/bank-linking/testing-and-debugging/bank-linking-sandbox-testing-guide # Testing Information This page lists the test institutions and credentials each bank linking service provider exposes in their sandbox environment. Use it to validate end-to-end connection flows in your integration before pointing at production. Service providers have sandboxes that you can use to make test connections and fetch test data. Each service provider has its own set of test institutions. Some service provider sandboxes have limitations, for example Finicity's does not return investment data. Testing the repair flow is not possible in sandbox. Repair flows can only be exercised against production connections that have entered a broken state. You can also find current test credentials in your Meld dashboard sandbox if a provider updates them. ## Plaid Plaid has a very comprehensive sandbox environment that can be used to test a lot of features. You can check out their [testing guide](https://plaid.com/docs/sandbox/test-credentials/), or see the test credentials below. | Service Provider | Test Institutions | OAuth? | Username | Password | Pin | | :--------------- | :---------------- | :--------------- | ----------- | :---------- | :---------------- | | Plaid | Any | some (ex. Chase) | `user_good` | `pass_good` | `credential_good` | You can select any institution in Plaid's sandbox and use those credentials to complete a connection. Most institutions will connect using OAuth with Plaid's fake bank behind the scenes, called Platypus Bank. Additionally, Plaid makes it very clear when you are in their sandbox by displaying "You are currently in Sandbox mode." at the bottom of their widget, which is not seen in their Production environment. ![](https://files.readme.io/dbf66c9-image.png) In addition to Sandbox and Production, Plaid also has a third environment, Development. Plaid's development environment has several restrictions, such as not supporting the `OWNERS` product (which Plaid calls Identities), and needing to connect with real bank credentials. As such, Meld recommends using Plaid's sandbox for testing, and Plaid's production when going live. ### Plaid Investments Testing Plaid's sandbox supports creating [custom users](https://plaid.com/docs/sandbox/user-custom/) with custom data that Plaid will return when you successfully connect. This can be useful when wanting to test a specific feature with fake data, such as Investments from Plaid. Simply create a user with a custom username and pass that username instead of `user_good` when creating a connection. ## Mesh Mesh's sandbox supports the same list of institutions as in production (with a few exceptions). What differentiates sandbox connections from production connections are the credentials entered. These test credentials are institution-dependent, but are provided as "hints" in the Mesh widget. The username/password combinations are typically either of the following: | Service Provider | Test Institution Type | Username | Password | | :--------------- | :----------------------------- | ------------- | :--------------- | | Mesh | Username/Password integrations | SandboxUser | SandboxPassword | | Mesh | API key integrations | SandboxApiKey | SandboxSecretKey | If there are any exceptions, they will still be displayed as "hints" in the Mesh widget. ### Mesh Investments Testing Mesh's sandbox supports testing investments with the above test credentials. ## Finicity Finicity does not support using the same customer ID for connecting to a test account and to a real bank account, so if you see the error "Existing Finicity customer is not a test customer so cannot connect with test institutions" while trying to make a connection, use a different customer ID. Only Finicity's production environment supports connecting to real bank accounts. Finicity has a set of passwords that return data from various types of financial accounts. You can check out their [testing guide](https://developer.mastercard.com/open-banking-us/documentation/test-the-apis/), or see the test credentials below. | Service Provider | Test Institutions | OAuth | Username | Password | Account Types | | :--------------- | :---------------------------------------------- | :---- | -------- | :----------- | :---------------------------------------------------------------------------------------------- | | Finicity | `FinBank Profiles -A` or `FinBank Profiles - B` | Yes | anything | `profile_02` | Savings, IRA, 401k, Credit Card | | Finicity | `FinBank Profiles -A` or `FinBank Profiles - B` | Yes | anything | `profile_03` | Checking, Personal Investment, 401K, Roth, Savings (Joint Account owners) | | Finicity | `FinBank Profiles -A` or `FinBank Profiles - B` | Yes | anything | `profile_04` | Checking, 403B, 529, Rollover, Mortgage | | Finicity | `FinBank Profiles -A` or `FinBank Profiles - B` | Yes | anything | `profile_05` | Checking, Investment, Stocks, UGMA, UTMA (Joint Account owners) | | Finicity | `FinBank Profiles -A` or `FinBank Profiles - B` | Yes | anything | `profile_06` | Checking, Retirement, KEOGH, 457, Credit Card | | Finicity | `FinBank Profiles -A` or `FinBank Profiles - B` | Yes | anything | `profile_07` | Checking, Stocks, CD, Investment Tax-Deferred, Employee Stock | | Finicity | `FinBank Profiles -A` or `FinBank Profiles - B` | Yes | anything | `profile_08` | Checking, Primary Savings, Money Market, 401A, Line of credit | | Finicity | `FinBank Profiles -A` or `FinBank Profiles - B` | Yes | anything | `profile_09` | Checking, Savings, Checking Failed Report. Errors returned in the report include 102, 103, 185. | Finicity also returns the same set of data via an OAuth flow. Before you can connect this way, you must first register for FinBank OAuth through your Finicity account. | Service Provider | Test Institutions | OAuth | Username | Password | Account Types | | :--------------- | :---------------- | :---- | ------------ | :----------- | :---------------------------------------------------------------------------------------------- | | Finicity | `FinBank OAuth` | No | `profile_02` | `profile_02` | Savings, IRA, 401k, Credit Card | | Finicity | `FinBank OAuth` | No | `profile_03` | `profile_03` | Checking, Personal Investment, 401K, Roth, Savings (Joint Account owners) | | Finicity | `FinBank OAuth` | No | `profile_04` | `profile_04` | Checking, 403B, 529, Rollover, Mortgage | | Finicity | `FinBank OAuth` | No | `profile_05` | `profile_05` | Checking, Investment, Stocks, UGMA, UTMA (Joint Account owners) | | Finicity | `FinBank OAuth` | No | `profile_06` | `profile_06` | Checking, Retirement, KEOGH, 457, Credit Card | | Finicity | `FinBank OAuth` | No | `profile_07` | `profile_07` | Checking, Stocks, CD, Investment Tax-Deferred, Employee Stock | | Finicity | `FinBank OAuth` | No | `profile_08` | `profile_08` | Checking, Primary Savings, Money Market, 401A, Line of credit | | Finicity | `FinBank OAuth` | No | `profile_09` | `profile_09` | Checking, Savings, Checking Failed Report. Errors returned in the report include 102, 103, 185. | ### Finicity Investments Testing Testing Finicity investments is very difficult using Finicity's test accounts due to the lack of test data available. Therefore Meld recommends using real investment accounts to test Finicity investments. Note that Finicity does enforce a third party screen before the bank's login page, as shown below: ![](https://files.readme.io/eb82585-image.png) Sometimes, Finicity's sandbox may connect very quickly and cause an error. In this case, after entering the login username and password, you may see a blank white screen. This only happens when connecting test accounts (not real bank accounts) and can be ignored. ## MX Before testing with MX, note that MX's sandbox supports connecting to both real and test bank accounts, although the number of real connections is very limited. If you wish to connect to a test account, make sure you select a test institution. MX has a very comprehensive sandbox environment that can be used to test a lot of features. You can check out their [testing guide](https://docs.mx.com/testing/guides/testing), or see the test credentials below. | Service Provider | Test Institutions | OAuth? | Username | Password | | :--------------- | :---------------------------------------------------------------------------------------- | :----- | -------- | :------- | | MX | MX Bank | No | `mxuser` | anything | | MX | MX Bank (OAuth) | Yes | `mxuser` | anything | | MX | [MXCU (OAuth)](https://docs.mx.com/resources/test-platform/mxcu/testing-oauth-with-mxcu/) | Yes | `mxuser` | anything | MX does finish their aggregation within their widget, so if you are connecting an account with a lot of transactions, the widget may stay open a little longer than you expect. ### MX Investments Testing MX does have some investment data that can be accessed with the above test credentials, but it's pretty spotty. Meld recommends testing MX Investments with real investment accounts. ## Akoya Akoya has several test accounts in their sandbox environment that can be used to test different data types. You can check out their [testing guide](https://docs.akoya.com/docs/mikomo), or see the test credentials below. The only test institution Akoya supports is `Mikomo`. Meld recommends testing with `mikomo_9` as some Akoya test accounts return data in a different format from what is documented, which can cause errors. | Service Provider | Test Institutions | Username | Password | Account Types | | :--------------- | :---------------- | ------------- | :------------ | :----------------------------------------------------------- | | Akoya | `Mikomo` | `mikomo_1` | `mikomo_1` | Investment (I, TODI) | | Akoya | `Mikomo` | `mikomo_2` | `mikomo_2` | Investment (HSA, I, TODI) | | Akoya | `Mikomo` | `mikomo_3` | `mikomo_3` | Investment (I, IRRL, TIC, IRAB, IRA, TODJ, ROTH, TODI, 401K) | | Akoya | `Mikomo` | `mikomo_5` | `mikomo_5` | Investment (J, HSA, ROTH) | | Akoya | `Mikomo` | `mikomo_6` | `mikomo_6` | Investment (HSA, TODI, IRA, IRRL, NONP, NRMA, 401K) | | Akoya | `Mikomo` | `mikomo_7` | `mikomo_7` | Checking, Commercial Loan, Credit Card, 401k, J | | Akoya | `Mikomo` | `mikomo_9` | `mikomo_9` | Checking | | Akoya | `Mikomo` | `mikomo_10` | `mikomo_10` | Checking, College Savings, Brokerage, CD, Savings | | Akoya | `Mikomo` | `mikomo_11` | `mikomo_11` | Checking | | Akoya | `Mikomo` | `mikomo_2023` | `mikomo_2023` | Checking, College Savings, Brokerage, CD, Savings | ### Akoya Investments Testing Akoya supports testing investments through certain test users, such as mikomo\_2023. However some fields that would appear with real investment data may be missing in the test data. ## Yodlee Yodlee has several test accounts in their sandbox environment that can be used to test different data types. You can check out their [testing guide](https://developer.yodlee.com/), or see the test credentials below. | Service Provider | Test Institutions | Provider Id | Username | Password | Account Types | MFA Response | | :--------------- | :-------------------- | :---------- | --------------------- | :------------ | :---------------------------------------------- | :---------------------- | | Yodlee | `DAG Site` | 16441 | `YodTest.site16441.1` | `site16441.1` | Checking, Savings | N/A | | Yodlee | `DAG Site` | 16441 | `YodTest.site16441.2` | `site16441.2` | Checking, Savings, Credit Card, Loan, Brokerage | N/A | | Yodlee | `DAG Site SecurityQA` | 16486 | `YodTest.site16486.1` | `site16486.1` | Security Question Login | `Texas` and `w3schools` | | Yodlee | `DAG Site Multilevel` | 16442 | `YodTest.site16442.1` | `site16442.1` | OTP Login | `123456` | ### Yodlee Investments Testing Yodlee does have some investment data that can be accessed with the above test credentials, but it's pretty spotty. Meld recommends testing Yodlee Investments with real investment accounts. ## Salt Edge Salt Edge does not have a testing guide, but does have a [guide](https://docs.saltedge.com/account_information/v5/#dynamic_registration) to follow before going live. Besides that, their test credentials can be found below: | Service Provider | Test Institutions | Username | Password | Pin | | :--------------- | :------------------------------------------------------------------------- | ---------- | :------- | :------ | | Salt Edge | Anything starting with `Fake Bank` except for `Fake Bank with Client Keys` | `username` | `secret` | `12345` | ## Salt Edge Partners Salt Edge Partners does not have a testing guide, but does have a [guide](https://docs.saltedge.com/partners/v1/#before_going_live) to follow before going live. Besides that, their test credentials can be found below: | Service Provider | Test Institutions | Username | Password | Pin | | :----------------- | :--------------------------- | ---------- | :------- | :------ | | Salt Edge Partners | `Fake Bank with Client Keys` | `username` | `secret` | `12345` | # Activity Log Source: https://docs.meld.io/docs/bank-linking/testing-and-debugging/debugging-activity-log This feature is in beta. The Activity Log is Meld's searchable audit trail of every request, webhook, and event that flows between your application, Meld, and the underlying service providers. Developers use it to trace what happened on a specific connection, replay webhook delivery, and pinpoint where in the pipeline something went wrong. The Activity Log is currently in beta. Behavior and search parameters may change. One powerful tool to help you debug potential issues or see the paper trail is Meld's [Activity Log](/api-reference/beta/activity-log/activity-log-search). The activity log registers all activities performed by you or performed by Meld on behalf of you and presents them in an easy to trace manner. Some of the major types of activities captured by the activity log are: 1. [Webhooks](#track-webhooks): 1. Webhooks that the provider sends Meld. 2. Webhooks that Meld sends you. 2. [API calls Meld makes to a service provider on your behalf and the response](#raw-service-provider-data). # How to Access the Activity Log You can interact with the Activity Log in two ways: * **Meld dashboard** — log in to your Meld dashboard and navigate to the Activity Log section to browse recent activity in the UI. * **API** — call the [Activity Log Search](/api-reference/beta/activity-log/activity-log-search) and [Activity Log Details](/api-reference/beta/activity-log/activity-log-details-get) endpoints to query programmatically. # How to Use the Activity Log 1. Start by hitting Meld's [Activity Log endpoint](/api-reference/beta/activity-log/activity-log-search) with whatever search parameters you would like. The date params (`start` and `end`) are required but all other params are optional. The activities are ordered chronologically with the most recent at the top, and every activity has a timestamp. You should typically filter using an `actions` and either a `connectionId` or `financialAccountId`. If you don't have either of those, then a `customerId` or `externalCustomerId` can work as well, but those searches will take longer. Examples of `actions` you should filter by are: | Action | Meaning | | :------------------------------------- | :-------------------------------------------------------------------------------------- | | `INBOUND_REQUEST` | An API call you made to Meld | | `OUTBOUND_REQUEST_SERVICE_PROVIDER` | An API call that Meld made to a service provider on your behalf, including the response | | `INBOUND_REQUEST_WEBHOOK` | A webhook sent from the service provider to Meld | | `OUTBOUND_REQUEST_WEBHOOK` | A webhook that Meld sent to you | | `INSTITUTION_CONNECTION_IMPORT_FAILED` | A notification that an import for a particular connection has failed | 2. Once you find a particular activity that you would like more information about, grab the key of the activity and head to Meld's [Activity Log Details endpoint](/api-reference/beta/activity-log/activity-log-details-get). Pass in the key as a query param and you will now see all the details for that particular activity. These details include the request and response headers, the request and response body, the response code, and more. You can also see the raw response Meld received from a particular service provider here. This can help debug if for example data that looks suspicious is due to Meld's transformation or is the exact data that Meld received from the provider. # Use Cases Here are some potential use cases of the activity log: ## Track Webhooks See which webhooks were sent for a particular connection from the provider to Meld as well as from Meld to you. This can help if you might've missed a webhook, or if you want to see the full reason a connection has an issue (which typically comes to Meld via webhook). ## Raw Service Provider Data See the raw data the provider sent Meld to evaluate if an issue with a particular connection is on Meld's side or the provider's side. ## Why a Connection is No Longer Active See why a connection moved from `ACTIVE` to another status. This often uses one of the two examples above to debug the root cause for why this happened. ## Why an Import Failed When you import connections over to Meld, you will receive webhooks for connections that were successfully imported. However, to see which connections failed to be imported, use the activity log and search using the `INSTITUTION_CONNECTION_IMPORT_FAILED` action, which will also include the Meld reason for the import failing. One trick is to find the action for the import failing then look at the details of the action that happened right before it which will likely have the service provider's error, if there is one. The activity log is a very powerful tool. Please make your requests as specific as possible by leveraging the query params, or there may be too much data to return or to be of use. # Testing and Debugging Source: https://docs.meld.io/docs/bank-linking/testing-and-debugging/index Developer tools for testing and debugging bank linking connections Meld provides two primary tools to help you validate connections before going live and diagnose issues after launch. * [Sandbox Testing Guide](/docs/bank-linking/testing-and-debugging/bank-linking-sandbox-testing-guide): test institutions, credentials, and tips for testing with Meld and each bank linking service provider in sandbox. * [Activity Log](/docs/bank-linking/testing-and-debugging/debugging-activity-log): search and review raw connection-level data exchanged between Meld and service providers. # Bank Linking Webhooks Source: https://docs.meld.io/docs/bank-linking/webhook-events-bank-linking This page lists every webhook event Meld sends over the lifetime of a bank linking connection. For each event you'll see when it fires and what your application should do on receipt. For the end-to-end flow and the order webhooks arrive in, see [When is your data available?](/docs/bank-linking/get-connection-data/when-is-your-data-available). Make sure your webhook endpoint is configured before triggering connections in production. See [Setting Up Webhooks](/docs/bank-linking/onboarding/step-5-setting-up-webhooks) for setup, authentication, and retry behavior. ## Attributes All webhook events have the same attributes. | Key | Type | Description | | :---------- | :------------- | :----------------------------------------------------------------------------------- | | `eventType` | String | Type of event | | `eventId` | String | Meld's unique identifier for the event | | `timestamp` | OffsetDateTime | The date and time the event was created | | `accountId` | String | Account id | | `profileId` | String | Id of the webhook profile responsible for this event to be sent | | `version` | Date | API version of the payload | | `payload` | Object | An object containing additional information of the event depending on the event type | # Connection Webhooks See the [API Reference](/api-reference/bank-linking) for the full payload schema of each connection webhook. ### `BANK_LINKING_CONNECTION_COMPLETED` **When it fires:** Sent when the customer completes the connection with their institution either in the initial connect flow or a reconnect flow. This does not imply that financial account data has completed aggregating or is ready to be fetched, just that the customer has completed the widget flow. **What to do:** Record that the customer finished the widget so you can update your UI. Wait for `BANK_LINKING_ACCOUNTS_UPDATED` before attempting to fetch product data. The `BANK_LINKING_CONNECTION_STATUS_CHANGE` webhook will be sent in tandem with this one (with the `newStatus` being `ACTIVE`). However, if a connection goes from a broken state to `ACTIVE` without the user needing to reconnect (i.e. sometimes connections may break due to temporary institution unavailability but are eventually resolved on their own), then only `BANK_LINKING_CONNECTION_STATUS_CHANGE` will be issued containing both the old status and the new `ACTIVE` status. This is because `BANK_LINKING_CONNECTION_COMPLETED` is reserved solely for when the customer completes the widget flow. ```json theme={null} { "eventType": "BANK_LINKING_CONNECTION_COMPLETED", "eventId": "NGoTSGJYpd3cLv1iyHWSw9", "timestamp": "2021-12-09T21:58:29.329186Z", "accountId": "QfdzpX3te1cDaviihXA4D5dA6ghnT3", "profileId": "W9kBgKB7afMxYoYuy3rJuZ", "version": "2021-10-28", "payload": { "requestId": "91d7fb355e319532b178acc8a7ae1a69", "customerId": "Qg56BnxskKokRV8rVo21Af3T4k6QKY", "externalCustomerId": "testCustomer1", "connectionId": "WQ4mBt3BEX2cmhCvSPyfTu", "institutionId": "Ntj3AwgdridJc2rtfeheku", "institutionName": "Chase", "serviceProviderDetails": { "serviceProvider": "PLAID", "serviceProviderConnectionId": "NXrZpLwd1eUQQqymvl9nSkkV8DpeLLUWoXdZx" } } } ``` ### `BANK_LINKING_CONNECTION_STATUS_CHANGE` **When it fires:** Sent whenever a connection changes status. This webhook essentially encompasses all of the other connection status related webhooks and will be sent alongside them. Any service provider errors that occur post-completion will trigger this webhook as well, either due to an attempted aggregation failing or if the service provider notifies Meld via their own webhooks that a connection is in a broken state. This can happen when fetching products following an initial connection, or during daily/forced refreshes of existing connections. **What to do:** This is the webhook to subscribe to for all connection-health tracking. Inspect `newStatus` and `newStatusReason` and route the customer to the [repair flow](/docs/bank-linking/manage-connection-status/repairing-connections) if the connection is now `RECONNECT_REQUIRED` or `CUSTOMER_ACTION_REQUIRED`. The `serviceProviderDetails` will provide additional information on what caused the connection to end up in such state. A list of potential reasons and actions to take for each of them can be found on the [Connection Statuses and Errors](/docs/bank-linking/get-connection-data/connection-statuses-and-errors) page. If a connection requires a reconnect, you can still request information (such as balances, transactions, etc.) about the connection and you will receive the data from the last time the connection was still operational. ```json theme={null} { "eventType": "BANK_LINKING_CONNECTION_STATUS_CHANGE", "eventId": "UhK4iqKP57FeLPgqBi8EUk", "timestamp": "2023-08-22T20:54:02.960285Z", "accountId": "W9ju7atKP5Jaf5RPweGeis", "profileId": "W9kBgKB7afMxYoYuy3rJuZ", "version": "2023-07-31", "payload": { "customerId": "WQ4KqLo3SRSPeTVjxxwo2o", "externalCustomerId": "20230822-67a", "connectionId": "WQ4KqLuBYYTYHi2YATXYdB", "institutionId": "W4AAAFPgUVB85QnQDu5xhU", "oldStatus": "ACTIVE", "oldStatusReason": null, "newStatus": "RECONNECT_REQUIRED", "newStatusReason": "LOGIN_REQUIRED", "activeDuplicateConnectionId": null, "serviceProviderDetails": { "environment": "sandbox", "error": { "error_type": "ITEM_ERROR", "error_code": "ITEM_LOGIN_REQUIRED", "error_message": "the login details of this item have changed (credentials, MFA, or required user action) and a user login is required to update this information. use Link's update mode to restore the item to a good state", "display_message": null, "request_id": null, "causes": null, "status": 400, "documentation_url": null, "suggested_action": null }, "item_id": "NXrZpLwd1eUQQqymvl9nSkkV8DpeLLUWoXdZx", "serviceProvider": "PLAID", "serviceProviderConnectionId": "NXrZpLwd1eUQQqymvl9nSkkV8DpeLLUWoXdZx", "webhook_code": "ERROR", "webhook_type": "ITEM" } } } ``` # Financial Account Webhooks See the [API Reference](/api-reference/bank-linking) for the full payload schema of each account webhook. ### `BANK_LINKING_ACCOUNTS_UPDATING` **When it fires:** Sent once the connect flow is completed and the accounts are in the process of aggregating. At this time, only basic account info is available. Also sent during subsequent daily/forced refreshes when the accounts are in the process of aggregating again. **What to do:** Treat this as a signal that aggregation has started — you can show a loading state but should not yet fetch product data. Wait for `BANK_LINKING_ACCOUNTS_UPDATED` before calling product endpoints. *Financial Account fields for this event type:* | Key | Type | Description | | :--------------------------- | :--------------- | :--------------------------------------------------------------------------------------------------------- | | `financialAccounts` | Array of objects | | |     `id` | String | Unique identifier for the financial account | |     `name` | String | If present, the name of the account as provided by the institution. | |     `truncatedAccountNumber` | String | If present, the last 4 digits of the account number, sometimes referred to as the account mask. | |     `newAccount` | Boolean | Indicates if the account was newly added or is an existing account getting updated (i.e. during a refresh) | ```json theme={null} { "eventType": "BANK_LINKING_ACCOUNTS_UPDATING", "eventId": "38oVQntsutXnfzn6fZ3mxW", "timestamp": "2022-09-06T17:42:49.703620Z", "accountId": "W9ju7atKP5Jaf5RPweGeis", "profileId": "W9kBgKB7afMxYoYuy3rJuZ", "version": "2022-08-29", "payload": { "customerId": "WGti3bpsethEWYJrm2icuo", "externalCustomerId": "20220906-206", "connectionId": "WGu7we7dVTCnVkjJrpzVNm", "institutionId": "29KL1L2iYUV3zUCoSHhwvr", "requestId": "1f97b51f3771328bc07187c8a2c1fe30", "financialAccounts": [ { "id": "WGti3dJFc6A9r1pYcBmE6e", "name": "Plaid Checking", "truncatedAccountNumber": "0000", "newAccount": true }, { "id": "WGti3cCpeiAtWxL6pFQPTg", "name": "Plaid Saving", "truncatedAccountNumber": "1111", "newAccount": true } ], "serviceProviderDetails": { "serviceProvider": "PLAID", "serviceProviderConnectionId": "NXrZpLwd1eUQQqymvl9nSkkV8DpeLLUWoXdZx" } } } ``` ### `BANK_LINKING_ACCOUNTS_UPDATED` **When it fires:** Sent once aggregation has completed for the accounts belonging to the connection. Products are now available to be fetched for the accounts. This webhook should arrive shortly after the `BANK_LINKING_ACCOUNTS_UPDATING` webhook, but sometimes products may take a while to aggregate for certain institutions. Note that if no products have been updated, then the `financialAccounts` object in this webhook will be empty. **What to do:** Fetch the listed products for each financial account. See [Retrieving Connection Data](/docs/bank-linking/get-connection-data/retrieving-connection-data) and the per-account [aggregation overview](/docs/bank-linking/get-connection-data/when-is-your-data-available). *Financial Account fields for this event type:* | Key | Type | Description | | :------------------ | :--------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `financialAccounts` | Array of objects | | |     `id` | String | Unique identifier for the financial account | |     `products` | Array of Strings | The products that were updated for the account. This list will never include `TRANSACTIONS` because their updates are handled separately within the transactions webhooks. Products are considered updated even if nothing changed, and it is solely meant to indicate that the updated products were fetched from the service provider. | ```json theme={null} { "eventType": "BANK_LINKING_ACCOUNTS_UPDATED", "eventId": "Ja2HXauns8LHPYR1NhEbrh", "timestamp": "2022-09-06T17:42:51.853020Z", "accountId": "W9ju7atKP5Jaf5RPweGeis", "profileId": "W9kBgKB7afMxYoYuy3rJuZ", "version": "2022-08-29", "payload": { "customerId": "WGti3bpsethEWYJrm2icuo", "externalCustomerId": "20220906-206", "connectionId": "WGu7we7dVTCnVkjJrpzVNm", "institutionId": "29KL1L2iYUV3zUCoSHhwvr", "requestId": "1f97b51f3771328bc07187c8a2c1fe30", "successfullyAggregatedAt": "2023-08-22T19:48:55Z", "financialAccounts": [ { "id": "WGti3cCpeiAtWxL6pFQPTg", "products": [ "BALANCES", "IDENTIFIERS", "OWNERS" ] }, { "id": "WGti3dJFc6A9r1pYcBmE6e", "products": [ "BALANCES", "IDENTIFIERS", "OWNERS" ] } ], "serviceProviderDetails": { "serviceProvider": "PLAID", "serviceProviderConnectionId": "NXrZpLwd1eUQQqymvl9nSkkV8DpeLLUWoXdZx" } } } ``` ### `BANK_LINKING_ACCOUNTS_REMOVED` **When it fires:** Sent when individual financial accounts belonging to a connection have been deleted. This can occur when the customer revokes access to individual accounts or they close a financial account with their bank altogether. This webhook will always be issued following the `BANK_LINKING_CONNECTION_DELETED` event, as a connection deletion implies all of its financial accounts are deleted as well. **What to do:** Remove the listed financial account IDs from your local data store and stop displaying them to the customer. ```json theme={null} { "eventType": "BANK_LINKING_ACCOUNTS_REMOVED", "eventId": "GUVQ5N9tQpLFALKpRevt6C", "timestamp": "2021-12-09T19:09:23.283931Z", "accountId": "QfdzpX3te1cDaviihXA4D5dA6ghnT3", "profileId": "W9kBgKB7afMxYoYuy3rJuZ", "version": "2021-10-28", "payload": { "customerId": "WGuEzKYdKukkUFxmzVDUvm", "externalCustomerId": "20221031-34", "connectionId": "WGuEzMHgUFDzumu6zVRyw5", "institutionId": "W9kgBBLULSJFSKTwCpb5Gf", "financialAccounts": [ { "id": "WGuEzL3AC3sF155f2ASWpu" }, { "id": "WGuEzHzJzEiDTBhCcniN4D" }, { "id": "WGuEzHoQA6UgG1Nq1qQ1Qy" }, { "id": "WGuEzHswGCZCn2bS1N9Mkh" } ], "serviceProviderDetails": { "serviceProvider": "FINICITY", "serviceProviderConnectionId": "6026862732" } } } ``` # Transaction Webhooks See the [API Reference](/api-reference/bank-linking) for the full payload schema of each transaction webhook. ### `BANK_LINKING_TRANSACTIONS_AGGREGATED` **When it fires:** Captures net transactional effects for each financial account belonging to the connection. Sent after each aggregation in which transactions are added or modified. **What to do:** If you cache transactions locally, re-fetch the affected financial accounts using the `oldestTransactionUpdatedSearchKey` so you pick up new and updated transactions. See the [webhook flow](/docs/bank-linking/get-connection-data/when-is-your-data-available) for more details. ```json theme={null} { "eventType": "BANK_LINKING_TRANSACTIONS_AGGREGATED", "eventId": "FEfSjVLXjM81CkG9ejkWBvu3Kb6ND5", "timestamp": "2022-01-31T22:05:04.068964Z", "accountId": "QfdzpX3te1cDaviihXA4D5dA6ghnT3", "version": "2022-03-09", "payload": { "customerId": "WGv8FTrTroky93FYwAJNXc", "externalCustomerId": "testCustomer", "connectionId": "WGumJ72d21SDCyma5Ax51k", "requestId": "1f97b51f3771328bc07187c8a2c1fe30", "successfullyAggregatedAt": "2023-08-22T19:48:55Z", "isPartial": false, "financialAccounts": [ { "financialAccountId": "WGv8FSGqrwvgVtqY5CARE9", "oldestTransactionUpdatedSearchKey": "3ztRY2PPYjEyzuuAfttKi9omi7UbsLxTbPTDV8MDpPhg9trwrsLZkHCJtb7QPs7cq35DJWSGqnRPP8UxGDmy2o2DRZJnG826oCuePdcnkAaCFyUEqc2QLeteoqJ3xBKetdApYwoFtA39ZpMxyL77LPxZEyUsWa4NWpxAEMQUHV9pSqZcgG", "numTransactionsAdded": 3, "numInvestmentTransactionsAdded": 0, "numTransactionsUpdated": 1, "numInvestmentTransactionsUpdated": 0, "transactionIdsRemoved": [], "investmentTransactionIdsRemoved": [] }, { "financialAccountId": "WGv8FSsWTrgAXBtS2cVPWR", "oldestTransactionUpdatedSearchKey": "3ztRY2PPYjEyzuuAfttKi9omi7UbsLxTbPTDV8MDpPhg9trwrsLZkHCJtb7QPs7cq35DJWSGqnRPP8UxGDmy2o2DRZJnG826oCuePdcnkAaCFyUEqc2QLeteoqJ3xBKetdApYwoFtA39ZpMxyL77LPxZEyUsWa4NWpxAEMQUHV9pSqZcgG", "numTransactionsAdded": 0, "numInvestmentTransactionsAdded": 0, "numTransactionsUpdated": 17, "numInvestmentTransactionsUpdated": 0, "transactionIdsRemoved": [], "investmentTransactionIdsRemoved": [] } ], "serviceProviderDetails": { "environment": "sandbox", "item_id": "zqyK7Abwv7cXJnr5Xk9xS31lMgqdXDuo3ZDxb", "new_transactions": 8, "serviceProvider": "PLAID", "webhook_code": "INITIAL_UPDATE", "webhook_type": "TRANSACTIONS" } } } ``` ### `BANK_LINKING_HISTORICAL_TRANSACTIONS_AGGREGATED` **When it fires:** Captures net historical transaction effects for each financial account belonging to the connection. Sent at most once per connection after historical transactions (transactions older than 30 days) have been aggregated. By default, historical transaction aggregation is initiated after the connection is made. **What to do:** Fetch the older transactions if your application surfaces extended history. Check `historicalAggregationSucceeded` per account — some institutions do not support extended history and will report `false` with an explanatory `historicalAggregationError`. See the [webhook flow](/docs/bank-linking/get-connection-data/when-is-your-data-available) for more details. ```json theme={null} { "eventType": "BANK_LINKING_HISTORICAL_TRANSACTIONS_AGGREGATED", "eventId": "FEfSjVLXjM81CkG9ejkWBvu3Kb6ND5", "timestamp": "2022-01-31T22:05:04.068964Z", "accountId": "QfdzpX3te1cDaviihXA4D5dA6ghnT3", "version": "2022-03-09", "payload": { "customerId": "WGv8FTrTroky93FYwAJNXc", "externalCustomerId": "testCustomer", "connectionId": "WGumJ72d21SDCyma5Ax51k", "requestId": "1f97b51f3771328bc07187c8a2c1fe30", "successfullyAggregatedAt": "2023-08-22T19:48:55Z", "isPartial": false, "financialAccounts": [ { "financialAccountId": "WGv8FSGqrwvgVtqY5CARE9", "oldestTransactionUpdatedSearchKey": "3ztRY2PPYjEyzuuAfttKi9omi7UbsLxTbPTDV8MDpPhg9trwrsLZkHCJtb7QPs7cq35DJWSGqnRPP8UxGDmy2o2DRZJnG826oCuePdcnkAaCFyUEqc2QLeteoqJ3xBKetdApYwoFtA39ZpMxyL77LPxZEyUsWa4NWpxAEMQUHV9pSqZcgG", "numTransactionsAdded": 84, "numInvestmentTransactionsAdded": 0, "numTransactionsUpdated": 1, "numInvestmentTransactionsUpdated": 0, "transactionIdsRemoved": [], "investmentTransactionIdsRemoved": [], "historicalAggregationSucceeded": false, "historicalAggregationError": { "error": { "message": "Member's institution does not support extended transaction history.", "status": "bad_request", "type": "bad_request_error" }, "serviceProvider": "MX" } }, { "financialAccountId": "WGv8FSsWTrgAXBtS2cVPWR", "oldestTransactionUpdatedSearchKey": "3ztRY2PPYjEyzuuAfttKi9omi7UbsLxTbPTDV8MDpPhg9trwrsLZkHCJtb7QPs7cq35DJWSGqnRPP8UxGDmy2o2DRZJnG826oCuePdcnkAaCFyUEqc2QLeteoqJ3xBKetdApYwoFtA39ZpMxyL77LPxZEyUsWa4NWpxAEMQUHV9pSqZcgG", "numTransactionsAdded": 150, "numInvestmentTransactionsAdded": 0, "numTransactionsUpdated": 1, "numInvestmentTransactionsUpdated": 0, "transactionIdsRemoved": [], "investmentTransactionIdsRemoved": [], "historicalAggregationSucceeded": false, "historicalAggregationError": { "error": { "message": "Member's institution does not support extended transaction history.", "status": "bad_request", "type": "bad_request_error" }, "serviceProvider": "MX" } } ], "serviceProviderDetails": { "serviceProvider": "FINICITY", "serviceProviderConnectionId": "6026862732" } } } ``` # Gets details for activities Source: https://docs.meld.io/api-reference/beta/activity-log/activity-log-details-get /openapi/beta-20231219.json get /activity-log/details # Search activity log Source: https://docs.meld.io/api-reference/beta/activity-log/activity-log-search /openapi/beta-20231219.json get /activity-log Retrieve logged activity filtered by various parameters over a date range. See [here](https://docs.meld.io/docs/debugging-activity-log) for information on how to use the activity log and why. # Get crypto purchase limits for fiat currencies Source: https://docs.meld.io/api-reference/service-providers/service-provider/service-providers-limits-fiat-currency-purchase-get /openapi/serviceproviders-20231219.json get /service-providers/limits/fiat-currency-purchases Returns a list of limits (minimums and maximums) in terms of fiat currencies tokens for buying crypto. # Get KYC levels for fiat currencies Source: https://docs.meld.io/api-reference/service-providers/service-provider/service-providers-limits-kyc-fiat-level-get /openapi/serviceproviders-20231219.json get /service-providers/limits/kyc-fiat-levels Returns a list of KYC limits in terms of fiat currencies tokens for buying crypto. # Get all webhook event types that can be sent Source: https://docs.meld.io/api-reference/webhooks/profile/webhooks-events-get /openapi/webhooks-20231219.json get /notifications/webhooks/event-types # Create a new webhook profile Source: https://docs.meld.io/api-reference/webhooks/profile/webhooks-profiles-create /openapi/webhooks-20231219.json post /notifications/webhooks # Get an existing webhook profile Source: https://docs.meld.io/api-reference/webhooks/profile/webhooks-profiles-get /openapi/webhooks-20231219.json get /notifications/webhooks/{webhookProfileId} # Search webhook profiles Source: https://docs.meld.io/api-reference/webhooks/profile/webhooks-profiles-search /openapi/webhooks-20231219.json get /notifications/webhooks Returns a list of webhook profiles that match the query parameters. The webhook profiles are sorted by name. # Test an existing webhook profile Source: https://docs.meld.io/api-reference/webhooks/profile/webhooks-profiles-test /openapi/webhooks-20231219.json post /notifications/webhooks/{webhookProfileId}/test Sends a WEBHOOK_TEST event to the URL in the profile # Update an existing webhook profile Source: https://docs.meld.io/api-reference/webhooks/profile/webhooks-profiles-update /openapi/webhooks-20231219.json patch /notifications/webhooks/{webhookProfileId} # Account Verification Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/account-verification Account verification confirms that a user owns and has access to a specific bank account. Developers use it most commonly before initiating an ACH transaction so that they know the user is authorized to move money from the bank account in question. ## How it works Account verification involves creating a connection that requests the [OWNERS](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-owners) and [IDENTIFIERS](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-identifiers) products: * Through the **OWNERS** product you get the user's name and address. * Through the **IDENTIFIERS** product you get the user's account number and routing number. Once the connection completes, you have verified the user's identity and access to the bank account and can perform an ACH transaction. Every bank linking connection has a Meld `customerId` associated with it. Use that same `customerId` when creating a payments customer so that the bank account and payments customer are linked. # Android App to App Authentication Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/app-to-app-authentication/android-app-to-app-authentication This page outlines the steps to load the Meld bank linking widget inside your Android mobile app using a WebView, and to support App to App authentication with banks that have implemented it. ## Before you begin * An Android project (Kotlin) where you can integrate a `WebView`. * A domain you control for [Android App Links](https://developer.android.com/training/app-links). * A Meld API key and the ability to call `/bank-linking/connect/start` to obtain a widget URL. # Project Setup ## Step 1: App Links Setup App Links are the Android equivalent of iOS's universal links. ### App Links Step 1: Setup Android app links following this [guide](https://developer.android.com/training/app-links). * Here is an example of what **AndroidManifest.xml** will look like: ```xml XML theme={null} ``` ### App Links Step 2: Redirect URL * We need to pass the `redirectUrl` in Meld's `/bank-linking/connect/start` endpoint. * Ensure that the redirectUrl parameter is an HTTP URL and not a deep link. It should specifically be formatted as an Android App Link. For detailed guidance on creating App Links, refer to the instructions provided in this [guide](https://developer.android.com/training/app-links). ### App Links Step 3: Opening the Widget URL in a Webview * Add the following settings to the webview to give the proper permissions: ```kotlin Kotlin theme={null} private lateinit var webView : WebView private lateinit var childWebView : WebView override fun onCreate(savedInstanceState: Bundle?) { // ... webView = findViewById(R.id.webView); // please name accordingly // Using a custom WebView Client as we have to override the url loading functionality webView.webViewClient = CustomWebViewClient() webView.settings.apply { domStorageEnabled = true javaScriptEnabled = true javaScriptCanOpenWindowsAutomatically =true } // This webView will handle the login screen childWebView = findViewById(R.id.childWebView); // please name accordingly childWebView.webViewClient = CustomWebViewClient() childWebView.settings.apply { domStorageEnabled = true javaScriptEnabled = true javaScriptCanOpenWindowsAutomatically =true } // This will handle the incoming meld events like connect_complete, handover, cancel etc // Follow step #3 for the implementation webView.addJavascriptInterface(JsObject(), "Android") } ``` * Here’s the definition of the **CustomWebViewClient** class: ```kotlin Kotlin theme={null} private inner class CustomWebViewClient :WebViewClient() { override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean { // we'll add the code here based on different providers val parsedUri = request?.url val uriString = parsedUri.toString() return false } } ``` # Enable Visibility To enable app to app authentication, you may need to declare specific apps as visible to your app by adding queries in your **AndroidManifest.xml** file. For example, to enable visibility for the Chase app, include the following: ```xml XML theme={null} // this will make the chase app visible to your app ``` The above example is specific to the Chase app. If you wish to enable app to app authentication with additional apps, include the respective package entries for each additional app in the `` section. ## Important Considerations for QUERY\_ALL\_PACKAGES Permission From Android 11 (API level 30), the **QUERY\_ALL\_PACKAGES** permission is restricted to apps that target API level 30 or later on devices running Android 11 or newer. To use this permission, your app must fulfill specific criteria outlined by the Google Play policy. This includes having a core purpose that requires visibility of all installed apps on the device and providing a sufficient justification as to why alternative, less intrusive methods of app visibility would not enable the app’s policy-compliant, user-facing functionality. In some cases, Android may restrict visibility of certain apps to your app. If your app needs to access a comprehensive list of all installed apps on the device, include the **QUERY\_ALL\_PACKAGES** permission in the Android manifest. Be aware that if you intend to publish your app on Google Play, the usage of this permission is subject to [Google Play’s approval process](https://support.google.com/googleplay/android-developer/answer/10158779). # Provider Specific Code Depending on which provider(s) your app uses, you will have to configure some provider specific code in order to make app to app authentication works. This section details what that code is. ## MX 1. To handle the MX service provider appropriately, incorporate the following code snippet within the **shouldOverrideUrlLoading** function: ```kotlin Kotlin theme={null} override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean { // ... val isMessageFromConnect = uriString.startsWith("meldapp://") || uriString.startsWith("atrium://") if (isMessageFromConnect) { // 2. Handle OAuth redirects if the URL is related to OAuth return mxHandler(parsedUri, view); } } fun mxHandler(uri: Uri?, view: WebView?): Boolean { if (uri?.path == "/oauthRequested") { try { val mxMetaData = JSONObject(uri?.getQueryParameter("metadata")) val oauthURL = mxMetaData.getString("url") val oauthPage = Uri.parse(oauthURL) if (isAppLink(context, oauthURL)) { val intent = Intent(Intent.ACTION_VIEW, oauthPage) view?.context?.startActivity(intent) } else { view?.loadUrl(oauthPage.toString()) } } catch (err: Exception) { Log.e("MX:Error with OAuth URL", err.message!!) } } return true } ``` ## Plaid and Finicity 1. For Plaid and Finicity, integrate the following code into the **shouldOverrideUrlLoading** function. This addition functions as an “else” case subsequent to the “if” condition handling the MX provider: ```kotlin Kotlin theme={null} val isMessageFromConnect = uriString.startsWith("://") || uriString.startsWith("atrium://") if (isMessageFromConnect) { // 2. Handle OAuth redirects if the URL is related to OAuth return mxHandler(parsedUri, view); } else { // 3. Handle HTTP(S) URLs (open in browser or load in WebView) if (parsedUri?.scheme == "https" || parsedUri?.scheme == "http" || uriString != widgetUrl) { if(parsedUri.toString().contains("") && uriString.contains("oauth_state_id")) { val oauthStateId = parsedUri?.getQueryParameter("oauth_state_id") val newWidgetUrl = Uri.parse(widgetUrl).buildUpon().appendQueryParameter("plaidStateId", oauthStateId) webView.visibility = View.GONE childWebView.visibility = View.VISIBLE childWebView.loadUrl(uriString) return true } if (isAppLink(context, parsedUri.toString())) { // checking if an app is available to handle this url view?.context?.startActivity(Intent(Intent.ACTION_VIEW, parsedUri)) return true } if(uriString != widgetUrl) { webView.visibility = View.GONE childWebView.visibility = View.VISIBLE childWebView.loadUrl(uriString) return true } } } ``` ```kotlin Kotlin theme={null} // Helper function to check if there is an app that can handle this URL fun isAppLink(context: Context, url: String): Boolean { val uri = Uri.parse(url) val intent = Intent(Intent.ACTION_VIEW, uri) val resolveInfo = context.packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) return resolveInfo != null } ``` # Listening to Front End Events 1. Currently these events work for only Plaid & Finicity, not MX. After a successful OAuth process Meld’s widget will emit a handful of events which will need to be appropriately handled when control returns back to the app of origin. 2. You can listen to couple of pre-defined events which can be used to detect if the authentication process is completed, canceled, or if there was an error. ```kotlin Kotlin theme={null} enum class ConnectHandlerResponse(val message: String) { /** * Emitted when the widget initializes */ INIT("[meld-connect]init"), /** * Emitted at various times with debug information: * When your customer cancels an integrated service provider's embedded widget * Whenever your customer navigates to another screen within an integrated service provider's embedded widget */ DEBUG("[meld-connect]debug"), /** * when the widget initializes */ INITIALIZE("[meld-connect]init"), /** * Emitted when the widget encounters an error * Will be accompanied by query-string metadata, containing details and reason keys, as available */ ERROR("[meld-connect]error"), /** * Emitted once the call to /connect/complete has finished * Will be accompanied by query-string metadata, containing: * institutionId: Meld's institution ID for the institution your customer chose to connect with * institutionName: The name of the same institution * accountCount: The number of accounts your customer has connected * accounts: Where available, an array of connected accounts including details such as name, type, and mask; null otherwise */ COMPLETE("[meld-connect]connect_complete"), /** * Emitted when the connect has been completed */ HANDOVER("[meld-connect]handover"), /** * Emitted when your customer cancels the widget */ CANCEL("[meld-connect]cancel") } private inner class JsObject { private val TAG = "MeldActivity" @JavascriptInterface fun postMessage( event : String) { if (!event.isNullOrEmpty() && event.startsWith("[meld-connect]")){ var eventData : String? = null val event = if (event.contains("?")) { val splitResult = event.split("?") eventData = splitResult[1] splitResult.firstOrNull() } else event when(connectHandlerResponseFindValueOf(event.orEmpty())){ ConnectHandlerResponse.DEBUG -> {} ConnectHandlerResponse.INITIALIZE -> {} ConnectHandlerResponse.ERROR -> {} ConnectHandlerResponse.COMPLETE -> { Log.d("connectComplete", eventData.toString()) } ConnectHandlerResponse.HANDOVER -> { // close the Meld Connect Widget setResult(RESULT_OK) finish() } ConnectHandlerResponse.CANCEL -> { setResult(RESULT_CANCELED) finish() } else -> {} } } else { Log.d(TAG, "postMessage: MELD: got unknown response: $event") } } } ```
# App to App Authentication Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/app-to-app-authentication/index App to App Authentication lets a user who launches the Meld bank picker inside your mobile app jump directly into the bank's native app to log in (using biometrics, for example), then return to your app once authenticated. Developers integrate it to improve conversion and user trust during bank linking. Meld supports this flow for Plaid, MX, and Finicity, with provider-specific code on each platform. ## How it works When enabled, a user who sees Meld's bank picker in your mobile app gets redirected to the bank's app to log in, then back to your app of origin afterward. This makes authentication easier and more secure — the user can log in with biometrics rather than typing a username and password, and they can feel more secure that their bank credentials are protected because they are using the bank's own app. If the user does not have their bank app installed on their phone, the bank login screen is launched in a webview instead. ## Supported banks App to app authentication is currently only supported for Chase, as it is a feature developed by each bank. Other banks are following suit and will have this feature available in the future. | | Plaid | Finicity | MX | | :------ | :------------ | :------------ | :---------- | | iOS | **Supported** | **Supported** | Coming Soon | | Android | **Supported** | Coming Soon | Coming Soon | ## Platform guides The subpages describe how to implement App to App Authentication in iOS Native (Swift) and Android Native (Kotlin) mobile apps. If you are using other technologies such as Flutter or React Native, you will need to adapt the code samples to fit your framework. * [iOS App to App Authentication](/docs/bank-linking/bank-linking-quickstart/app-to-app-authentication/ios-app-to-app-authentication) * [Android App to App Authentication](/docs/bank-linking/bank-linking-quickstart/app-to-app-authentication/android-app-to-app-authentication) # iOS App to App Authentication Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/app-to-app-authentication/ios-app-to-app-authentication This page outlines the steps to load the Meld bank linking widget inside your iOS mobile app using a `WKWebView`, and to support App to App authentication with banks that have implemented it. ## Before you begin * An iOS project in Xcode where you can integrate a `WKWebView`. * Access to your website's server to host the `apple-app-site-association` file. * An Apple Developer account to create and use App IDs and provisioning profiles. * A Meld API key and the ability to call `/bank-linking/connect/start` to obtain a widget URL. # Project Setup ## Step 1: Universal Links Setup Universal Links provide a seamless user experience by connecting a URL to a specific part of your iOS app, bypassing the browser. This guide is designed for beginners and explains how to set up and use Universal Links in your iOS applications. ### Universal Links Step 1: Enable Associated Domains * Create App ID: Log into the [Apple Developer Portal](https://developer.apple.com/), go to “Certificates, Identifiers & Profiles,” and create a new App ID for your project. Ensure that you enable “Associated Domains". * Update Xcode Project: In Xcode, open your project settings, select your target, and go to the “Signing & Capabilities” tab. Add the “Associated Domains” capability by clicking the “+” icon and selecting it from the list. ### Universal Links Step 2: Configure Your Website * Create Apple-App-Site-Association File: You need to create a JSON file named apple-app-site-association (without any file extension) and host it at the root of your HTTPS-enabled web server or in the .well-known\ subdirectory. The content should look like this: ```json JSON theme={null} com.apple.developer.associated-domains applinks:*.yourdomain.com ``` * Replace TEAMID.BUNDLEID in the above code with your actual team ID and bundle identifier. The paths array should contain the URLs you want to link to your app. * Host the File: Ensure the apple-app-site-association file is accessible via HTTPS without any redirects at https\://``.com/apple-app-site-association. ### Universal Links Step 3: Configure Associated Domains in Xcode * Modify Entitlements: In your Xcode project, under the “Associated Domains” capability you added earlier, add an entry: applinks:`` ### Universal Links Step 4: Handle Universal Links in Your App * Update AppDelegate: Open AppDelegate.swift and implement the following\ function to handle incoming universal links: ```swift Swift theme={null} func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { if userActivity.activityType == NSUserActivityTypeBrowsingWeb, let url = userActivity.webpageURL { // Handle URL and parse parameters if needed return true } return false } ``` This code checks if the incoming activity type is a web browsing session and prints the URL, but you’ll likely want to redirect the user to the appropriate part of your app based on the URL. ### Universal Links Step 5: Test Your Universal Links * Prepare Your Device: To test on a real device, ensure the device has the provisioning profile that includes the Associated Domains capability. * Test the Link: Send yourself an email or message with the link you configured earlier (e.g., https\://``.com/path/to/content). Tap the link on your device, and it should open your app directly, bypassing the browser. ### Troubleshooting Tips * Verify Your AASA File: Use online tools like Apple’s AASA validator to ensure your apple-app-site-association file is correctly configured and accessible. * Check Console Logs: If the universal link is not working, check the device’s console logs for any errors related to universal links or associated domains. By following these steps, you should be able to implement and test Universal Links in your iOS apps effectively, providing users with a direct link into your app from web content. ## Step 2: WKWebview Setup ### WKWebview Step 1: Adding to View Controller * Start by setting up a standard Xcode project and add a WKWebView to your ViewController. Ensure you configure the WKWebView’s javaScriptCanOpenWindowsAutomatically setting to true. ```swift Swift theme={null} let preferences = WKPreferences() preferences.javaScriptCanOpenWindowsAutomatically = true let webConfiguration = WKWebViewConfiguration() webConfiguration.preferences = preferences webConfiguration.userContentController.add(self, name: meldMessageHandler) webView = WKWebView(frame: .zero, configuration: webConfiguration) webView.navigationDelegate = self webView.uiDelegate = self ``` ### WKWebview Step 2: Load the Widget URL * After adding the WKWebView to your ViewController as described in the previous step, load the Widget URL, which you can obtain from the connect/start API. Once the Widget URL is successfully loaded into the WebView, it will display the bank picker interface. Ensure proper error handling and network checks are in place to manage the loading process smoothly. ### WKWebview Step 3: Updating the Code * Add the following code: ```swift theme={null} let appScheme = "meldapp://" // Your apps custom scheme let atriumScheme = "atrium://" // MX atrium's default scheme (deprecated) let mxScheme = "mx://" // MX default scheme ``` ### WKWebview Step 4: Manage Navigation Requests * To capture and address specific use-cases for specific service providers, developers need to manage all navigation requests within the WKWebView. This requires implementing the WKNavigationDelegate protocol, which provides methods to track and control the loading of web content. A crucial method to implement is **webView(\_:decidePolicyFor:decisionHandler:)**. This method determines whether a navigation request should proceed, be canceled, or handled differently based on the request’s characteristics. # Provider Specific Code Depending on which provider(s) your app uses, you will have to configure some provider specific code in order to make app to app authentication work. This section details what that code is. ## MX 1. This code needed since MX uses the custom URL schema and relies on loading the content inside the MX widget. It also extracts the url parts and loads inside the MX widget and decision handler cancels the navigating requests. ```swift theme={null} let isPostMessageFromMX = (urlString?.hasPrefix(appScheme) == true || urlString?.hasPrefix(atriumScheme) == true || urlString?.hasPrefix(mxScheme) == true) if (isPostMessageFromMX){ let urlc = URLComponents(string: urlString ?? "") let path = urlc?.path ?? "" // there is only one query param ("metadata") with each url, so just grab the first let metaDataQueryItem = urlc?.queryItems?.first if path == "/oauthRequested" { handleOauthRedirect(payload: metaDataQueryItem) } decisionHandler(.cancel) return } ``` 2. handleOauthRedirect handles the APP to APP or APP to web passing ```swift theme={null} func handleOauthRedirect(payload: URLQueryItem?) { let metadataString = payload?.value do { if let json = try JSONSerialization.jsonObject(with: Data(metadataString.utf8), options: []) as? [String: Any] { if let url = json["url"] as? String { print("Intercepted Request inside:handleOauthRedirect \(url)") let options: [UIApplication.OpenExternalURLOptionsKey: Any] = [ .universalLinksOnly: true, // Try to open the app via universal link ] UIApplication.shared.open(URL(string: url)!, options: options) { (success) in if success { print("The URL was successfully opened.") } else { print("The URL could not be opened.") self.presentWebView(with: URL(string: url)!) } } } } } catch let error as NSError { print("Failed to parse payload: \(error.localizedDescription)") } } ``` 3. The web view loaded in the case of MX is handled as follows in **webView(\_:decidePolicyFor:decisionHandler:)**: ```swift theme={null} let urlString = navigationAction.request.url?.absoluteString currentURLOpened = navigationAction.request.url let currentURLOpenedString = currentURLOpened?.absoluteString if(currentURLOpenedString!.contains("{{universal-link}}/?status")){ self.popupWebViewControllerExternal?.dismiss(animated: true) popupWebView = nil } ``` ## Plaid and Finicity 1. To manage HTTPS requests from Plaid and Finicity, handle the navigation within the **webView(\_:decidePolicyFor:decisionHandler:)** method of the WKNavigationDelegate. Use the decision handler to allow navigation when these requests occur, enabling the WKWebView to securely load the relevant webpage. 2. **Plaid specific:** Upon completing the Plaid process, it is essential to pass the **oauth\_state\_id** to the Meld backend. This should be done along with the initial URL that was saved earlier in the process. This specific code block is only for Plaid, and does not need to be implemented for Finicity. ```swift theme={null} if url.absoluteString.contains("{{universal-link}}/?oauth_state_id=") { let urlString = url.absoluteString self.dismiss(animated: true, completion: nil) if let range = urlString.range(of: "=") { let substring = urlString[range.upperBound...] Let request = URLRequest(url: URL(string: connectUrlToSave!.absoluteString + "&plaidStateId=\(substring)")!) webView.load(request)// Output: 11834d6d-7d09-459b-b757-068697a5a07b } decisionHandler(.cancel) return } ``` All of the code under this message will need to be implemented for both Plaid and Finicity. Complete implementation for the **webView(\_:decidePolicyFor:decisionHandler:)**: ```swift theme={null} // handle navigation requests extension ConnectViewController: WKNavigationDelegate { // Capture request URLs public // Intercept all requests before they are loaded func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { let urlString = navigationAction.request.url?.absoluteString currentURLOpened = navigationAction.request.url let currentURLOpenedString = currentURLOpened?.absoluteString if(currentURLOpenedString!.contains("{{universal-link}}/?status")){ self.popupWebViewControllerExternal?.dismiss(animated: true) popupWebView = nil } let isPostMessageFromMX = (urlString?.hasPrefix(appScheme) == true || urlString?.hasPrefix(atriumScheme) == true || urlString?.hasPrefix(mxScheme) == true) if (isPostMessageFromMX){ let urlc = URLComponents(string: urlString ?? "") let path = urlc?.path ?? "" // there is only one query param ("metadata") with each url, so just grab the first let metaDataQueryItem = urlc?.queryItems?.first if path == "/oauthRequested" { handleOauthRedirect(payload: metaDataQueryItem) } decisionHandler(.cancel) return } else{ if let url = navigationAction.request.url { // If needed, modify the request or block it if url.absoluteString.contains("{{universal-link}}/?oauth_state_id=") { let urlString = url.absoluteString self.dismiss(animated: true, completion: nil) if let range = urlString.range(of: "=") { let substring = urlString[range.upperBound...] Let request = URLRequest(url: URL(string: connectUrlToSave!.absoluteString + "&plaidStateId=\(substring)")!) webView.load(request)// Output: 11834d6d-7d09-459b-b757-068697a5a07b } decisionHandler(.cancel) return } // Check if the URL scheme is something other than http or https if url.absoluteString.contains("closemyapp") { // Close the WebView or dismiss the view controller self.dismiss(animated: true, completion: nil) } } } decisionHandler(.allow) } ``` # Listening to Front End Events 1. This section applies to all service providers. Handle the UI requests so that app to web request can be handled internally. ```swift theme={null} / handle UI requests extension ConnectViewController: WKUIDelegate { public func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures _: WKWindowFeatures) -> WKWebView? { // create a new webView guard webView == view else { debugPrint("MELD: popup cannot launch another webview") return nil } let popup = WKWebView(frame: webView.bounds, configuration: configuration) popup.uiDelegate = self popup.navigationDelegate = self popupWebView = popup DispatchQueue.main.async { let popupWebViewController = PopupWebViewController(with: popup) popupWebViewController.delegate = self self.present(popupWebViewController, animated: true) } return popupWebView } public func webViewDidClose(_ webView: WKWebView) { // handle closing. make sure to pop if it's the root webView // root view if view == webView { delegate?.cancel() return } // close child view } ``` 2. Meld emits events to inform the status of the handshake.intercept. ```swift theme={null} private enum ConnectHandlerResponse: String { case debug = "[meld-connect]debug" // debug information case initialize = "[meld-connect]init" // when the widget initializes case error = "[meld-connect]error" // when the widget encounters an error case complete = "[meld-connect]connect_complete" // once the call to /connect/complete has finished case handover = "[meld-connect]handover" // when the connect has been completed case cancel = "[meld-connect]cancel" // when your customer cancels the widget } public func userContentController(_: WKUserContentController, didReceive message: WKScriptMessage) { guard message.name == meldMessageHandler else { return } guard let body = message.body as? String, body.hasPrefix("[meld-connect]") else { return // ignore } let parts = body.split(separator: "?", maxSplits: 1) guard parts.count <= 2 else { return } guard let response = ConnectHandlerResponse(rawValue: String(parts[0])) else { return // ignore } let payload: Any? if parts.count >= 2, let payloadData = parts[1].data(using: .utf8) { payload = try? JSONSerialization.jsonObject(with: payloadData) } else { payload = nil } switch response { case .debug: // Debug. Ignore debugPrint("MELD:debug") case .initialize: // Initialization. Ignore. debugPrint("MELD:init") case .error: // An Error debugPrint("MELD:error") case .complete: // Complete — linking is done debugPrint("MELD:complete") DispatchQueue.main.async { [weak self] in debugPrint("got payload \(payload ?? "")") self?.delegate?.handover() } case .handover: // DONE. Dismiss and proceed debugPrint("MELD:handover") DispatchQueue.main.async { [weak self] in debugPrint("got payload \(payload ?? "")") self?.delegate?.handover() } case .cancel: debugPrint("MELD:cancel") // Cancel. Dismiss DispatchQueue.main.async { [weak self] in self?.delegate?.cancel() } } } ```
# Bank Linking Glossary Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/bank-linking-glossary This page defines the terms used throughout Meld's Bank Linking documentation so that you and your team can speak the same language as the API. Use the diagram at the bottom to see how each entity relates to the others. ## Terms * **Account** — Your Meld account, which represents your company. * **Customer** — A customer you create with Meld. A single customer usually represents one person or business and can have multiple bank connections. Track customers using `customerId` (a Meld-generated unique ID), or pass your own ID in as `externalCustomerId`. * **Service Provider** — The entity responsible for completing a connection between a user and a bank account. Plaid, Finicity, MX, Yodlee, Salt Edge, Salt Edge Partners, and Akoya are all service providers. * **Connection** — A link between a service provider and a login to a bank. A connection can have several financial accounts associated with it (for example, a checking account and a savings account). A connection is tied to a particular institution. * **Institution** — A financial entity where money is stored. A bank is the most common type of institution; credit unions are another. * **Financial Account** — Includes checking accounts, savings accounts, 401(k)s, IRAs, credit card accounts, and so on. A single connection can have multiple financial accounts, and individual accounts may support different sets of products. For example, credit card accounts don't support identifiers (i.e. routing and account numbers). ## Relationship diagram ![Relationship diagram showing Account, Customer, Connection, Institution, and Financial Account relationships](https://files.readme.io/ba3de38-image.png) # Balances Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-balances The Balances product returns the cached or realtime balances for a customer's financial account. Use it to determine if an account has sufficient funds before using it as a funding source for a transaction. **API Reference** * Search financial accounts: `GET /bank-linking/accounts` * Get a specific financial account: `GET /bank-linking/accounts/{accountId}` * Refresh customer accounts: `POST /bank-linking/connections/{connectionId}/refresh` ## Overview There are two types of balance data: current balance and available balance. * **Current balance** — The total amount of money in the account. This does not mean it is all available to spend. Some funds may be from deposits or checks that have not cleared yet. * **Available balance** — The total amount of money in the account that is available for your customer to spend. For fraud detection and insufficient-funds (NSF) prevention use cases, use available balance to determine fund sufficiency — it represents the predicted balance net of any pending transactions. Current balance does not take pending transactions into account. In some cases, a financial institution may not return available balance information. If that happens, you can calculate it by starting with the current balance, then using the transactions endpoint to detect pending transactions and adjusting the balance accordingly. ## Balance data When calling the financial accounts endpoint, you can access the `currency`, `currentAmount`, and `availableAmount` for that account. See [Retrieving Connection Data](/docs/bank-linking/get-connection-data/retrieving-connection-data) for an example. ## Sample response ```json theme={null} { "id": "W9ja25UmrZF25xbG22wdd", "accountId": "W9kbkRme9VT2iz6qdsaww2", "customerId": "W9ja24edcTHBks8h2e2dww", "institutionId": "Ntj3AwgdridJc2rasdssdd", "institutionName": "Chase", "status": "ACTIVE", "type": "DEPOSITORY", "subtype": "CHECKING", "name": "Checking", "truncatedAccountNumber": "1234", "identifiers": { // data }, "balances": { "currency": "USD", "currentAmount": 3578.41, "availableAmount": 3578.41, "updatedAt": "2022-03-08T23:19:23Z" }, "owners": [ // data ], "serviceProviderDetails": [ // data ] } ``` ## Account balance updates Account balance data is constantly updated as new transactions are made and previous transactions are processed. Use Meld's webhooks to receive updates on the data — see [Webhooks Quickstart](/docs/bank-linking/onboarding/step-5-setting-up-webhooks/webhook-quickstart) for setup details. # Identifiers Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-identifiers The Identifiers product returns the account number, routing number, and related identifiers for a customer's financial account. Use it to ensure that subsequent transactions (such as ACH transfers) end up in the right place. **API Reference** * Search financial accounts: `GET /bank-linking/accounts` * Get a specific financial account: `GET /bank-linking/accounts/{accountId}` ## Overview Each checking or savings account is assigned a unique account number and routing number. * **Account number** — Identifies your customer's unique bank account. Typically a 10–12 digit string that tells the bank which account to withdraw funds from or deposit funds in. * **Routing number** — A nine-digit number that financial institutions use to identify other financial institutions. It indicates which bank holds your customer's financial account. ## Use cases There are many use cases that require a customer's account and routing number, such as: * Your app allows users to cash out a credit balance to a bank account. * Your app allows users to pay you directly from their checking or savings account. * Your app allows an end-to-end ACH transfer. Account authentication lets you request a customer's account number and bank identification number (such as routing number, for US accounts) in a seamless and validated way without having to look up and type their account numbers. Account authentication generally supports checking and savings accounts only. ## Account authentication data When calling the financial account endpoint, you can access the `accountNumber` and `routingNumber` for that account. See [Retrieving Connection Data](/docs/bank-linking/get-connection-data/retrieving-connection-data) for an example. **Tokenized Account and Routing Numbers** Service providers may provide "tokenized" account and routing numbers when working with institutions that support an OAuth flow. This may be due to the provider's or institution's policies. A tokenized account number is not the actual number but a representation of it. These tokenized numbers work identically to normal account numbers. The digits returned in the `mask` field continue to reflect the actual account number, not the tokenized account number. For this reason, when displaying account numbers in your UI, always use the `mask` rather than truncating the account number. If a user revokes permissions to your app, the tokenized numbers will continue to work for ACH deposits but not withdrawals. ## Sample response ```json theme={null} { "id": "W9ja25UmrZF25xbG22wdd", "accountId": "W9kbkRme9VT2iz6qdsaww2", "customerId": "W9ja24edcTHBks8h2e2dww", "institutionId": "Ntj3AwgdridJc2rasdssdd", "institutionName": "Chase", "status": "ACTIVE", "type": "DEPOSITORY", "subtype": "CHECKING", "name": "Checking", "truncatedAccountNumber": "1234", "identifiers": { "ach": { "accountNumber": "00100000034561234", "routingNumber": "023000797" }, "eft": null, "international": null, "bacs": null }, "balances": { // data }, "owners": [ // data ], "serviceProviderDetails": [ // data ] } ``` # Investments Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-investments The Investments product lets developers connect to a customer's investment (brokerage) account and retrieve the current holdings and trade history. Use it for wealth-management, portfolio tracking, and financial-advice use cases. **API Reference** * Search investment holdings: `GET /bank-linking/investment-holdings` * Search investment transactions: `GET /bank-linking/investment-transactions` ## Overview Investments refers to an investment account (also known as a brokerage account) where one has various stocks and other securities such as mutual funds, CDs, bonds, and ETFs. The investment product lets you connect an investment account and get back all relevant information about the account. There are two types of investment data: investment holdings and investment transactions. See [Retrieving Connection Data](/docs/bank-linking/get-connection-data/retrieving-connection-data) for an example on fetching the data. ## Investment Holdings Investment Holdings refers to the current holdings of an investment account — what securities are currently in the account, along with other relevant information such as the number of shares, the cost basis, and the current value of the holding. This is similar to the Balances product but is a breakdown, since it includes all the securities in an account. ### Sample response for a single holding ```json theme={null} { "id": "WQ57LwnwzUo5WMM5Fi7nn2", "accountId": "W9kbkRme9VT2iz6qRT3212", "customerId": "WQ43wxs1bjUhohY8ekjeje", "financialAccountId": "WQ44KLVNHt9ARfp7aRWCC4", "symbol": "RKLB", "description": "Rocket Lab USA", "quantity": 2.00000000000000000000, "currentValue": 11.12000000000000000000, "costBasis": 11.11000000000000000000, "currency": "USD", "isin": "US7731221062", "cusip": "773122106", "type": "stock", "serviceProviderDetails": [ // data ] } ``` ## Investment Transactions Investment Transactions are the trade history of an account. For example, if the account owner purchased 10 shares of Google in a single trade, that would be a transaction. Unlike holdings, which are a snapshot of the present, transactions are the history of the account and give insight into how the current holdings were assembled. ### Sample response for a single transaction ```json theme={null} { "id": "WQ57iFJpqR524P82QZrFiW", "accountId": "W9kbkRme9VT2iz6qRT3212", "customerId": "WQ43wxs1bjUhohY8ekjeje", "financialAccountId": "WQ44KLVNHt9ARfp7aRWCC4", "amount": 11.11000000000000000000, "currency": "USD", "description": "Rocket Lab USA Limit Buy", "transactionDate": "2023-06-16T00:00:00Z", "postedDate": "2023-06-16T00:00:00Z", "symbol": "RKLB", "quantity": 2.00000000000000000000, "costBasis": 5.56000000000000000000, "type": "buy", "status": "POSTED", "serviceProviderDetails": [ // data ] } ``` Investments support varies by service provider. See the provider-specific mapping pages under [Partner Details](/docs/bank-linking/partner-details/index) for what is supported. # Owners Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-owners The Owners product returns the account-identity data recorded with a customer's financial institution — full name, phone number, mailing address, and email address. Developers use it to verify customer identity for ACH, KYC, and risk workflows. **API Reference** * Search financial accounts: `GET /bank-linking/accounts` * Get a specific financial account: `GET /bank-linking/accounts/{accountId}` ## Overview Account identity information helps you verify your customer's identity by accessing the data recorded with their financial institution. ## Use cases Common use cases for verifying a customer's identity: * Verify an account before initiating an ACH transaction. * Verify depository-type accounts (such as checking and savings) and credit-type accounts (such as credit cards). * Verify the information provided by your customer with a trusted source when opening a new account. * Verify customers you have identified as higher risk based on data such as email address, location, financial institution, or activity patterns. ## Account identity data When calling the financial account endpoint, you can access the account's identity data for that account. See [Retrieving Connection Data](/docs/bank-linking/get-connection-data/retrieving-connection-data) for an example. Specifying `OWNERS` as a product in `/bank-linking/connect/start` does not guarantee account-owners data will be available for every account in the connection. Some accounts do not have owners data, and others may only have a subset of the owner fields. ## Sample response ```json theme={null} { "id": "W9ja25UmrZF25xbG22wdd", "accountId": "W9kbkRme9VT2iz6qdsaww2", "customerId": "W9ja24edcTHBks8h2e2dww", "institutionId": "Ntj3AwgdridJc2rasdssdd", "institutionName": "Chase", "status": "ACTIVE", "type": "DEPOSITORY", "subtype": "CHECKING", "name": "Checking", "truncatedAccountNumber": "1234", "identifiers": { // data }, "balances": { // data }, "owners": [ { "addresses": [ { "data": { "street": "123 Cherry Hill Lane", "city": "Palo Alto", "region": null, "postalCode": "94537-8038", "country": "USA", "full": "123 Cherry Hill Lane Palo Alto CA 94537-8038 USA" }, "primary": "UNKNOWN" } ], "emails": [ "gjones@hotmail.net" ], "names": [ "George Jones" ], "phoneNumbers": null } ], "serviceProviderDetails": [ // data ] } ``` # Processor Token Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-processor-token A processor token tokenizes sensitive information (such as account and routing numbers) between a bank linking provider and a payment service provider — for example, a Plaid–Checkout integration. Use a processor token when a downstream processor needs to act on a bank account without handling raw account details. **API Reference** * Create a processor token: `POST /bank-linking/accounts/{accountId}/processor-token` ## Overview Processor tokens are optional and are used to make calls to a payment service provider (for example, Checkout) on behalf of the bank linking provider (for example, Plaid) without needing to directly pass the sensitive data. For example, Plaid can authorize Checkout to pull money from a user's bank account by sending a processor token instead of the raw account details. * Processor tokens do not expire. * Processor tokens cannot be modified, but can be revoked at any time. * Meld currently supports processor tokens for **Plaid** and **MX**. Processor Token is treated differently than other products: it isn't requested in the initial call to `/bank-linking/connect/start`. It has its own dedicated endpoint. For Plaid-specific details, see [Plaid's processor documentation](https://plaid.com/docs/api/processors/). # Transactions Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-transactions The Transactions product returns a customer's account transaction history. Developers use it for cash-flow modeling, risk analysis, expenditure categorization, and similar use cases. **API Reference** * Search financial transactions: `GET /bank-linking/transactions` * Get a financial transaction: `GET /bank-linking/transactions/{transactionId}` ## Overview Transactional data helps you understand a customer's expenditure, timing, and frequency of purchases. Call the transactions endpoint to filter transactions. See [Retrieving Connection Data](/docs/bank-linking/get-connection-data/retrieving-connection-data) for an example. Transactions are typically only available for the following account types: * Depository accounts such as checking and savings. * Credit-type accounts, such as credit cards or student loan accounts. ## Fetching transactions See [Fetching Transaction Information](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/fetching-transaction-information) for how to fetch Meld transactions. ## Updating transactions Transactions can get updated, especially their status. See [When Is Your Data Available?](/docs/bank-linking/get-connection-data/when-is-your-data-available) for more. ## Data normalization Meld normalizes transaction data, including the type, signage, and status. See [Data Normalization](/docs/bank-linking/get-connection-data/data-normalization) for more information. ## Transaction days fetched by refresh type Meld loads a different period of transaction history depending on several factors — whether it is the first load (`INITIAL`), a subsequent refresh (`SUBSEQUENT`), or a historical aggregation (`HISTORICAL`). ### Initial loads For `INITIAL` loads (immediately following completing or reconnecting a connection), only a limited transaction history is available. Meld loads whatever the service provider has initially available: * Plaid: last 30 days * Finicity: last 180 days * MX: last 90 days * Other service providers: 30 days ### Historical loads After completing an `INITIAL` aggregation, Meld triggers a `HISTORICAL` aggregation with the service provider. Historical aggregations collect **2 years** of transaction history across all service providers. Historical transaction data typically takes longer to aggregate (and is billed separately by the service provider), which is why it is executed after the initial aggregation. Because of this, a secondary `BANK_LINKING_HISTORICAL_TRANSACTIONS_AGGREGATED` webhook is issued after the first `BANK_LINKING_TRANSACTIONS_AGGREGATED` webhook to indicate that historical transactions are now also available (as well as how many were loaded). Since each service provider varies on what is immediately available during the `INITIAL` load, the `HISTORICAL` load collects whatever remains up to 2 years (i.e., from 30 days to 2 years for Plaid, from 180 days to 2 years for Finicity, etc.). The numbers of transactions loaded are broken down by period and reflected in the aforementioned webhooks. ### Subsequent loads Following both the `INITIAL` and `HISTORICAL` loads, only the recent transaction history is aggregated daily to capture any new transactions or updates to existing transactions. These loads are considered `SUBSEQUENT` and only retrieve the last 20 days of transaction data. This is sufficient to capture recent transactions as well as updates to existing transactions (such as transactions transitioning from `PENDING` to `POSTED`, or cancellations of transactions). ## Recurring transactions Certain providers identify recurring transactions (such as a monthly subscription). For Plaid and MX specifically, recurring transactions in the transaction details object have `recurring=TRUE`, while all other transactions have `recurring=FALSE`. ## Sample response ```json theme={null} { "id": "W9ja24xnWMVFc1s7D3rewfw", "accountId": "W9kbkRme9VT2iz623efee", "customerId": "W9ja24edcTHBks8hz2cvvf", "financialAccountId": "W9ja25UmrZF25xb2E3WSQ", "amount": 2018.27, "currency": "USD", "description": "Payment to Chase card ending in 1234", "status": "POSTED", "category": "Fee", "transactionDate": "2022-02-28T11:00:00Z", "postedDate": "2022-02-28T11:00:00Z", "serviceProviderDetails": [ // data ] } ``` # Fetching Transaction Information Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/bank-linking-products/fetching-transaction-information This page explains the recommended strategies for fetching and updating transactions in your application after Meld signals new data is available. Pick the strategy that best matches how aggressive your refresh cadence is and how much extra data you are willing to fetch. ## Before you begin * You have an active bank linking connection. * You are subscribed to the `BANK_LINKING_TRANSACTIONS_AGGREGATED` webhook (see [Webhooks Quickstart](/docs/bank-linking/onboarding/step-5-setting-up-webhooks/webhook-quickstart)) or you are polling the connection's `successfullyAggregatedAt` timestamp. ## When to fetch Fetching and updating transactions follows a general process: 1. Wait for an indication that updates have occurred — either via the `BANK_LINKING_TRANSACTIONS_AGGREGATED` webhook or by noticing an updated `successfullyAggregatedAt` time for the connection. 2. Capture those updates using one of the strategies below. Transactions are ordered by date descending (newest first, oldest last) for each financial account, and each transaction has an associated key field that represents its position in this sequence. When capturing additions/updates, retrieving the full transaction history each time isn't necessary (except right after the initial connection). Transactions typically aren't added or modified beyond 2 weeks of the time of fetching them. ## Recommended strategies Use one of the following to ensure all transaction additions/updates are captured during each refresh: 1. **Date-range fetch.** Fetch transactions using the `startDate` and `endDate` params. Following a successful refresh, set them so the last 2 weeks of transaction history is fetched. This reasonably ensures all transaction updates are captured. The length of this period can be modified to be longer or shorter depending on how conservative you want to be. Paginate through these results using the `after` param set to the key of the last transaction on each page until reaching the end of the results for that time period. 2. **Oldest-updated key (forward).** Use the `oldestTransactionUpdatedSearchKey` present for each financial account on the `BANK_LINKING_TRANSACTIONS_AGGREGATED` webhook. This key can be used as a baseline so all updates are captured without also capturing unnecessary older transactions that weren't updated (which could occur with option 1). Call the search transactions endpoint with no key initially, then paginate through the results using the `after` key until you reach the transaction with key matching the `oldestTransactionUpdatedSearchKey` (newest transaction → oldest transaction updated). 3. **Oldest-updated key (reverse).** The reverse of strategy #2: call the search transactions endpoint with the `before` key set to the `oldestTransactionUpdatedSearchKey`, and paginate through the results in the opposite direction using the `before` key until there are none remaining (oldest transaction updated → newest transaction). The `after` and `before` fields refer to pagination order, not occurrence order. This is standard across all bank-linking search endpoints. In particular for transactions, this can be misleading: they do **not** mean "occurred after" or "occurred before". Since transactions are ordered with the newest first and oldest last, each call with an `after` key returns the transactions on the next page (which are actually older), and each call with a `before` key returns the results on the previous page (which are actually newer). When updating transactions, Meld recommends searching by financial account ID rather than connection ID. The ordering of transactions is done by transaction date per financial account, so searching by connection ID would yield a sequence of: *account1 transactions ordered by date* → *account2 transactions ordered by date* → etc. This can be misleading when parsing the transactions, as some recent transactions may appear missing when in reality they are later in the sequence because they belong to a different financial account for the connection. # Bank Linking Products Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/bank-linking-products/index ## Overview Once your customer has connected their account to your app, you can use Meld's endpoints to retrieve customer financial account information. This page lists the products available, shows how Meld's product names map to each service provider's terminology, and links to the deeper documentation for each. The following Meld products are normalized to return data across any bank linking service provider configured to your account: * [Balances](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-balances): The cached or realtime balances of the customer's account. * [Identifiers](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-identifiers): The institution's account and routing numbers (or related account-specific identifiers). * [Owners](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-owners): Account-owner information such as name and address. * [Transactions](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-transactions): Account transaction history. * [Investment Holdings](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-investments): Current investment holdings, including stocks and other securities. * [Investment Transactions](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-investments): Trade history. The following are not standalone products but features Meld supports: * [Processor Token](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-processor-token): Where supported by the service provider, this token can be used by an authorized third party to make API calls directly to the processor on your behalf. * [Recurring Transactions](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-transactions): Certain providers identify recurring transactions (such as a monthly subscription). For Plaid and MX specifically, recurring transactions in the transaction details object have `recurring=TRUE`, while all other transactions have `recurring=FALSE`. Processor Token is treated differently than other products: it isn't requested in the initial call to `/bank-linking/connect/start`. It has its own dedicated endpoint described on the [Processor Token](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-processor-token) page. Mapping of Meld's products to service provider's products:
Meld Product
Plaid Product
Finicity Product
MX Product
Yodlee Product
SaltEdge Product
SaltEdge Partners Product
Akoya Product
**Balances**
Balance
availBalance
Balance
Account\_details
Account\_details
Account\_details
Balances
**Identifiers**
Auth
ACH
Verification
Account\_details
Account\_details
Account\_details
Payments
**Owners**
Identity
accountOwner
Identity
Account\_details
Holder Info
Holder Info
Customers
**Transactions**
Transactions
transAgg
Transactions
Transactions
Transactions
Transactions
Transactions
**Investment Holdings**
Investment Holdings
availBalance
Investment Holdings
Holdings
Investments
**Investment Transactions**
Investment Transactions
transAgg
Transactions
Transactions
Transactions
# Account Types and Subtypes Each service provider has its own way of returning data and hierarchy/nomenclature when it comes to account types and subtypes. Meld normalizes the data across providers so accounts have a uniform way of identifying account types. You can reference the service provider mappings to the Meld account types/subtypes in the *Normalized Account Types and Subtypes* section at the bottom of each service provider's mappings page (see [Partner Details](/docs/bank-linking/partner-details/index)). If service providers add new account types/subtypes, they will be mapped to the Meld type of `UNIDENTIFIED` until a proper mapping has been determined. A list of all the Meld account types and subtypes can be found below: # Bank Linking Use Cases Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/bank-linking-use-cases This page summarizes how to build an app that uses Meld's Bank Linking stack end to end — from creating a connection through ongoing management — and lists common use cases that Bank Linking data unlocks. Use it as a roadmap that points to the deep-dive pages for each step. ## How to use the product 1. [Create a connection](/docs/bank-linking/creating-connections/index) to your customer's bank account(s). 1. Test credentials and sandbox flows are described in the [Bank Linking Sandbox Testing Guide](/docs/bank-linking/testing-and-debugging/bank-linking-sandbox-testing-guide). 2. Inform the user that the connection is complete while you fetch the data in the background. See [Informing the user of the status](/docs/bank-linking/get-connection-data/index). 2. [Get connection data](/docs/bank-linking/get-connection-data/index) to pull the financial data you need. 1. This includes [balances](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-balances), [identifiers](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-identifiers), [owners](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-owners), [transactions](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-transactions), and [investments](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-investments). 2. Configure and consume [webhooks](/docs/bank-linking/webhook-events-bank-linking) so you know when new financial data is available or a connection is broken. 3. You can also create a [processor token](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-processor-token) for a financial account. 3. [Manage connection status](/docs/bank-linking/manage-connection-status/index) to keep connections organized and up to date. 1. [Refresh](/docs/bank-linking/manage-connection-status/refreshing-connections) a connection to get the latest data. 2. [Repair](/docs/bank-linking/manage-connection-status/repairing-connections) a connection to start receiving updates again. 3. [Delete](/docs/bank-linking/manage-connection-status/deleting-connections) a connection as needed. 4. [Route](/docs/bank-linking/creating-connections/select-an-institution/routing-overview) between providers to optimize conversion. 1. Each service provider has different coverage and details. Be sure to read through them before using a particular provider. See [Plaid](/docs/bank-linking/partner-details/plaid/index), [Finicity](/docs/bank-linking/partner-details/finicity/index), [MX](/docs/bank-linking/partner-details/mx/index), [Salt Edge](/docs/bank-linking/partner-details/salt-edge/index), and [Salt Edge Partners](/docs/bank-linking/partner-details/salt-edge-partners/index). 2. Meld's Bank Linking stack lets you route customers to specific providers based on predefined defaults, a provider waterfall using institution coverage and availability, or institution-specific decisioning. Routing rules are defined in the Meld dashboard. 5. If you already have existing connections with a service provider, you can bring them over by [importing connections](/docs/bank-linking/manage-connection-status/importing-connections). ## Potential use cases Common reasons to integrate Bank Linking: 1. View users' balances and investment holdings to give them financial advice. 2. View users' balances and transactions to assess loan qualification. 3. View users' owner information to verify their identity. 4. View users' identifier information (account number and routing number) to perform an ACH transaction. See [Account Verification](/docs/bank-linking/bank-linking-quickstart/account-verification) for more. ## Building your own institution picker The first step of bank linking is knowing which institution the user wants to connect to. Most customers launch Meld's widget, which has an institution picker as the first screen. However, you can build your own picker, have the user select an institution, and pass that `institutionId` into the Meld `/bank-linking/connect/start` endpoint — this skips the institution picker screen in the Meld UI. To populate institutions along with their symbols in your own UI, fetch this data from the institutions endpoint. Institution data rarely changes, so cache the response rather than calling the institutions endpoint live every time. # Bank Linking Quickstart Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/index Welcome to Meld's Bank Linking product. Meld's Bank Linking stack lets developers connect their users' bank accounts through multiple service providers (Plaid, MX, Finicity, Salt Edge, Yodlee, Akoya) using a single integrated API and UX flow. This quickstart covers how connections are created and managed, the data products available, and the terminology used throughout the rest of the bank linking documentation. Looking for Meld's Crypto / Digital Assets documentation? Skip this section and head to the [Crypto Overview](/docs/stablecoins/crypto-overview_/index) instead. ## Before you begin * Complete [Onboarding](/docs/bank-linking/onboarding/index) so you have a Meld dashboard account. * Grab your Meld API key from the **Developer** tab of the Dashboard (see [Step 3: Meld API Key](/docs/bank-linking/onboarding/step-3-meld-api-key)). * Confirm which environment your key targets (sandbox or production). Reach out to Meld if you would like the OpenAPI spec for bank linking. ## How to use the product The high-level flow is summarized in [Bank Linking Use Cases](/docs/bank-linking/bank-linking-quickstart/bank-linking-use-cases). For terminology and a relationship diagram, see the [Bank Linking Glossary](/docs/bank-linking/bank-linking-quickstart/bank-linking-glossary). For details on the financial-data products you can retrieve after a connection completes, see [Bank Linking Products](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/index). **Environments** Meld offers separate sandbox and production environments. Each environment has its own API key and base URL: | Environment | API Base URL | | :---------- | :----------------------- | | Sandbox | `https://api-sb.meld.io` | | Production | `https://api.meld.io` | ## Accounts with service providers Meld has reseller agreements with Finicity, MX, and Akoya and can create an account for you with each of these providers. For the other providers, if you don't have your own account with the provider already, Meld can temporarily share their account with you for testing while your account is being spun up. Contact Meld for more details. # Step 4: Complete the Connection Source: https://docs.meld.io/docs/bank-linking/creating-connections/complete-the-connection/index The final step of the connection flow is for the customer to authenticate with their bank. After they pick an institution, the widget hands off to that institution's login screen (or to the service provider's OAuth flow). This page covers what your customer sees, how to handle failure cases, and what events to expect when the connection succeeds. Once they have chosen their institution, they will be taken to that institution's login page. Here's an example: ![](https://files.readme.io/6010dd5-image.png) For security reasons, many institutions will open their login screens in a new popup to enforce an OAuth login. In that case, the login screen will be made by the institution, rather than the provider. Here's an example: ![](https://files.readme.io/9f177ec-image.png) Once the user enters their username and password, if they have multi factor authentication set up with their bank, they may have to enter a pin or passcode. After this, provider will complete the connection to the institution, and once the user sees a success screen they can close the widget. Once you have successfully completed a connection, you can now view the user's financial data. See [Get Connection Data](/docs/bank-linking/get-connection-data) for more information. ## Next steps To see what to do next, including which webhooks to expect once a connection completes, see [Get Connection Data](/docs/bank-linking/get-connection-data). ## Connection issues There are several reasons why a connection might fail. The most prevalent reason is that the user entered the incorrect credentials, in which case they will be asked to reenter their credentials. A user may also connect to an institution where they don't have any eligible accounts. For example, Meld does not support Mortgages for Plaid right now, so if the user uses Plaid to connect to an institution where they only have a mortgage account, they may not be able to connect it, and would see a message like this: ![](https://files.readme.io/7faa787-image.png) Sometimes, a service provider may temporarily have an issue connecting to a particular institution. While Meld tries to mitigate this by noticing breakages and steering users towards providers whose connection to that institution is working, on occasion users may still see a screen like below: ![](https://files.readme.io/da7e798-image.png) In this case, the user should go back and select a different institution, or route to the same institution through a different provider. For more information on the latter option, see [Bank Linking Routing](/docs/bank-linking/creating-connections/select-an-institution/routing-overview). For a deep dive into widget errors, including how to query them from the Activity Log, see [Widget Errors During a Connection](/docs/bank-linking/creating-connections/complete-the-connection/widget-errors-during-a-connection). # Widget Errors During a Connection Source: https://docs.meld.io/docs/bank-linking/creating-connections/complete-the-connection/widget-errors-during-a-connection A customer may encounter errors inside the Meld Connect Widget while trying to make a connection. Examples include requesting a product you do not have enabled with the service provider, hitting test connection limits, or transient service provider failures. This page is for developers debugging failed connections — it explains how to query widget errors and what each error subtype means. ## Before you begin * You have a Meld API key for the environment in question (sandbox or production) * You know either the `connectionId` of the failed attempt, or a date range you want to inspect * You are comfortable querying the Meld Activity Log ## How to query widget errors Widget errors are recorded in the Activity Log. Query them by filtering on the action `INSTITUTION_CONNECTION_WIDGET_ERROR`: ```bash theme={null} curl --location 'https://api-sb.meld.io/activity-log?start=2024-05-08T00:00:00.000Z&end=2024-05-10T00:00:00.000Z&connectionId=W9343jnjn3jn3&actions=INSTITUTION_CONNECTION_WIDGET_ERROR' \ --header 'Meld-Version: 2022-11-10' \ --header 'Content-Type: application/json' \ --header 'Authorization: BASIC ***Redacted API Key***' ``` For the full Activity Log schema and supported query parameters, refer to the [API reference](/api-reference). ## Widget error subtypes There are three event types (subtypes) of widget errors. Filter by one or more using the `eventTypes` query parameter on the Activity Log endpoint. | Event type | What it means | Recommended developer action | | :---------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `SERVICE_PROVIDER_INITIALIZATION_FAILURE` | The widget could not initialize with the underlying service provider. Common causes: the requested product is not enabled on your provider account, missing provider credentials, or invalid configuration in the dashboard. | Check that the products you requested in `/connect/start` are enabled with the provider for your account. Verify provider credentials in the Meld dashboard. If the issue persists, contact Meld support with the connection id. | | `SERVICE_PROVIDER_FAILURE` | The service provider returned an error during the customer's session. Common causes: institution outage, provider rate limit, expired provider session, or customer-side issues such as repeated invalid credentials. | Inspect the `serviceProviderDetails` in the activity log entry. If the institution is unhealthy, advise the customer to retry later or use a different provider via routing. If you see repeated provider rate-limit errors, throttle test traffic. | | `INTERNAL_FAILURE` | An unexpected error inside Meld occurred during the widget flow. | Retry the connection. If the error persists, contact Meld support with the `connectionId` and the timestamp. | ## Common scenarios and fixes | Symptom | Likely cause | Developer action | | :---------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Widget fails immediately on launch | The connect token expired (older than 3 hours) or was malformed | Generate a fresh token via `/connect/start` and relaunch | | Picker is empty or has very few institutions | Too many required products or an overly restrictive region filter | Reduce the `products` array, move non-essential products to `optionalProducts`, or remove `regions` | | "Error creating user" when selecting an institution | You re-enabled a service provider with the same `customerId` / `externalCustomerId` used before disabling | Use new `customerId` / `externalCustomerId` values when reconnecting via a re-enabled provider | | Customer sees "Institution unavailable" or a routing retry screen | Provider's integration with that institution is degraded | Encourage the customer to use Routing Retry to try another provider; consider enabling Smart Routing in the dashboard | | Connection completes but no webhooks fire | Webhook profile not configured or filtered too narrowly | Verify your webhook profile per [Webhook Quickstart](/docs/bank-linking/onboarding/step-5-setting-up-webhooks/webhook-quickstart) and confirm event types include bank linking events | **Sandbox vs. production** Widget errors are logged identically in sandbox (`api-sb.meld.io`) and production (`api.meld.io`). Sandbox traffic may produce additional `SERVICE_PROVIDER_FAILURE` entries that mirror the test behavior of each provider's sandbox; these are expected and do not indicate a real outage. # Step 1: Create a Connect Token Source: https://docs.meld.io/docs/bank-linking/creating-connections/create-a-connect-token/index The first step in linking a customer's bank account is creating a **connect token**. A connect token is a short-lived JWT that authorizes the Meld Connect Widget to start a connection session for a specific customer with a specific set of products and regions. This page walks developers through generating a token, configuring it correctly, and using the response to launch the widget in Step 2. ## Before you begin * You have a Meld sandbox or production API key * You have decided which [products](/docs/bank-linking/creating-connections/create-a-connect-token/supported-bank-linking-products) you need (you can also use [optional products](/docs/bank-linking/creating-connections/create-a-connect-token/optional-products)) * You know the [regions](/docs/bank-linking/creating-connections/create-a-connect-token/supported-bank-linking-products) your customers bank in, or are comfortable leaving regions unset * Your server is calling the endpoint — **never call this endpoint from the browser or mobile client**, the API key is a secret ## Create the token Send a `POST` request to `{api_base_url}/bank-linking/connect/start` using your API key. The response contains the `connectToken` you will use in [Step 2: Launch the widget](/docs/bank-linking/creating-connections/launch-the-widget). For the full request and response schema, refer to the [Bank Linking — Create Connect Token API reference](/api-reference/bank-linking). The connect token is used only while the user is making the connection to their bank and **expires after 3 hours**. You do not have to persist it — once the connection completes it cannot be reused. To track a connection over time, use the `customerId` returned in the response, or the `externalCustomerId` you optionally pass in. Be deliberate about the regions and products you request. In Step 3, when the user selects their institution, only institutions that support **all** requested products and are in one of the requested regions appear in the widget. Choosing too many products or too few regions can dramatically shrink the institution list. If you don't know the region in which your customer banks, omit the `regions` parameter entirely. **Disabling / re-enabling service providers** If you disable a service provider in the Meld dashboard and later re-enable it (with the same or different credentials), use new `customerId` / `externalCustomerId` values for subsequent connections. Reusing prior identifiers can result in an `Error creating user` when the customer selects an institution. ## Skipping or prepopulating the Meld picker If you already know which institution the user wants to link (for example, you collect that in your own UI), you have two options to make the flow more seamless: 1. **Prepopulate the search box.** Pass the institution name as a string when creating the connect token. The Meld picker autofills the search input so matching institutions appear by default. The user can still edit the search string. 2. **Skip the picker entirely.** Pass the `institutionId` corresponding to the user's bank. This skips the Meld picker screen and sends the user straight to the institution login. To resolve an institution name to a Meld `institutionId`, use the [/institutions endpoint](/api-reference/bank-linking). Note that for MX, not all institutions that support transactions also support historical transactions, so `HISTORICAL_TRANSACTIONS` appears as a separate product in the institution search response. However, when listing products in `/connect/start`, pass `TRANSACTIONS` only — `HISTORICAL_TRANSACTIONS` is not a valid value for the products array on `/connect/start`. A key part of building the request body is the list of products. For more information on the available products and which to request, see [Supported Bank Linking Products](/docs/bank-linking/creating-connections/create-a-connect-token/supported-bank-linking-products). ## Sample request ```bash theme={null} curl --location 'https://api-sb.meld.io/bank-linking/connect/start' \ --header 'Meld-Version: 2022-11-10' \ --header 'Content-Type: application/json' \ --header 'Authorization: BASIC ***Redacted API Key***' \ --data '{ "externalCustomerId": "testCustomer", "products": [ "BALANCES", "OWNERS", "IDENTIFIERS", "TRANSACTIONS", "INVESTMENT_HOLDINGS", "INVESTMENT_TRANSACTIONS" ], "regions": ["US", "CA"], "institutionSearchString": "Fidelity" }' ``` ## Sample response ```json theme={null} { "id": "WQ46XEVzT14vrrDq6LSJVg", "connectToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyZWRpcmVjdCI6dHJ1ZSwicmVnaW9ucyI6WyJVUyIsIkNBIl0sImlzcyI6Im1lbGQuaW8iLCJjb25uZWN0aW9uIjoiV1E0NlhFVnpUMTR2cnJEcTZMU0pFZCIsImV4cCI6MTY4Njc5ODkzMywiaWF0IjoxNjg2Nzg0NTMzLCJhY2NvdW50IjoiVzlrYmtSbWU5VlQyaXo2cUFTZkFmMiIsImN1c3RvbWVyIjoiV1E0NlhEcllZTVlyZTlSQmt3YXl5USIsInByb2R1Y3RzIjpbIlRSQU5TQUNUSU9OUyIsIkJBTEFOQ0VTIiwiSURFTlRJRklFUlMiLCJPV05FUlMiXSwicm91dGluZ1Byb2ZpbGUiOiJXR3ZGWHRYYkRvQ2VXc3hVdHVpeFB5In0.jAwpUokwkSKi7BTwKJOY_Y6XAPv_O8yOi6mVaUKxnMI", "customerId": "WQ46XDrYYMYre9RBkwayyZ", "externalCustomerId": "testCustomer", "widgetUrl": "https://institution-connect-sb.meld.io?connectToken=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyZWRpcmVjdCI6dHJ1ZSwicmVnaW9ucyI6WyJVUyIsIkNBIl0sImlzcyI6Im1lbGQuaW8iLCJjb25uZWN0aW9uIjoiV1E0NlhFVnpUMTR2cnJEcTZMU0pFZCIsImV4cCI6MTY4Njc5ODkzMywiaWF0IjoxNjg2Nzg0NTMzLCJhY2NvdW50IjoiVzlrYmtSbWU5VlQyaXo2cUFTZkFmMiIsImN1c3RvbWVyIjoiV1E0NlhEcllZTVlyZTlSQmt3YXl5USIsInByb2R1Y3RzIjpbIlRSQU5TQUNUSU9OUyIsIkJBTEFOQ0VTIiwiSURFTlRJRklFUlMiLCJPV05FUlMiXSwicm91dGluZ1Byb2ZpbGUiOiJXR3ZGWHRYYkRvQ2VXc3hVdHVpeFB5In0.jAwpUokwkSKi7BTwKJOY_Y6XAPv_O8yOi6mVaUKxnMI" } ``` ### Key response fields | Field | Description | | :------------------- | :-------------------------------------------------------------------------------------------------- | | `id` | The Meld connection id. Use this to look up the connection later. | | `connectToken` | The JWT used to launch the widget. Expires after 3 hours. | | `customerId` | Meld's id for the customer. Persist this to track the customer across connections. | | `externalCustomerId` | The id you passed in (if any), echoed back. | | `widgetUrl` | The full URL with the token attached. You can load this directly in an iframe to launch the widget. | ## Common errors | HTTP status | Likely cause | Developer action | | :----------------- | :-------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------ | | `401 Unauthorized` | Missing or invalid `Authorization` header | Verify the API key value and the `BASIC ` prefix; confirm you are hitting the correct environment | | `400 Bad Request` | Invalid product name, both required and optional product overlap, or unsupported region | Check `products` / `optionalProducts` / `regions` values against the API reference | | `400 Bad Request` | No required product specified when using `optionalProducts` | Move at least one product into the `products` array | | `404 Not Found` | The `institutionId` you passed does not exist | Re-resolve the institution via the `/institutions` endpoint | | `5xx` | Transient Meld or provider issue | Retry with backoff; if it persists, contact Meld support | Now that you have your connect token, you are ready to move on to [launching the widget](/docs/bank-linking/creating-connections/launch-the-widget). # Optional Products Source: https://docs.meld.io/docs/bank-linking/creating-connections/create-a-connect-token/optional-products By default, Meld filters the institutions shown in the picker to those that support **every** product in the `/connect/start` request. Sometimes you want users to see institutions that may not support all products, and then fetch data only for the products that are supported. **Optional products** solve this use case: they are aggregated on a best-effort basis and do not restrict which institutions appear. ## When to use optional products * You want maximum institution coverage but still want bonus data (such as investments) where it exists. * A product like `INVESTMENT_HOLDINGS` is nice to have for some customers but should not gate the rest of the flow. * You are retroactively adding products via the [update connection endpoint](/docs/bank-linking/manage-connection-status/updating-connections) — those products always land in `optionalProducts`. ## Rules * You must specify **at least one** product in the required `products` array. * A product cannot appear in both `products` and `optionalProducts`. * Optional products are aggregated on a best-effort basis — Meld does not guarantee data will be returned for them. Build your UI accordingly. ## Sample request ```bash theme={null} curl --location 'https://api-sb.meld.io/bank-linking/connect/start' \ --header 'Meld-Version: 2022-11-10' \ --header 'Content-Type: application/json' \ --header 'Authorization: BASIC ***Redacted API Key***' \ --data '{ "externalCustomerId": "testCustomer", "products": [ "BALANCES", "TRANSACTIONS" ], "optionalProducts": [ "INVESTMENT_HOLDINGS", "INVESTMENT_TRANSACTIONS" ] }' ``` In the example above, the picker filters institutions to those that support `BALANCES` and `TRANSACTIONS`. Institutions are **not** filtered out for missing investment support. If the connected accounts do carry investment data, Meld retrieves it and emits the corresponding webhooks. Optional products are powerful for maximizing coverage, but do **not** guarantee data will be returned for those products. Always handle the case where investment or other optional data is empty. # Supported Bank Linking Products Source: https://docs.meld.io/docs/bank-linking/creating-connections/create-a-connect-token/supported-bank-linking-products A **product** in Meld's Bank Linking stack is a category of financial data you can request for a connection — for example `BALANCES`, `TRANSACTIONS`, or `INVESTMENT_HOLDINGS`. This page is for developers deciding which products to request in [`/connect/start`](/docs/bank-linking/creating-connections/create-a-connect-token) and which service provider to route to. Choosing the right product set is critical because it determines which institutions appear in the picker and what data Meld returns. ## Explanation of the products * **Identifiers** — account and routing numbers used for ACH and payment instruments. * **Owners** — name and contact details for the account holder. * **Balances** — current and available balance on each account. * **Transactions** — posted and pending transactions; up to 2 years of history is loaded once available. * **Investment Holdings** — securities held in an investment account. * **Investment Transactions** — buys, sells, transfers, and other movement within investment accounts. * **Processor Token** — a token usable with downstream processors (e.g. Stripe, Dwolla); availability depends on the provider. The complete catalog of products in this table is the canonical list. The [Optional Products](/docs/bank-linking/creating-connections/create-a-connect-token/optional-products) feature uses the same product names — keep that page and this one in sync when adding new products. ## Supported products Each connection is established via a service provider. The service provider handles the customer's credentials, multi-factor authentication, and deals directly with the customer's bank. The list of currently supported service providers and their product availability is below. Specific details for each provider live on the Provider Details pages. | | | | | | | | | | :-------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------- | | |
[Identifiers](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-identifiers)
|
[Owners](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-owners)
|
[Balances](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-balances)
|
[Transactions](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-transactions)
|
[Investment Holdings](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-investments)
|
[Investment Transactions](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-investments)
|
[Processor Token](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-processor-token)
| | [Finicity](/docs/bank-linking/partner-details/finicity) |
X
|
X
|
X
|
X
|
X
|
X
| | | [Plaid](/docs/bank-linking/partner-details/plaid) |
X
|
X
|
X
|
X
|
X
|
X
|
X
| | Akoya |
X
|
X
|
X
|
X
|
X
|
X
| | | Yodlee |
X
|
X
|
X
|
X
|
X
|
X
| | | [MX](/docs/bank-linking/partner-details/mx) |
X
|
X
|
X
|
X
|
X
|
X
|
X
| | [Salt Edge Open Banking](/docs/bank-linking/partner-details/salt-edge) |
X
|
X
|
X
|
X
| | | | | [Salt Edge Partners](/docs/bank-linking/partner-details/salt-edge-partners) |
X
|
X
|
X
|
X
| | | | | Mesh | | |
X
| |
X
|
X
| | ## Product initialization Products are initialized in the [`/connect/start`](/docs/bank-linking/creating-connections/create-a-connect-token) request when creating a connection. Which products to initialize depends on your application's requirements. The products specified restrict the institutions shown in the selection pane to those that support **all** of them. If maximizing institution coverage is a high priority, only request products in `/connect/start` that are critical to your use case. To fetch more data where available without filtering institutions, use [Optional Products](/docs/bank-linking/creating-connections/create-a-connect-token/optional-products). This filtering is done at the **institution** level only — not at the account level. When the customer selects which accounts to share, all of their accounts are displayed even if individual accounts do not support all requested products. There is no guarantee that each selected account will support a given product; the filter only guarantees that the institution supports the product for at least some account types. Some products are priced differently and vary in availability by service provider. Confirm pricing with Meld before adding products in production. # Creating Connections Source: https://docs.meld.io/docs/bank-linking/creating-connections/index Creating institution connections is the core of Meld's Bank Linking service: it is how your application gains permissioned access to a customer's financial data. This guide is for developers integrating bank account linking into their product, and walks you through every step required to establish a working connection. Each connection has its own set of [products](/docs/bank-linking/creating-connections/create-a-connect-token/supported-bank-linking-products) associated with it, and is established via an underlying [service provider](/docs/bank-linking/creating-connections/create-a-connect-token/supported-bank-linking-products). ## Before you begin Before creating your first connection, make sure you have: * A Meld account with at least one Bank Linking service provider enabled in the dashboard * A sandbox API key (treat it as a secret — never expose it in front-end code) * A webhook profile configured if you plan to react to connection lifecycle events * Reviewed the list of [supported products](/docs/bank-linking/creating-connections/create-a-connect-token/supported-bank-linking-products) so you know which to request ## Steps to make a connection 1. [Create a connect token.](/docs/bank-linking/creating-connections/create-a-connect-token) Choose the [products](/docs/bank-linking/creating-connections/create-a-connect-token/supported-bank-linking-products) for which you would like data. 2. [Launch the widget.](/docs/bank-linking/creating-connections/launch-the-widget) 3. [The user selects an institution.](/docs/bank-linking/creating-connections/select-an-institution) 4. [The user completes the connection.](/docs/bank-linking/creating-connections/complete-the-connection) ## Routing Routing sends the user to the best available provider to complete their connection. More information on bank linking routing can be found in the [Routing overview](/docs/bank-linking/creating-connections/select-an-institution/routing-overview). **Routing across service providers** Meld's Bank Linking stack lets you route customers to specific service providers based on predefined defaults, a service provider waterfall driven by institution coverage and availability, or institution-specific decisioning. Routing rules are defined within the Meld dashboard. # Step 2: Launch the Widget Source: https://docs.meld.io/docs/bank-linking/creating-connections/launch-the-widget/index The Meld Connect Widget is the embedded UI your customer uses to pick a financial institution and authenticate with their bank. This page is for developers who already have a connect token and need to render the widget on web, mobile web, or in a native mobile app. Once your customer completes the flow, you can call Meld's APIs to retrieve their financial data. ## Before you begin * You have a valid `connectToken` from [Step 1: Create a Connect Token](/docs/bank-linking/creating-connections/create-a-connect-token) (tokens expire after 3 hours) * You have decided how to render the widget: web iframe, mobile WebView, or new tab * Your front-end is set up to listen for `window.message` events (or the platform equivalent) so it can react to widget events ## Overview The Meld Connect Widget contains two components: * **Front-end component** — used by the customer to select the financial institution. * **Back-end component** — used by your server to access the customer's financial data via the Meld API. Meld recommends launching the widget inside an iframe for optimal UI, and closing it when the `connect_complete` event is emitted. You can also load the URL in a new tab or webview but certain UI elements will not look as polished. How you display the widget does not affect the customer's ability to connect. ## Launch the Meld Connect Widget Use the connect token to build the URL and render the widget. Below is sample code: ```html theme={null} ``` ### Iframe Best Practices: * **Minimum width:** 450px for optimal display * **Minimum height:** 790px to prevent UI overlap * **Allow permissions:** Include `camera`, `microphone`, `payment` for KYC and transactions * **Responsive design:** Consider dynamic sizing for mobile ## Step 2: Customization with URL Parameters Add optional parameters to your Meld Widget URL for customization: ```javascript theme={null} // Your complete Meld Widget URL with optional parameters const dashboardUrl = 'meldcrypto.com/?publicKey=your_public_key'; // From Meld Dashboard Developer tab const customizedUrl = `${dashboardUrl}&sourceAmount=100&destinationCurrencyCode=BTC&countryCode=US`; window.location.href = customizedUrl; ``` ### Key Parameters: * **sourceAmount** - Pre-fill purchase amount (e.g., 100) * **destinationCurrencyCode** - Pre-select cryptocurrency (e.g., BTC, ETH\_ETHEREUM) * **walletAddress** - Pre-fill destination wallet if known * **countryCode** - Override auto-detected country (rarely needed) ➡️ **[Complete parameter reference](/docs/stablecoins/meld-checkout-integration/meld-checkout-guide/url-parameters)** ## Step 3: Testing Your Integration ### Sandbox Testing Always test in sandbox environment first: ```javascript theme={null} // Use your sandbox Meld Widget URL (from Developer tab in sandbox environment) const sandboxUrl = 'meldcrypto.com/?publicKey=your_public_key'; ``` ### Test Workflow: 1. **Redirect to sandbox widget** with your parameters 2. **Complete the test transaction** using the [sandbox testing credentials](/docs/stablecoins/sandbox-guide/test) 3. **Verify webhook reception** (if implemented) 4. **Test all user flows** (buy/sell as applicable) ## Step 6: Mobile Implementation ### React Native Example: ```javascript theme={null} import { Linking } from 'react-native'; const openMeldWidget = () => { const widgetUrl = `meldcrypto.com/?publicKey=your_public_key&redirectUrl=myapp://crypto-complete`; Linking.openURL(widgetUrl); }; ``` ### Deep Link Return: ```javascript theme={null} // Handle return from widget const handleDeepLink = (url) => { if (url.includes('crypto-complete')) { // User completed transaction, update UI navigateToCryptoSuccess(); } }; ``` ### Mobile Considerations: * **Use redirectUrl** with deep links to return users to your app * **Consider theme parameter** to match your app's appearance * **Test on both iOS and Android** for compatibility ## Step 4: Go Live ### Production Checklist: * ✅ **Use production Meld Widget URL** (from Developer tab in production environment) * ✅ **Test with small real transaction** * ✅ **Verify webhook endpoints** are working * ✅ **Monitor first transactions** for issues ### Production URL: ```javascript theme={null} const productionUrl = 'meldcrypto.com/?publicKey=your_public_key'; ``` ## Troubleshooting Common Issues ### Widget Won't Load * ✅ Verify Meld Widget URL is correct and from the right environment * ✅ Check for browser popup blockers * ✅ Ensure iframe permissions are set correctly ### Parameters Not Working * ✅ URL encode parameter values * ✅ Check parameter spelling and case sensitivity * ✅ Verify parameter combinations are valid ### Mobile Issues * ✅ Test deep link return flow * ✅ Verify redirectUrl format is correct * ✅ Check mobile browser compatibility ## Error Handling ### JavaScript Error Handling: ```javascript theme={null} try { window.location.href = widgetUrl; } catch (error) { console.error('Failed to open widget:', error); // Fallback: show error message to user alert('Unable to open crypto purchase. Please try again.'); } ``` ### Iframe Error Handling: ```javascript theme={null} const iframe = document.getElementById('meld-widget'); iframe.onerror = () => { console.error('Widget failed to load'); // Show error message or fallback content }; ``` ## Next Steps ### Optimize your integration * **[URL parameters](/docs/stablecoins/meld-checkout-integration/meld-checkout-guide/url-parameters)** — complete parameter reference * **[Customization](/docs/stablecoins/meld-checkout-integration/meld-checkout-guide/meld-checkout-customization)** — styling and advanced options ### Monitor and improve * **[Webhook events](/docs/stablecoins/for-all-products/webhook-events)** — real-time notifications * **[Transaction statuses](/docs/stablecoins/for-all-products/transaction-statuses)** — understanding transaction states * **[Dashboard data](/docs/stablecoins/additional-information/dashboard-data)** — analytics and reporting *** *Your Widget Integration is now live! Users can purchase crypto directly through your application with Meld's secure, compliant widget.* # Customization Source: https://docs.meld.io/docs/stablecoins/meld-checkout-integration/meld-checkout-guide/meld-checkout-customization This discusses the customization options available to you for the Meld Checkout flow # Meld Checkout Customization & Advanced Configuration Customize your Meld Checkout Integration appearance, behavior, and user experience through dashboard settings and advanced configuration options. ## Meld Support Required Contact Meld for these advanced customizations: ### Account Logo Update your account logo in the top-left corner of the UI. **Process:** Send your logo file (PNG/SVG preferred) to Meld support with specifications: * **Size:** 200x60px recommended * **Format:** PNG with transparent background or SVG * **Colors:** Optimized for both light and dark themes ### Flow Management Control which transaction types are available: * **Disable Buy Flow** - Show only sell/offramp functionality * **Disable Sell Flow** - Show only buy/onramp functionality * **Custom Flow Logic** - Advanced routing based on user profile ### Token Restrictions Limit available cryptocurrencies: * **Chain-specific tokens** - Only show tokens from specific blockchains * **Curated token list** - Custom selection of supported cryptocurrencies * **Partner tokens** - Highlight specific tokens for partnerships ### Quote Management Customize provider quote display: * **Provider prioritization** - Promote specific providers to top of list * **Quote limits** - Control number of quotes shown to users * **Custom provider branding** - Special provider highlighting ### Default Settings Set account-wide defaults: * **Default redirect URL** - Fallback URL for transaction completion * **Theme defaults** - Account-wide light/dark mode preference ## Dashboard Customization Access these options in the **Meld Dashboard → Developer Tab → Preferences**: ### Visual Branding #### Button Color Customize the primary **Buy** or **Sell** CTA button color at the bottom of the UI. ```css theme={null} /* Example color values */ Button Color: #3498db /* Blue */ Button Color: #e74c3c /* Red */ Button Color: #2ecc71 /* Green */ Button Color: #9b59b6 /* Purple */ ``` #### Button Text Color Set the text color inside the CTA button for optimal contrast. ```css theme={null} /* Common combinations */ Button Color: #3498db, Text Color: #ffffff /* Blue background, white text */ Button Color: #ffffff, Text Color: #333333 /* White background, dark text */ Button Color: #2c3e50, Text Color: #ecf0f1 /* Dark background, light text */ ``` ![](https://files.readme.io/ba42d402322fcd88e1fd9fa5c3f3c3070dc7cc8c24e68d67b03a877e0e3762b3-image.png) **Reset Option:** Use the **Reset** button to restore default values at any time. ## Theme Configuration ### Light/Dark Mode Implementation Control the UI appearance with the theme parameter: ```javascript theme={null} // Light theme (default) const lightThemeUrl = `https://meldcrypto.com/?publicKey=${publicKey}&theme=lightMode`; // Dark theme const darkThemeUrl = `https://meldcrypto.com/?publicKey=${publicKey}&theme=darkMode`; // Dynamic theme based on user preference const userTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'darkMode' : 'lightMode'; const dynamicUrl = `https://meldcrypto.com/?publicKey=${publicKey}&theme=${userTheme}`; ``` ### Theme Locking Prevent users from changing the theme: ```javascript theme={null} const lockedThemeUrl = `https://meldcrypto.com/?publicKey=${publicKey}&theme=darkMode&themeLocked=true`; ``` ## Mobile Optimization ### Deep Link Configuration Seamlessly return users to your mobile app after transactions: ```javascript theme={null} // iOS Deep Link const iosUrl = `https://meldcrypto.com/?publicKey=${publicKey}&redirectUrl=myapp://crypto-success`; // Android Deep Link const androidUrl = `https://meldcrypto.com/?publicKey=${publicKey}&redirectUrl=myapp://crypto-success`; // Universal Link const universalUrl = `https://meldcrypto.com/?publicKey=${publicKey}&redirectUrl=https://myapp.com/crypto-success`; ``` ### Responsive Implementation Optimize the UI display for different screen sizes: ```javascript theme={null} // Mobile-first approach function getMobileOptimizedUrl() { const isMobile = window.innerWidth <= 768; const baseUrl = `https://meldcrypto.com/?publicKey=${publicKey}`; if (isMobile) { // Full screen redirect for mobile return `${baseUrl}&redirectUrl=myapp://success`; } else { // Iframe embedding for desktop return baseUrl; } } ``` ### Mobile iframe Considerations ```html theme={null} ``` ## Analytics & Tracking ### Customer Identification Strategy Track users across sessions for analytics: ```javascript theme={null} // Use external customer ID for consistent tracking const analyticsUrl = `https://meldcrypto.com/?publicKey=${publicKey}` + `&externalCustomerId=${userId}` + `&externalSessionId=${sessionId}`; // Example: E-commerce integration function trackCryptoTransaction(userId, orderId) { const trackingUrl = `https://meldcrypto.com/?publicKey=${publicKey}` + `&externalCustomerId=${userId}` + `&externalSessionId=order_${orderId}` + `&redirectUrl=https://shop.com/order-complete?id=${orderId}`; window.location.href = trackingUrl; } ``` ### Conversion Optimization Pre-fill known information to improve conversion rates: ```javascript theme={null} // Optimize for returning customers function getOptimizedCheckoutUrl(userProfile) { let url = `https://meldcrypto.com/?publicKey=${publicKey}`; // Only override country if specifically needed (auto-detection usually works) if (userProfile.Country) { url += `&countryCode=${userProfile.Country}`; } if (userProfile.preferredCrypto) { url += `&destinationCurrencyCode=${userProfile.preferredCrypto}`; } if (userProfile.wallet) { url += `&walletAddress=${userProfile.wallet}&walletAddressLocked=true`; } return url; } ``` ## Advanced Use Cases ### Marketplace Integration Configure the Meld Checkout for NFT marketplace or token sales: ```javascript theme={null} // Token sale configuration function createTokenSaleCheckout(tokenInfo) { return `https://meldcrypto.com/?publicKey=${publicKey}` + `&destinationCurrencyCode=${tokenInfo.currency}` + `&destinationCurrencyCodeLocked=true` + `&sourceAmount=${tokenInfo.price}` + `&sourceAmountLocked=true` + `&theme=darkMode` + `&redirectUrl=https://marketplace.com/purchase-complete`; } // Usage const nftSaleUrl = createTokenSaleCheckout({ currency: 'ETH', price: 150 }); ``` ### Multi-Currency Support Offer multiple cryptocurrency options: ```javascript theme={null} // Dynamic currency selection function createCurrencyCheckout(selectedCrypto) { const cryptoOptions = { 'bitcoin': 'BTC', 'ethereum': 'ETH', 'usdc': 'USDC', 'solana': 'SOL' }; return `https://meldcrypto.com/?publicKey=${publicKey}` + `&destinationCurrencyCode=${cryptoOptions[selectedCrypto]}` + `&theme=lightMode`; } ``` ### Geographic Customization Optimize currency and amounts for specific regions: > **Note:** Country is automatically detected in the Meld Checkout Integration. Use countryCode parameter only when you need to override the detection for special cases. ```javascript theme={null} // Region-specific currency and amount configuration function getRegionalCheckout(overrideCountry = null) { const regionalConfig = { 'US': { currency: 'USD', amount: 100 }, 'GB': { currency: 'GBP', amount: 75 }, 'EU': { currency: 'EUR', amount: 85 }, 'CA': { currency: 'CAD', amount: 130 } }; const config = regionalConfig[overrideCountry] || regionalConfig['US']; let url = `https://meldcrypto.com/?publicKey=${publicKey}` + `&sourceCurrencyCode=${config.currency}` + `&sourceAmount=${config.amount}`; // Only add countryCode if overriding auto-detection if (overrideCountry) { url += `&countryCode=${overrideCountry}`; } return url; } ``` ## Contact Meld For advanced customizations requiring Meld support: **Email/Slack/Telegram:** Contact your integration specialist or support team\ **Documentation:** Reference this guide when making requests ### Request Template: ``` Subject: Meld Checkout Customization Request - [Your Account Name] Customization Type: [Logo Update/Flow Management/Token Restrictions/etc.] Current Setup: [Description of current checkout configuration] Desired Changes: [Specific requirements and use case] Timeline: [When you need changes implemented] ``` *** **Implementation Complete!** Your Meld Checkout Integration is now optimized for your specific use case and user experience requirements. # URL Parameters Source: https://docs.meld.io/docs/stablecoins/meld-checkout-integration/meld-checkout-guide/url-parameters Review the URL params you can use to prefill the Meld Checkout UI # Meld Checkout URL Parameters Reference Complete technical reference for all Meld Checkout Integration URL parameters. Use these parameters to prefill information and customize the user experience. ## Transaction Parameters ### transactionType Default transaction type to display first. If not passed in, the Meld Checkout will default to the Buy flow. ``` &transactionType=BUY # Default to buy flow (default) &transactionType=SELL # Default to sell flow ``` ### sourceAmount Amount of fiat currency to exchange. ``` &sourceAmount=50 # $50 &sourceAmount=100 # $100 (default) &sourceAmount=1000 # $1000 ``` ### sourceCurrencyCode Fiat currency for the transaction. Defaults to country's primary currency if not provided. ``` &sourceCurrencyCode=USD # US Dollars &sourceCurrencyCode=EUR # Euros &sourceCurrencyCode=GBP # British Pounds ``` ### destinationCurrencyCode Cryptocurrency to purchase or sell. ``` &destinationCurrencyCode=BTC # Bitcoin &destinationCurrencyCode=ETH # Ethereum &destinationCurrencyCode=USDC_BASE # USDC on Base &destinationCurrencyCode=SOL # Solana ``` ## Wallet Parameters ### walletAddress User's wallet address for the destination cryptocurrency. ``` &walletAddress=bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh # Bitcoin &walletAddress=0x742c8f2e0ce07Dd3f7E78A31E5A97D45c50fF2c8 # Ethereum &walletAddress=So11111111111111111111111111111111111111112 # Solana ``` ### walletTag Commonly known as wallet tag, destination tag, or memo, this field is only used for certain cryptocurrencies (EOS, STX, XLM, XMR, XRP, BNB, XEM, and HBAR) and must be passed in on top of the wallet address for these cryptocurrencies for the transaction to succeed. ``` &walletAddress=rDsbeomae4FXwgQjRq9bCVFeVbU8c65pNF&walletTag=123456789 ``` ## Location Parameters ### countryCode ISO 2-letter country code to override auto-detected country. > **Note:** Country is automatically detected based on user location in the Meld Checkout Integration. Use this parameter only when you need to override the automatic detection. ``` &countryCode=US # United States &countryCode=GB # United Kingdom &countryCode=CA # Canada &countryCode=DE # Germany &countryCode=AU # Australia ``` ## Payment Parameters ### paymentMethodType How the user plans to pay for cryptocurrency. ``` &paymentMethodType=CREDIT_DEBIT_CARD # Credit/Debit Card &paymentMethodType=BANK_TRANSFER # Bank Transfer &paymentMethodType=APPLE_PAY # Apple Pay &paymentMethodType=GOOGLE_PAY # Google Pay ``` ## User Identification ### customerId Meld's internal customer ID. Do not use with externalCustomerId. ``` &customerId=cust_abc123def456 ``` ### externalCustomerId Your internal customer ID. Do not use with customerId. ``` &externalCustomerId=user_789 &externalCustomerId=customer_12345 ``` ### externalSessionId Your internal session ID for tracking and analytics. ``` &externalSessionId=session_abc123 &externalSessionId=checkout_456 ``` ## UI Parameters ### theme Meld Checkout color theme. ``` &theme=lightMode # Light theme (default) &theme=darkMode # Dark theme ``` ### redirectUrl URL to redirect users after transaction completion. Supports deep links for mobile apps.
> **Note:** Meld does not pass transaction completion data or status through redirect parameters. The redirectUrl is used only to navigate users back to your application. Use webhooks to track transaction status and completion. ``` &redirectUrl=https://yourapp.com/success # Website &redirectUrl=myapp://transaction-complete # Mobile deep link &redirectUrl=https://yourapp.com/crypto/complete # Custom success page ``` ## Field Locking Lock any parameter to prevent user editing by passing the parameter with the word "Locked" after it. Locking a field will make it so that the user cannot edit the values passed into those fields. Field locking only applies to the Meld Checkout interface. However, if you lock the `sourceCurrencyCode`/`destinationCurrencyCode` or `walletAddress` field in the Meld Checkout widget, that field will also be locked in the partner's widget for the providers that support it. See [White-Label customization → Locking fields](/docs/stablecoins/white-label-api-integration/whitelabel-api-guide/white-label-customization) for the list of providers. ### Lockable Parameters: The locked params are as follows: * `countryCodeLocked` - Pass the country code value (e.g., `countryCodeLocked=US`) * `paymentMethodTypeLocked` - Pass the payment method value (e.g., `paymentMethodTypeLocked=CREDIT_DEBIT_CARD`) * `sourceAmountLocked` - Pass the amount value (e.g., `sourceAmountLocked=100`) * `sourceCurrencyCodeLocked` - Pass the currency code value (e.g., `sourceCurrencyCodeLocked=USD`) * `destinationCurrencyCodeLocked` - Pass the crypto code value (e.g., `destinationCurrencyCodeLocked=BTC`) * `walletAddressLocked` - Pass the wallet address value (e.g., `walletAddressLocked=bc1qxy2k...`) * `themeLocked` - Pass the theme value (e.g., `themeLocked=darkMode`) ### Important Rules: * If you pass in both the unlocked and locked version of a param (for example `countryCode` and `countryCodeLocked`), the locked param will be used * Locked parameters take the actual value, not `=true` ### Field Locking Example: ``` meldcrypto.com/?publicKey=your_public_key&destinationCurrencyCodeLocked=BTC&walletAddressLocked=bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh ``` ## Complete Examples ### Basic Bitcoin Purchase ``` meldcrypto.com/?publicKey=your_public_key&sourceAmount=100&destinationCurrencyCode=BTC&countryCode=US ``` ### Locked Ethereum Purchase ``` meldcrypto.com/?publicKey=your_public_key&destinationCurrencyCodeLocked=ETH&walletAddressLocked=0x742c8f2e0ce07Dd3f7E78A31E5A97D45c50fF2c8&themeLocked=darkMode ``` ### Mobile App Integration ``` meldcrypto.com/?publicKey=your_public_key&externalCustomerId=user_12345&externalSessionId=session_abc789&redirectUrl=myapp://crypto-success&theme=lightMode ``` ### Locked BONK Purchase on Solana ``` meldcrypto.com/?publicKey=your_public_key&destinationCurrencyCodeLocked=BONK_SOLANA&walletAddressLocked=6chn9n4CLhNdyBpiLLj9ouUUUmEQYBLWfpUMMuHB9K3k&themeLocked=darkMode ``` ### Marketplace Token Sale ``` meldcrypto.com/?publicKey=your_public_key&destinationCurrencyCodeLocked=USDC&sourceAmountLocked=50&countryCodeLocked=US ``` ## Parameter Validation Rules ### Required Combinations: * **walletTag** requires **walletAddress** for supported cryptocurrencies (EOS, STX, XLM, XMR, XRP, BNB, XEM, and HBAR) * **customerId** and **externalCustomerId** are mutually exclusive - do not pass in both ### Important Parameter Rules: * **Locked parameters override unlocked versions**: If you pass in both the unlocked and locked version of a param (for example `countryCode` and `countryCodeLocked`), the locked param will be used * **Provider UI locking**: If you lock the sourceCurrencyCode/destinationCurrencyCode or walletAddress field in the Meld Checkout, then that field will also be locked in the partner's widget for the providers that support it * **Locked parameters take actual values**: Pass the actual value, not `=true` (e.g., `walletAddressLocked=6chn9n...` not `walletAddressLocked=true`) ### Invalid Parameter Handling: * **Unrecognized parameters** are ignored silently * **Invalid values** fall back to defaults * **Any param with unrecognized or unsupported values** will be ignored ## URL Encoding Always URL encode parameter values that contain special characters: ```javascript theme={null} const walletAddress = 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh'; const redirectUrl = 'https://myapp.com/success?transaction=complete'; const meldCheckoutUrl = 'meldcrypto.com/?publicKey=your_public_key'; const url = `${meldCheckoutUrl}&walletAddress=${encodeURIComponent(walletAddress)}&redirectUrl=${encodeURIComponent(redirectUrl)}`; ``` ## Testing Parameters Focus on successful transaction flows in sandbox environment: ``` meldcrypto.com/?publicKey=your_public_key&sourceAmount=10&destinationCurrencyCode=BTC&countryCode=US ``` Use small amounts and focus on green path flows to validate your integration. *** **Next:** [Customization Options](/docs/stablecoins/meld-checkout-integration/meld-checkout-guide/meld-checkout-customization) for advanced styling and configuration. # Quickstart Source: https://docs.meld.io/docs/stablecoins/meld-checkout-integration/meld-checkout-quickstart This is the 5 minute quickstart guide for executing the Meld checkout flow for the first time. This quickstart is for product teams and developers who want to launch Meld Checkout for the first time. You will get the hosted Meld Checkout URL from the dashboard and embed or redirect to it from your app so users can buy or sell crypto without any UI work on your side. # Meld Checkout Integration Quick Start **Time required:** 5 minutes **Difficulty:** No coding required ## Before you begin * Access to the Meld dashboard (sandbox and production are separate) * A page in your app where you can render a link or trigger a redirect **Sandbox vs production:** Each environment has its own Meld Checkout URL. Sandbox URLs only work with test data; production URLs handle real money. Always finish testing in sandbox first. ## Step 1: Get Your Meld Checkout URL The Meld Dashboard provides a complete, ready-to-use Meld Checkout URL. ### Process: 1. **Check your email** for the Meld dashboard invitation 2. **Click the invitation link** and log in 3. In the dashboard, **navigate to Developer tab** 4. **Find "Meld Checkout URL"** - this is your complete URL 1. Note that you will need Meld to enable this key for you. If you don't see it in the Meld dashboard, please reach out to your Meld account manager to request this being added. 5. **Copy and save** your Meld Checkout URL securely ### **Note:** You get a complete URL from the dashboard, not just a key. This URL is ready to use immediately! ## Step 2: Use Your Meld Checkout URL ### Your Dashboard URL (Ready to Use): ``` [Copy the complete URL from your Meld Dashboard Developer tab] ``` ### Add Optional Parameters for Customization: **See the [URL parameters guide](/docs/stablecoins/meld-checkout-integration/meld-checkout-guide/url-parameters) for all available parameters and options.** **See the [Customization guide](/docs/stablecoins/meld-checkout-integration/meld-checkout-guide/meld-checkout-customization) for all available styling options.** ``` meldcrypto.com/?publicKey=your_meld_checkout_key&sourceAmount=100&destinationCurrencyCode=BTC&countryCode=US ``` ### Integration Examples: **HTML Link:** ```html theme={null} Buy Crypto ``` **JavaScript Redirect:** ```javascript theme={null} function buyCrypto() { window.location.href = 'meldcrypto.com/?publicKey=your_meld_checkout_key'; } ``` ## Step 3: Test Your Integration ### Sandbox Testing: 1. **Use your Meld Checkout URL** (get from Developer tab in sandbox environment) 2. **Open the URL** in your browser 3. **Complete the test transaction** using [sandbox test credentials](/docs/stablecoins/sandbox-guide/test) 4. **Verify in dashboard** - check Transactions tab ## Step 4: Go Live ### Production Setup: 1. **Use your Meld Checkout URL** (get from Developer tab in production environment) 2. **Test with small real transaction** 3. **Start directing users** to the Meld Checkout widget! ✅ **Meld Checkout Integration Complete!** Users can now buy digital assets directly through your application. ➡️ **[Meld Checkout Customization Guide](/docs/stablecoins/meld-checkout-integration/meld-checkout-guide/meld-checkout-customization)** — styling and advanced options ## Next steps If you're ready to customize and complete your integration, see the end-to-end [Meld Checkout Guide](/docs/stablecoins/meld-checkout-integration/meld-checkout-guide).
# Sandbox Testing Guide Source: https://docs.meld.io/docs/stablecoins/sandbox-guide/index # Sandbox Environment Guide This guide is for developers and QA testing their Meld crypto integration before going live. It covers what the sandbox is for, what it cannot do, which providers have usable sandboxes, and the supported test combinations. Note that the countries, tokens, fiat currencies, and payment methods supported in sandbox are a small subset of the ones supported in production. We recommend you only test in sandbox with the below combinations for the best results: **Tokens** * BTC * ETH * USDC **Countries** * US * Europe **Payment Methods** * CREDIT\_DEBIT\_CARD * APPLE\_PAY * GOOGLE\_PAY **Fiat Currencies** * USD * EUR In production you will be able to use thousands of other combinations — see [Digital asset coverage](/docs/stablecoins/digital-asset-coverage).
## ⚠️ Important: Sandbox Purpose and Limitations ### What Sandbox is For The sandbox environment is designed for: * **Exploring transaction flows** and understanding how the process works * **Testing UI integration** and user experience * **Familiarizing yourself** with provider interfaces * **Initial development** and basic functionality testing * **Happy path combinations** of the most widely used tokens ### What Sandbox is NOT For **🚨 Critical Warning:** The sandbox environment should **NOT** be relied upon for: * **Coding solutions related to calculations** * **Production data accuracy validation** * **Finalizing business logic** based on API responses * **Performance testing** with accurate response times * **Coverage testing** because onramp sandboxes are very limited in the tokens they support **API responses in the sandbox environment may lack complete data accuracy** and should only be used for understanding flow structure, not for building calculation logic. ## 🌍 Environment Access | Environment | Widget URL | API Base URL | | -------------- | --------------------------- | ------------------------ | | **Sandbox** | `https://sb.meldcrypto.com` | `https://api-sb.meld.io` | | **Production** | `https://meldcrypto.com` | `https://api.meld.io` | ## 🏦 Sandbox-Friendly Providers ### ✅ Recommended for Testing (Full Sandbox Support) These providers offer reliable sandbox environments ideal for testing: * **Unlimit** - Complete sandbox functionality with test cards * **Simplex** - Reliable test environment with test wallet support * **TransFi** - Good sandbox with specific test credentials * **Transak** - Full-featured sandbox with test SSN/PAN numbers * **Sardine** - Phone-based testing with predictable OTP system ### ⚠️ Limited Sandbox Support These providers have sandbox limitations: * **Banxa** - Sandbox often rejects KYC/transactions; production testing recommended * **Stripe** - Functions like production; requires real payment for full testing * **Paybis** - Basic sandbox functionality * **BTC Direct** - Limited sandbox features ### ❌ Production Only These providers do not offer sandbox environments: * **Robinhood** - Production environment only * **Coinbase Pay** - Production environment only * **Blockchain.com** - Production environment only * **Alchemy Pay** - Production-like sandbox requiring real payments ## 🔄 Flow Testing Capabilities ### Buy Flow (Onramp) * ✅ **Fully testable** end-to-end in sandbox * Complete transaction simulation available * Test cards and credentials work for most providers * **Recommended starting point** for all integrations ### Sell Flow (Offramp) * ⚠️ **Limited testing** - quote generation only * Cannot complete full transactions in sandbox * Real cryptocurrency/fiat required for end-to-end testing * Use for UI flow testing only ### Transfer Flow * ⚠️ **Limited testing** - configuration and quotes only * Cannot execute actual transfers in sandbox * Real wallet addresses required for testing * Currently available in **White-Label API only** ## 🚀 Testing Best Practices ### 1. Start with Buy Flow * Begin all integrations with buy flow testing * Use recommended providers (Unlimit, Simplex, TransFi) * Complete full transaction flow before moving to other flows ### 2. Provider Selection for Testing * Choose providers from "Recommended for Testing" list * Avoid production-only providers during initial development * Test with multiple providers to understand variations ### 3. Sandbox Data Handling * **Never build calculation logic** based on sandbox responses * Use sandbox only for flow understanding and UI testing * Validate all calculations and business logic in production ### 4. Progressive Testing Approach ``` Sandbox (Flow Testing) → Staging (Integration Testing) → Production (Final Validation) ``` ## 🔧 Integration Testing Workflow 1. **Sandbox Flow Testing** * Test UI integration * Understand provider interfaces * Validate basic functionality 2. **Production Validation** * Test with real (small) transactions * Validate calculation accuracy * Confirm webhook functionality 3. **Go Live** * Deploy with confidence * Monitor real transaction data * Iterate based on production insights ## 📞 Support For sandbox-related questions or issues: * Check provider-specific testing credentials in the detailed testing guide * Review flow limitations before troubleshooting * Contact Meld support for integration guidance Remember: **Sandbox is for learning the flow, production is for validating the logic.** # White-Label API and Meld Checkout Testing Credentials Source: https://docs.meld.io/docs/stablecoins/sandbox-guide/test # Testing Overview This page is for developers and QA running sandbox transactions through Meld's White-Label API or Meld Checkout flows. It lists test cards, wallet addresses, KYC triggers, and quirks for each onramp's sandbox. ## Quick start **New to testing?** Start with the [Sandbox Environment Guide](/docs/stablecoins/sandbox-guide) to understand sandbox limitations and best practices. **Ready to test?** This page contains specific testing credentials for each provider. # Provider testing credentials Before testing, review the [Sandbox Environment Guide](/docs/stablecoins/sandbox-guide) to understand sandbox limitations. ## Testing Notes * Most providers have relaxed KYC requirements in sandbox * Some providers validate wallet addresses match the cryptocurrency type * Use test credentials provided below for each provider * Start with **Buy Flow testing** as it's fully supported in sandbox ## Unlimit Use the following test cards when testing in [Unlimit's](https://docs.gatefi.com/) sandbox. | Card Type | Card Number | Expiration | CVV | | :--------- | :------------------ | :--------- | --- | | Visa | 4000 0000 0000 0085 | 10/30 | 123 | | Mastercard | 5100 0000 0000 0065 | 10/30 | 123 | Use a real wallet address when testing, for example when trying to buy `ETH_GOERLI` (ETH on TestNet) use a real `ETH` wallet address. ## Banxa **While Banxa's sandbox does sometimes allow users to complete transactions, often KYC or transactions are rejected. Therefore Meld recommends testing in Banxa's production rather than its sandbox.** In Banxa's sandbox, the OTP is **7203**. Use the following test cards when testing in [Banxa's](https://docs.banxa.com/docs/testing-information) sandbox. | Card Type | Info | Card Number | Expiration | CVV | | :-------- | :--- | :------------------ | :--------- | --- | | Visa | US | 4111 1111 1111 1111 | 01/30 | 555 | Banxa allows real wallet addresses while testing in their sandbox. ## BTC Direct Use the following test cards when testing in [BTC Direct's](https://docs.adyen.com/development-resources/testing/) sandbox. | Card Type | Card Number | Expiration | CVV | | :--------- | :------------------ | :--------- | --- | | Visa | 4111 1111 4555 1142 | 03/30 | 737 | | Mastercard | 2222 4000 7000 0005 | 03/30 | 737 | BTC Direct allows real wallet addresses while testing in their sandbox. ## TransFi Use the following test cards when testing in [TransFi's](https://docs.transfi.com/docs) sandbox. | Card Name | Card Number | Expiration | CVV | | :-------- | :------------------ | :--------- | --- | | FL-BRW1 | 4000 0278 9138 0961 | 11/30 | 123 | Test in TransFi's sandbox using `BTC` and the wallet address `2N3oefVeg6stiTb5Kh3ozCSkaqmx91FDbsm`. ## Transak Use the following test cards when testing in [Transak's](https://docs.transak.com/docs/test-credentials) sandbox. | Card Type | Card Number | Expiration | CVV | | :-------- | :------------------ | :--------- | --- | | Visa | 4111 1111 1111 1111 | 10/33 | 123 | | Visa | 4485 1415 2054 4212 | 10/33 | 100 | For US: Use SSN number `000000001` to create a test account.\ For India: Use PAN number `ABCDE1234A` to place an INR order. Transak requires a wallet address that matches the token passed in, for example if you are testing with `BTC_TESTNET` then use wallet address `2N3oefVeg6stiTb5Kh3ozCSkaqmx91FDbsm`. ## Onramp Money In Onramp.Money's Sandbox, to get past the phone number screen, you can use the phone number `+91 8660031402` and the passcode `112233`. Use any real wallet address when testing, and Meld recommends testing with the token ETH. ## Fonbnk Fonbnk requires a real wallet address for whatever token is selected in their sandbox. Also, there is no test card available, since the payment methods Fonbnk supports are Bank Transfer and Airtime. Use Bank Transfer for testing purposes. ## Paybis Use the following test cards when testing in Paybis' sandbox. | Card Type | Card Number | Expiration | CVV | | :-------- | :------------------ | :--------- | --- | | Visa | 4111 1111 1111 1111 | 10/30 | 123 | Paybis requires a wallet address that matches the token passed in, for example if you are testing with `BTC_TESTNET` then use wallet address `2N3oefVeg6stiTb5Kh3ozCSkaqmx91FDbsm`. ## Koywe When testing in Koywe's sandbox, a single user will only be able to make transactions in 1 particular country. Koywe also requires real ID for it's sandbox, but it doesn't have to match the country of the transaction (for example a US ID will work even when making a Chile transaction). We recommend testing with Chile and the payment method Khipu in order for transactions to be completed successfully. Use the following values to complete a transaction. | Amount | Payment Method | RUT | Khipu Password | Khipu Secondary Codes | Bank Account | | :--------------------------------------------- | :------------- | :----------- | -------------- | :-------------------- | :----------- | | 20000 CLP (Khipu won't work if its over 25000) | Khipu | 28.666.939-5 | 1234 | 11, 22, 33 | Any | ## Robinhood Robinhood does not have a sandbox, only a production environment. Therefore you will have to make a real payment to fully test Robinhood's onramp. ## Coinbase Pay Coinbase Pay does not have a sandbox, only a production environment. Therefore you will have to make a real payment to fully test Coinbase Pay's onramp. ## Blockchain.com Blockchain.com does not have a sandbox, only a production environment. Therefore you will have to make a real payment to fully test Blockchain.com's onramp. ## Stripe Use the following test values when testing in [Stripe's](https://docs.stripe.com/crypto/integrate-the-onramp#test-mode-values) sandbox. | OTP | SSN | Address Line 1 | Card Number | Expiration | CVV | | :----- | :-------- | :------------------- | ------------------- | :--------- | :-- | | 000000 | 000000000 | address\_full\_match | 4242 4242 4242 4242 | 10/30 | 123 | If you are bringing over your own Stripe account: 1. please reach out to Stripe to enable `preferred_payment_method` and `source_total_amount` for your account, or else you will see errors. 2. When setting up Stripe webhooks, do the following steps in order: 1. Grab your Stripe API keys from the Stripe dashboard and put them in the Meld dashboard, putting in any value for the Webhook Secret. 2. Take the Meld webhook url from the Meld dashboard and go to the Stripe dashboard and set up webhooks to Meld. Select all events. 3. Take the generated Stripe webhook secret and go back to the Meld dashboard and edit that value to add that in so that webhooks work properly. ## Alchemy Pay Alchemy Pay's sandbox functions like a real production environment. Therefore you will have to make a real payment to fully test Alchemy Pay's onramp. ## Topper Use the following test information when testing [Topper](https://docs.topperpay.com/environments/) in sandbox. You can use rw2ciyaNshpHe7bCHo4bRWq6pqqynnWKQg for the token XRP\_XRPLEDGER and if prompted for a wallet tag use 224333255 | Card Type | Card Number | Expiration | CVV | | :-------- | :--------------- | :-------------- | ----------- | | Visa | 4921817844445119 | Any future date | Any 3 digit | ## Mercuryo Use the following test values when testing in Mercuryo's sandbox. | Card Type | Cardholder Name | Card Number | Expiration | CVV | | :-------- | :-------------- | :------------------ | :-------------- | --------------------------------- | | Visa | Cardholder Name | 4444 4444 4444 3333 | any future date | 123 | | Visa | Cardholder Name | 5555 4444 3333 1111 | any future date | CVV 123 for success, 555 for fail | Your IP address must be whitelisted by Mercuryo in order to launch their sandbox widget. ## Kryptonim Use the following test values when testing in Kryptonim's sandbox. | Card Type | Cardholder Name | Card Number | Expiration | CVV | | :-------- | :-------------- | :--------------- | :--------- | --- | | Visa | any name | 4929111260419572 | 05/2028 | 790 | ## Swapped Use the following test values when testing in Swapped's sandbox. | Card Type | Cardholder Name | Card Number | Expiration | CVV | | :-------- | :-------------- | :------------------ | :--------- | --- | | Visa | any name | 4929 4205 7359 5709 | 10/31 | 123 |
# Product 3: Virtual Account and Payouts Source: https://docs.meld.io/docs/stablecoins/virtual-account-integration/index Overview of the Virtual Account Flow This page is for product and engineering teams evaluating Meld's Virtual Account product. It explains what virtual accounts are, how onramp vs offramp flows work, and when this product is the right choice. For a step-by-step integration, jump to the [Virtual Account Quickstart](/docs/stablecoins/virtual-account-integration/virtual-account-quickstart). ## What are virtual accounts? A virtual account is a dedicated set of receiving details — a bank account number, IBAN, or crypto address — assigned to a specific customer. When funds arrive at those details, the provider automatically converts and settles them to the destination you configured (e.g. a crypto wallet for onramp, or a bank account for offramp). **Onramp (fiat to crypto):** The customer receives unique bank details (ACH routing + account number, SEPA IBAN, etc.). They deposit fiat from their bank; the provider converts it and delivers stablecoin to the specified wallet address. **Offramp (crypto to fiat):** The customer receives a crypto deposit address. They send stablecoin from their wallet; the provider converts and pays out fiat to the bank details on file. In this flow, typically a virtual account is not created per business / user unless necessary. Instead the onramp sends money directly from the provider's funding account to the business' / user's end bank account. Because each virtual account is unique to a customer, deposits are automatically attributed — no manual reference matching required. Meld orchestrates the provider integration, KYC, quoting, and settlement tracking so you work with a single API surface regardless of which provider (Noah, Due, etc.) handles the underlying rails. Please reach out to Meld for more information about virtual accounts and how to enable them.
# Full Integration Guide Source: https://docs.meld.io/docs/stablecoins/virtual-account-integration/virtual-account-guide This is the full end to end integration guide for integrating the virtual account flow. This is the end-to-end Virtual Account integration guide for developers. It walks through the complete API sequence — customer creation, KYC, quoting, order creation, and webhook tracking — that you'll need to build a production-grade onramp or offramp. ## Before you begin * You have completed the [Virtual Account Quickstart](/docs/stablecoins/virtual-account-integration/virtual-account-quickstart) end to end at least once in sandbox * You have a Meld API key with virtual-account access (sandbox first, production for go-live) * You have a webhook endpoint configured in **Developer → Webhooks** in the dashboard * If you are KYCing end users, you have at least one virtual-account provider (Noah, Due, or Brale) enabled on your account # Notes * This flow is supported for both businesses and individuals. Businesses will have to KYB with Meld, which is manual. Users will have to KYC via Meld with the onramp, which can be done via API, and is described below. * While both Noah and Due support this flow for both businesses and users, Brale only supports this flow for businesses at this time. Brale also requires an additional *redemption* step in the onramp flow in order for the business to receive their crypto. # Full Integration Guide This is the end-to-end guide for executing virtual account transactions (onramp and offramp) through the Meld API. > **API compatibility:** New fields may appear in responses without a version bump. Use flexible JSON parsing and do not reject unknown properties. Breaking changes ship under a dated `Meld-Version`. *** ## How it works 1. Create a **customer** in the Account API for the individual or business that will transact. 2. **KYB** your business with Meld. 3. If your end users will be making transactions, then **KYC** each customer with the provider (e.g. Noah, Due). Meld asks the provider to verify the customer; the provider responds asynchronously. 4. Get a **quote** from the Payment API for the currency pair and amount. 5. Create an **onramp order** (fiat to crypto) or **offramp order** (crypto to fiat). 6. The business or individual completes the fiat or crypto leg (bank transfer or wallet send). 7. Track progress via **webhooks** and the Transactions API. *** ## 1. Create a Customer API Endpoint: `POST /accounts/customers` **Request:** ```json theme={null} { "externalId": "your-internal-user-id-123", "name": { "firstName": "John", "lastName": "Doe" }, "email": "john@example.com", "phone": "+14155551234", "dateOfBirth": "1990-03-15", "type": "INDIVIDUAL" // or BUSINESS if you are a business } ``` **Response (abbreviated):** ```json theme={null} { "id": "WmYYgvN8ukpV62N3m4u3ee", "accountId": "W2aRZnYGPwhBWB94iFsZus", "externalId": "your-internal-user-id-123", "name": { "firstName": "John", "lastName": "Doe" }, "email": "john@example.com", "phone": "+14155551234", "dateOfBirth": "1990-03-15", "status": "ACTIVE", "type": "INDIVIDUAL", "serviceProviderCustomers": [], "addresses": [] } ``` Save the returned `id` — you will pass it as `customerId` on every subsequent call. If you plan on using `DUENETWORK` later for executing transactions, you must also specify the customer's address by calling `POST /accounts/customers/{customerId}/addresses`. > **Note:** Order and transaction requests also accept optional `subaccountCustomerId` and `externalSubaccountCustomerId` fields. Use these to tag which sub-account or business grouping a transaction belongs to for reporting and filtering purposes. *** ## 2. KYC / KYB If you are transacting as a business, you will KYB with Meld. If your end users are transacting, each will have to KYC once with each onramp they use. KYC happens via Sumsub. If you have already KYCed your users with Sumsub, you can pass in the KYC token — see [You KYC the user](/docs/stablecoins/unified-kyc/you-kyc-the-user). If you'd prefer Meld to KYC the user, that process is described in [Meld KYCes the user](/docs/stablecoins/unified-kyc/meld-kyces-the-user). To test KYC in the sandbox, follow the steps in [KYC testing](/docs/stablecoins/sandbox-guide/kyc-testing). **Initiate KYC:** API endpoints: * `POST /accounts/customers/{customerId}/kyc/initiate` * `PATCH /accounts/customers/{customerId}/kyc/initiate` **Request:** ```json theme={null} { "serviceProvider": "SUMSUB", "mode": "HOSTED_URL" } ``` Alternatively, if you already have a user who has passed [KYC on your Sumsub account](/docs/stablecoins/unified-kyc/you-kyc-the-user), you can import it into Meld by sharing the Sumsub applicant token (`TOKEN_IMPORT` mode). To import the KYC of the user, call `POST /accounts/customers/{customerId}/kyc/initiate`. The `customerId` is in the path, and your request body looks like this: ```json theme={null} { "serviceProvider": "SUMSUB", "mode": "TOKEN_IMPORT", "serviceProviderDetails": { "kycToken": "_act-sbx-jwt-eyJhbGciOiJub25lIn0.eyJqdGkiOiJfYWN0LXNieC1mNGFlNWYwYy1iYjFmLTQwZmUtYTk1YS1jZWM0MmY3OTBjYmIiLCJ1cmwiOiJodHRwczovL2FwaS5zdW1zdWIuY29tIn0.", "applicantId": "Sumsub applicant id" } } ``` **Response:** ```json theme={null} { "customerId": "WmYYgvN8ukpV62N3m4u3ee", "serviceProvider": "SUMSUB", "status": "PENDING", "url": "https://kyc-provider.example.com/verify/abc123" } ``` Direct the user to the `url` to complete verification. If you are not passing in a token, Sumsub will ask the user for all required fields, and if the token is passed in, Sumsub will only prompt the user for any missing fields. KYC completes **asynchronously** — the provider notifies Meld via webhook, and Meld publishes a `CUSTOMER_KYC_STATUS_CHANGE` webhook to your endpoint. **Sample KYC webhook payload:** ```json theme={null} { "eventType": "CUSTOMER_KYC_STATUS_CHANGE", "eventId": "BBxyzABC123456defGHIjk", "timestamp": "2025-02-24T14:20:00.000000Z", "accountId": "WQ5RyhdFzE45qjsomdzQ1u", "version": "2025-03-01", "payload": { "customerId": "WmYYgvN8ukpV62N3m4u3ee", "status": "APPROVED" } } ``` **Poll for KYC Status:** API Endpoint: `GET /accounts/customers/{customerId}` Check `serviceProviderCustomers[].kyc.status`. Values: `PENDING`, `APPROVED`, `REJECTED`, `EXPIRED`, `UNKNOWN`. Wait for the kyc.status to be `APPROVED` before proceeding. The customer must be KYC-approved before any transactions can be created. In both KYC initiation modes (**HOSTED\_URL** and **TOKEN\_IMPORT**), you can optionally specify Virtual Account providers you want to share the user's KYC with after Sumsub approves submission, via the `kycShareProviders` field. Alternatively, you can add share providers later by calling `PATCH /accounts/customers/{customerId}/kyc/initiate`. ```json theme={null} { "serviceProvider": "SUMSUB", "mode": "HOSTED_URL", "kycShareProviders": ["NOAH", "DUENETWORK"] } ``` ```json theme={null} { "serviceProvider": "SUMSUB", "mode": "TOKEN_IMPORT", "kycShareProviders": ["NOAH", "DUENETWORK"], "serviceProviderDetails": { "kycToken": "_act-sbx-jwt-eyJhbGciOiJub25lIn0.eyJqdGkiOiJfYWN0LXNieC1mNGFlNWYwYy1iYjFmLTQwZmUtYTk1YS1jZWM0MmY3OTBjYmIiLCJ1cmwiOiJodHRwczovL2FwaS5zdW1zdWIuY29tIn0.", "applicantId": "Sumsub applicant id" } } ``` After Sumsub verifies the user's submission, the KYC token will be automatically shared with all specified share Virtual Account providers, effectively onboarding the user on Virtual Account's platforms. The Virtual Account provider may require additional KYC for users in case the data shared by Sumsub is insufficient. To track this, you can either listen for `CUSTOMER_KYC_STATUS_CHANGE` webhooks. **Supported share providers and their requirements** * Noah * Due Network. Requires customer country to be specified — call `POST /accounts/customers/{customerId}/addresses` to add an address. **Expected Webhook Response** ```json theme={null} { "eventType": "CUSTOMER_KYC_STATUS_CHANGE", "eventId": "customerId", "timestamp": "2022-02-24T16:36:41.717262Z", "payload": { "accountId": "WeP9eoFziQX4yXE5abcfec", "customerId": "customerId", "serviceProvider": "SUMSUB", "status": "APPROVED", "kycRecepient": { "serviceProvider": "Virtual account provider", "status": "PENDING" // PENDING, REJECTED, APPROVED }, "statusUpdatedAt": "2022-02-24T16:36:41.717262Z" } } ``` After receiving a webhook event, you can call `GET /accounts/customers` for more details. For example, when the Virtual Account provider requires additional KYC on their platform, you will receive a webhook event with `kycRecipient.status=PENDING`. Then, when calling `GET /accounts/customers`, you will receive the following response: ```json theme={null} { "id": "customerId", "accountId": "accountId", "serviceProviderCustomers": [ { "serviceProvider": "Virtual Account Provider", "id": "provider customer id", "kyc": { "status": "PENDING", "updatedAt": "2026-04-08T20:04:20.457281Z", "additionalInfo": { "HostedURL": "https://virtual-provider-paltform.com/additionalKycHostedURL" // addtional provider specific kyc fields, like addtional requirements (documents, questionnaire, etc.) }, "onboardingMethod": { "kycProvider": "SUMSUB", "onboardingType": "KYC_TOKEN_SHARE" } } } ], "status": "ACTIVE", "type": "INDIVIDUAL" } ``` You will need to direct your user to `HostedURL` so they can pass additional KYC on the Virtual Account provider's platform. After users complete additional KYC and the Virtual Account provider verifies it, you will receive another `CUSTOMER_KYC_STATUS_CHANGE` event with `kycRecipient.status=APPROVED`. The new status will also be reflected in `GET /accounts/customers` responses. **Errors:** * Calling `initiate` again for the same customer + provider returns **409**. *** ## 3. Configure webhooks In the Meld dashboard (**Developer > Webhooks**), add your endpoint URL and subscribe to at minimum: * `TRANSACTION_CRYPTO_PENDING` * `TRANSACTION_CRYPTO_TRANSFERRING` * `TRANSACTION_CRYPTO_COMPLETE` * `TRANSACTION_CRYPTO_FAILED` * `CUSTOMER_KYC_STATUS_CHANGE` You can find all webhook events in [Webhook events](/docs/stablecoins/for-all-products/webhook-events). Verify webhook signatures per [Webhook authentication](/docs/stablecoins/for-all-products/webhooks-authentication). *** ## 4. Get a Quote API Endpoint: `POST /payments/virtual-account/crypto/quote` Note that the provider Brale doesn't support quotes. All Brale transactions are 1:1, aka for \$100 you will receive 100 USDC. **Request:** ```json theme={null} { "countryCode": "US", "sourceAmount": 100, "sourceCurrencyCode": "USD", "destinationCurrencyCode": "USDC", "paymentMethodType": "ACH", "customerId": "WmYYgvN8ukpV62N3m4u3ee" } ``` For a **sell** quote, swap the currency codes: `sourceCurrencyCode` is the crypto, `destinationCurrencyCode` is the fiat. **Response (abbreviated):** ```json theme={null} { "quotes": [ { "transactionType": "CRYPTO_PURCHASE", "sourceAmount": 100, "sourceCurrencyCode": "USD", "destinationAmount": 99.10, "destinationCurrencyCode": "USDC", "totalFee": 0.90, "networkFee": 0.00, "transactionFee": 0.90, "exchangeRate": 1.0, "paymentMethodType": "ACH", "serviceProvider": "NOAH" } ] } ``` **Key fields per quote:** | Field | Meaning | | ------------------- | ---------------------------------------------------- | | `sourceAmount` | What the user spends (fiat for buy, crypto for sell) | | `destinationAmount` | What the user receives | | `totalFee` | Total fees in the fiat currency | | `serviceProvider` | Provider backing this quote | | `transactionType` | `CRYPTO_PURCHASE` (buy) or `CRYPTO_SELL` (sell) | Present quotes to the user. Capture the selected `serviceProvider` for the next step. *** ## 5. Create an Order ### Buy (onramp) — fiat to crypto API Endpoint: `POST /payments/virtual-account/ramp/onramp/order` Use the `customerId` from Step 1. The `serviceProvider` comes from the quote the user selected. **Request:** ```json theme={null} { "customerId": "WmYYgvN8ukpV62N3m4u3ee", "sourceAmount": 100, "sourceCurrencyCode": "USD", "destinationCurrencyCode": "USDC", "destinationWalletAddress": "0x51FB80013111111111111112121111111", "paymentMethodType": "ACH", "serviceProvider": "NOAH" } ``` **Response:** ```json theme={null} { "orderId": "WeP9eoFziQX4yXE5abcfec", "paymentMethodType": "ACH", "receivingBankInformation": { "accountNumber": "9876543210", "routingNumber": "021000021" }, "serviceProviderDetails": {} } ``` The user sends fiat to the returned bank details from their banking app. The provider settles crypto to `destinationWalletAddress` after receiving the funds. ### Sell (offramp) — crypto to fiat API Endpoint: `POST /payments/virtual-account/ramp/offramp/order` **Request:** ```json theme={null} { "customerId": "WmYYgvN8ukpV62N3m4u3ee", "sourceAmount": 100, "sourceCurrencyCode": "USDC", "destinationCurrencyCode": "USD", "sourceWalletAddress": "0x51FB80013111111111111112121111111", "paymentMethod": { "type": "ACH", "owner": "John Doe", "details": { "accountType": "CHECKING", "routingNumber": "1111111111", "accountNumber": "22222222", "bankName": "Chase Bank", "beneficiaryAddress": { "street_line_1": "123 Market St.", "street_line_2": null, "city": "San Francisco", "state": "CA", "postalCode": "94115", "country": "US" }, "bankAddress": { "street_line_1": "270 Park Avenue", "street_line_2": null, "city": "New York", "state": "NY", "postalCode": "10017", "country": "US" } } }, "serviceProvider": "NOAH" } ``` **Offramp `paymentMethod.type` options:** | Type | Details schema | | ----------------------------- | ------------------------------------------------------------------------------------------------ | | `ACH` / `LOCAL_BANK_TRANSFER` | `accountType`, `routingNumber`, `accountNumber`, `bankName`, `beneficiaryAddress`, `bankAddress` | | `SEPA` | `iban` | **Response:** ```json theme={null} { "orderId": "WfR7abcDEF12345xyz9876", "paymentMethodType": "ACH", "walletAddress": "0xABCDEF1234567890ABCDEF1234567890ABCDEF12" } ``` The user sends crypto to `walletAddress` from their wallet. The provider pays out fiat to the bank details after receiving the crypto. Make sure the user sends the correct amount of crypto as in the Create Sell Order API call. *** ## 6. Track the Transaction ### Webhooks Meld sends webhooks as the transaction progresses. Correlate to your order using `virtualAccountRampOrderId` in the payload (matches the `orderId` from the order response). Find more information on the various webhook event types in [Webhook events](/docs/stablecoins/for-all-products/webhook-events). ### Sample webhook payload (buy) ```json theme={null} { "eventType": "TRANSACTION_CRYPTO_PENDING", "eventId": "AAsuLXHXD3mS1cjNBuHHzv", "timestamp": "2025-02-24T16:36:41.717262Z", "accountId": "WQ5RyhdFzE45qjsomdzQ1u", "profileId": "W9ka8vLE4ufBkSg3BEciZb", "version": "2025-03-01", "payload": { "virtualAccountRampOrderId": "WeP9eoFziQX4yXE5abcfec", "paymentTransactionId": "WePZCYJW7cdXR7SxUMp8mE", "customerId": "WmYYgvN8ukpV62N3m4u3ee", "externalCustomerId": "your-internal-user-id-123", "paymentTransactionStatus": "PENDING", "transactionType": "CRYPTO_PURCHASE", "sessionId": "WePjVaT4iBHPpqW49F419x" } } ``` For sell transactions, `transactionType` is `CRYPTO_SELL`. The `paymentTransactionStatus` field shows the current status of the transaction. Find the list of transaction statuses and their meanings in [Transaction statuses](/docs/stablecoins/for-all-products/transaction-statuses). ### Fetch the full transaction Extract `paymentTransactionId` from the webhook and call: API Endpoint: `GET /payments/transactions/{paymentTransactionId}` **Sample response:** ```json theme={null} { "id": "WePZCYJW7cdXR7SxUMp8mE", "accountId": "W2aRZnYGPwhBWB94iFsZus", "transactionType": "CRYPTO_PURCHASE", "status": "SETTLED", "sourceAmount": 100.00, "sourceCurrencyCode": "USD", "destinationAmount": 99.10, "destinationCurrencyCode": "USDC", "paymentMethodType": "ACH", "serviceProvider": "NOAH", "serviceTransactionId": "noah-tx-abc123", "orderId": "WeP9eoFziQX4yXE5abcfec", "customer": { "id": "WmYYgvN8ukpV62N3m4u3ee", "externalId": "your-internal-user-id-123" }, "countryCode": "US", "sessionId": "WePjVaT4iBHPpqW49F419x", "externalSessionId": null, "createdAt": "2025-02-24T16:36:41.000000Z", "updatedAt": "2025-02-24T17:05:12.000000Z" } ``` The `orderId` on the transaction matches `virtualAccountRampOrderId` in the webhook and the `orderId` from your order response, tying all three together. *** ## Sequence diagram ![Virtual Account Integration Flow](https://www.plantuml.com/plantuml/png/tLJHZjf657ttLrnjNn8fb03B2aNQAiRO17KNN61JhPeA3VO5HfWPPsOCfQkL-jGFgFg5-PBE3AQsPQ9kNcjBFCmvzzvpxXdpNYeYLBPB7BcbB2M2x42WJ3cJU8zIaZNsCU47LmX-02KoB16N9Dgk1SzOxF642_WkyCrROiWDYVc1iZMiI2BHAKFuEKCM8Jmv0BPztgHZd_DXm9cQqTyHMgtoKSwzjey6xkWAxoZ3FXSnYXprx5D6QuAxvdKq9IH2qOYcXAbAZ_RWndFRLKAjZTyBMa6lIYgfRBcTGTUhmpDrJ12MF8chM4ZYsEoirNQCvqXnKT7KzSnxXkgctfht39j1rSrgPAZvwIMVEVG1IOpbqi0-PxY-W7xG2TnwjKakjp6WUwfFBm_DmLhVmOr_4tGBGdsf1LHTQ7vUbV5Sle3855L7U_qgedhsCQUrnVQ2jxwvcIKPNU_QRRkcSre425UFF1FW9LGFkG8YWJA5Qq4K5mungS2Lps9ua2Wid4aKrGPk5Ed92jnmP1aaYbILyUEZ1w7WrgIbR8zHbH6IumvBFL9oT7BNLtt2jj04RqCeiw0WDBdWuMTSRZZVVc9QRyffRkUmSPpRMvWfJA45N_xu6po1H25ucLHyXm9kRnsdC0sD7wm3U0VXP3Q79ZdPayqeBv2EnHkQM9Hf-XFG-1sSMQCOdel9RJlPxE4Z2hgY4L6Ki_yONKniFj7ukQctrgICAaeKVbL39f8t81LHcTHj_F5WD1uQ9IOtCq0tqJoFxz845SA-B6TFYQbt3ATH4fD71n594lDF2g-8FBBeTI7Yi35D7sRpoRq_NMYVYzdSdJ_C5ju7TppqBviS-ZMFdvYU_Vr9ljuEk64udNpq1yKcEtQJGrJmKyOLrbTexiw_6znJeJAIDF8GcvzDZMCOjTLPIFxoQp7gcjXP4oR8BeLNAqeKVFdpBuZ4CLNyjHrjlhhK_glq_3xqiwC3y78vtBPks6_Q0KEz5Ac5H_4R41WXtLEsDf_4YAsusDdWJBu0USBsyz1rCsGnMCsAGpuObheGykaEdAYDNAGfKIf1yo6MnDXevrDtF7Ez-MWoNdZJnt0-MUHtwmLCUnJuq-dBwDxaFhppv_x5JUQtHWd298cCUNrnr6-Z-DdHValPBK_PhLQdy4wPcVZiJYhX1K7GyTzgzqdoRUcPvjyGtu4a2IetZWLA-VNCSgcALml-Zt6zrp_PBla7) ## Testing Provider sandboxes often cannot complete full settlement — you may only reach virtual bank account creation. See [Virtual account flow testing credentials](/docs/stablecoins/sandbox-guide/virtual-account-flow-testing-credentials) for provider-specific sandbox values (Noah, Due, Brale). ## Next steps ### Testing your implementation * [**Virtual account testing credentials**](/docs/stablecoins/sandbox-guide/virtual-account-flow-testing-credentials) — provider-specific sandbox values ### Advanced features * [**Webhook events**](/docs/stablecoins/for-all-products/webhook-events) — real-time updates * [**Dashboard data**](/docs/stablecoins/additional-information/dashboard-data) — track performance ### Production deployment * [**Service-provider setup**](/docs/stablecoins/for-all-products/service-provider-setup) — configure additional onramps *** ## Support & resources * [**Postman collection**](https://www.postman.com/meldeng/workspace/meld-io-public-api-collection) — test APIs directly * [**FAQ**](/docs/stablecoins/faq) — common questions answered
# Product 1: White-Label API Source: https://docs.meld.io/docs/stablecoins/white-label-api-integration/index Overview of the White-Label API Integration Flow The White-Label API integration is for developers and product teams who want to build their own UI on top of Meld's APIs to let users buy, sell, or transfer crypto. The transaction itself — including KYC and payment — happens in the onramp's UI; you control everything before and after. This is the most popular choice due to its UI flexibility. 1. For the 30-minute quickstart guide that gets you through the flow for the first time, see the [White-Label Quickstart](/docs/stablecoins/white-label-api-integration/whitelabel-quickstart). 2. Once you have completed the quickstart, see the [end-to-end White-Label API Guide](/docs/stablecoins/white-label-api-integration/whitelabel-api-guide) for the full integration walkthrough. If you're still deciding between products, the [Overview](/docs/stablecoins/crypto-overview_) compares White-Label API, Meld Checkout, and Virtual Account side by side. # Full Integration Guide Source: https://docs.meld.io/docs/stablecoins/white-label-api-integration/whitelabel-api-guide/index This is the full end to end integration guide for integrating the white-label API flow. # Build Your Own UI with Meld APIs This guide is for developers building a custom cryptocurrency interface on top of Meld's White-Label API. You'll learn the exact sequence of API calls — from detecting the user's country to launching the provider widget and tracking the transaction — needed to ship a production-ready buy/sell/transfer flow with your own UI. ## Before you begin * ✅ **API key** from the Meld dashboard (see [White-Label Quickstart](/docs/stablecoins/white-label-api-integration/whitelabel-quickstart) Step 1) * ✅ **Webhook endpoint** configured (recommended; you can also poll the API) * ✅ **Basic understanding** of REST APIs and JSON * ✅ **Sandbox environment** — base URL `https://api-sb.meld.io` Sandbox and production credentials, base URLs, and widget URLs are separate. Develop against sandbox until your flow is ready, then switch credentials to go live. **Important:** This is White-Label API Integration, NOT Meld Checkout 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 ## Technical Architecture ``` ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Your UI │◄──►│ Meld APIs │◄──►│ Provider UI │───►│ Your UI │ │ │ │ │ │ (Widget) │ │ │ ├─────────────┤ ├─────────────┤ ├─────────────┤ ├─────────────┤ │ • Quotes │ │ • Pricing │ │ • Payment │ │ • Status │ │ • Selection │ │ • Sessions │ │ • KYC │ │ • Results │ │ • Launch │ │ • Webhooks │ │ • Compliance│ │ • Tracking │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ ``` ## 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): ```javascript theme={null} // 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: ```javascript theme={null} // Sample Code: Get supported countries list with flags GET /service-providers/properties/countries?accountFilter=true ``` ### For US Users - State Requirements: ```javascript theme={null} // 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 ``` ![](https://files.readme.io/f8d4f0f7f888b118578747b0f9b21352d1478ef730a2eba53e1476a492c0cdb7-ezgif-38c25fa2d98f23.gif) *Screenshot: Meld Widget showing automatic country detection with manual override option* ### API Response Example: ```json theme={null} { "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: ```javascript theme={null} // Sample Code: Get default currency and payment method for country GET /service-providers/properties/defaults/by-country?countries={countryCode} ``` **Response:** ```json theme={null} [ { "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: ```javascript theme={null} // Sample Code: Get all available fiat currencies for the country GET /service-providers/properties/fiat-currencies?countries={countryCode}&accountFilter=true ``` ### Get Payment Methods: ```javascript theme={null} // Sample Code: Get payment methods available for selected fiat currency GET /service-providers/properties/payment-methods?fiatCurrencies={fiatCurrency}&accountFilter=true ``` ### UI Implementation: ```javascript theme={null} // 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); ``` ![](https://files.readme.io/79ce5b8f16fe7ced3dafc545083e92b7cf374c3e1d3c643d83e409c6e36245e9-Screenshot_2025-09-02_at_2.08.10_PM.png) *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: ```javascript theme={null} // Sample Code: Get available cryptocurrencies for the country GET /service-providers/properties/crypto-currencies?countries={countryCode}&accountFilter=true ``` ### Response Example: ```json theme={null} { "cryptoCurrencies": [ { "currencyCode": "BTC", "currencyName": "Bitcoin", "networkCode": "BTC", "networkName": "Bitcoin" }, { "currencyCode": "ETH_ETHEREUM", "currencyName": "Ethereum", "networkCode": "ETHEREUM", "networkName": "Ethereum" } ] } ``` ### UI Implementation: ```javascript theme={null} // 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) })); } ``` ![](https://files.readme.io/d8047750901e8d2f687baaac9e44fac18736c63dc14d64583e7fbb8a3b799db3-ezgif-34fa46603b5728.gif) *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: ```javascript theme={null} // Sample Code: Get purchase limits for validation GET /service-providers/limits/fiat-currency-purchases?accountFilter=true ``` ### Response Example: ```json theme={null} { "limits": { "USD": { "minAmount": 20, "maxAmount": 20000, "dailyLimit": 5000, "monthlyLimit": 20000 } } } ``` ### User Input Validation: ```javascript theme={null} // 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 }; } ``` ![](https://files.readme.io/d13ccd4e7660f6c04d85a78f18419d945245c65ceff5e2ed4b2c7e39cf7c3896-Screenshot_2025-09-02_at_2.12.41_PM.png) *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. It is important that you tie each user to a Meld customerId or externalCustomerId (they map 1:1). This will ensure that the rampIntelligence suggests the best quote for the user. ### API Call: ```javascript theme={null} // Sample Code: Get real-time quotes from multiple providers POST /payments/crypto/quote ``` ### Request Body: ```json theme={null} { "externalCustomerId": "user_123", "sourceAmount": "200", "sourceCurrencyCode": "USD", "destinationCurrencyCode": "ETH", "countryCode": "US", "walletAddress": "0xfCFAa8059080D01b27ccA2B1fA086df0853397E6" } ``` ### Response Example: ```json theme={null} { "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, "partnerFee": 2, "destinationAmount": 0.04308219, "destinationCurrencyCode": "ETH", "exchangeRate": 4642.290, "paymentMethodType": "CREDIT_DEBIT_CARD", "serviceProvider": "UNLIMIT", "rampIntelligence": { "rampScore": 20.00, "lowKyc": false } }, { "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, "partnerFee": 1.96, "destinationAmount": 0.042577, "destinationCurrencyCode": "ETH", "exchangeRate": 4697.4, "paymentMethodType": "CREDIT_DEBIT_CARD", "serviceProvider": "ROBINHOOD", "rampIntelligence": { "rampScore": 19.76, "lowKyc": false } } ], "message": null, "error": null, "timestamp": null } ``` ### Key Response Fields Explained: * **`rampScore`**: Meld's recommendation score (higher = better conversion likelihood) * **`lowKyc`**: Whether provider requires minimal identity verification ![](https://files.readme.io/5e8d9532c381286e3ea1edeff72364e228c23ea704a8f6ce985fce53394d4966-ezgif-352c80746562fc.gif) *Screenshot: Meld Widget showing multiple provider quotes with ranking* ## 🏆 **MELD RECOMMENDED QUOTE RANKING** **Use Meld's`rampScore` for optimal conversion rates!** Meld provides a `rampScore` 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: ```javascript theme={null} // Sample Code: Use Meld's recommended ranking for best results function rankQuotesByMeldScore(quotes) { return quotes.sort((a, b) => { // Primary: Sort by rampScore (higher = better) if (a.rampScore !== b.rampScore) { return b.rampScore - a.rampScore; } // Secondary: Sort by destinationAmount (more crypto = better) return b.destinationAmount - a.destinationAmount; }); } ``` ### **Best Practice:** Always display the highest `rampScore` quote first to maximize transaction success rates and user satisfaction. 📊 **Learn More:** See [Ramp Intelligence](/docs/stablecoins/white-label-api-integration/whitelabel-api-guide/ramp-intelligence) 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: ```javascript theme={null} // Sample Code: Create widget session to get provider URL POST /crypto/session/widget ``` ### Request Body: ```json theme={null} { "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: ```json theme={null} { "id": "WePLapZetkn1hfeKFScf3T", "externalSessionId": "session_456", "externalCustomerId": "user_123", "customerId": "WXEX4DsAX7cp6Ch78oq2w3", "widgetUrl": "https://meldcrypto.com?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "serviceProviderWidgetUrl": "https://transak.com/?sessionId=asjknakjnjknwejksdnjkdsjkfnsd", "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." } ```
Note that the `widgetUrl` returns a Meld url, which iframes the onramp's url (if allowed) or does a full redirect to the onramp's url for the onramps that block iframing. The `serviceProviderWidgetUrl` directly returns the url of the onramp. You can choose to launch either one, based on your preference. We recommend using the `serviceProviderWidgetUrl` instead of the `widgetUrl` **Recommendation:** ### Provider UI Launch Implementation: #### Desktop Implementation: ```javascript theme={null} // Sample Code: Launch provider UI in popup window function launchProviderUI(sessionResponse, options = {}) { const popup = window.open( sessionResponse.serviceProviderWidgetUrl, '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: ```javascript theme={null} // Sample Code: Launch provider UI on mobile function launchMobileProviderUI(sessionResponse) { // For webviews if (isMobileApp()) { window.ReactNativeWebView?.postMessage( JSON.stringify({ type: 'OPEN_WEBVIEW', url: sessionResponse.serviceProviderWidgetUrl }) ); } else { // Mobile browser - full redirect window.location.href = sessionResponse.widgetUrl; } } ``` ![](https://files.readme.io/c36806bc626f1b6cdbc3d19d59c327e5857af4e9afefb645d7cbd00d871e2302-ezgif-358045d7123d01.gif) *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. You will receive webhooks from Meld every time a transaction is created or updated. To learn more about the various Meld webhook events and see payload examples, see [Webhook events](/docs/stablecoins/for-all-products/webhook-events). ### Webhook Integration: ```javascript theme={null} // 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: ```javascript theme={null} // 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.'; } ``` ### **Status Reference:** See [Transaction Statuses](/docs/stablecoins/for-all-products/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): ```javascript theme={null} 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): ```javascript theme={null} // Always fetch fresh: - Crypto quotes - Widget session creation - Transaction details - Transaction status ``` ### Implementation Example: ```javascript theme={null} 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: ```javascript theme={null} 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 * **[Sandbox testing credentials](/docs/stablecoins/sandbox-guide/test)** — test cards, wallets, and KYC triggers ### Advanced features * **[Webhook events](/docs/stablecoins/for-all-products/webhook-events)** — real-time updates * **[Dashboard data](/docs/stablecoins/additional-information/dashboard-data)** — track performance * **[Ramp Intelligence](/docs/stablecoins/white-label-api-integration/whitelabel-api-guide/ramp-intelligence)** — improve success rates ### Production deployment * **[Service-provider setup](/docs/stablecoins/for-all-products/service-provider-setup)** — configure additional onramps *** ## Support & resources * **[FAQ](/docs/stablecoins/faq)** — common questions answered *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.* # Ramp Intelligence Source: https://docs.meld.io/docs/stablecoins/white-label-api-integration/whitelabel-api-guide/ramp-intelligence # Ramp Intelligence Meld's intelligent quote ranking system maximizes conversion rates by guiding users toward the most suitable payment providers. Through advanced algorithms analyzing user history, provider performance, and KYC requirements, Meld helps developers achieve significantly higher success rates. ### **Proven Results** Meld's rampScore and lowKyc optimization features have enabled developers to achieve **69-94% end-to-end conversion rates** - a dramatic improvement over industry averages. ## Overview Every quote response includes intelligent ranking data to help you: * **Prioritize familiar providers** where users have succeeded before * **Identify low-friction options** requiring minimal verification * **Optimize for user success** based on historical performance * **Reduce abandonment rates** through smart provider selection ### **Scope**: These optimization features apply to buy transactions only and are included automatically in all quote responses. ## Understanding KYC Impact **Know Your Customer (KYC)** verification significantly impacts conversion rates. Each provider has different requirements: * **Minimal KYC**: Email, phone, basic details (high conversion) * **Standard KYC**: Document uploads, identity verification (medium conversion) * **Enhanced KYC**: Bank statements, proof of funds (low conversion) Meld's optimization ensures users are directed to providers where they've already completed verification, dramatically reducing friction. ## Key Optimization Parameters Every quote includes these conversion-critical fields: ### `rampScore` (0-100) Meld's proprietary recommendation engine that predicts conversion likelihood for each user-provider pair. Higher scores indicate greater success probability. Meld recognizes users by their wallet address, so this score works across the entire Meld ecosystem, even if your user used the same wallet address via a different wallet before! ### `lowKyc` (boolean) Indicates whether the transaction can complete without document uploads for new users. ### Calculation Factors Meld's algorithm processes multiple data points, updated several times daily: **User History (Primary Factor)** * Previous successful transactions with each provider * Transaction recency and frequency * Failure patterns and recovery behavior * Cross-wallet transaction history within Meld network **Provider Performance** * Historical success rates by geography and payment method * Average processing times and reliability metrics * Fee competitiveness and spread analysis * Payment method diversity and regional support **Market Dynamics** * Real-time provider availability and capacity * Regional regulatory compliance status * Seasonal performance variations ### Score Interpretation | Score Range | Meaning | Recommendation | | ----------- | ------------------- | ------------------------------------------------- | | 70-100 | **Excellent Match** | Multiple recent successes, prioritize prominently | | 30-69 | **Good Match** | Some success history, feature positively | | 0-29 | **New/Unknown** | No significant history, standard positioning | **Implementation Note**: Always include the user's `walletAddress` in quote requests to enable personalized scoring. ### Implementation Strategy **Primary Sorting**: Always rank quotes by `rampScore` (highest first) **Visual Emphasis**: Highlight providers with scores > 30 using: * "⭐ Recommended" badges * Primary button styling * "You've used this before" messaging **Code Example**: ```typescript theme={null} interface Quote { rampScore: number; serviceProvider: string; lowKyc: boolean | null; partnerFee: number | null; sourceAmount: number; destinationAmount: number; totalFee: number; // ... other fields } function sortQuotesByRecommendation(quotes: Quote[]): Quote[] { return quotes.sort((a, b) => b.rampScore - a.rampScore); } function getRecommendationBadge(score: number): string { if (score >= 70) return "🏆 Your Best Choice"; if (score >= 30) return "⭐ Recommended"; return ""; } ``` ### **Best Practice**: Never hide or de-emphasize high-scoring options, even if they have higher fees. User familiarity typically outweighs small price differences in conversion impact. ## Low KYC Optimization The `lowKyc` flag identifies providers where new users can complete transactions without document uploads, significantly reducing friction and improving conversion rates. ### What Low KYC Means **✅ Low KYC Requirements:** * Basic information: email, phone, name * Simple verification: SMS codes, email confirmation * Optional: SSN or tax ID (varies by country) * **No document uploads required** **❌ Standard KYC Requirements:** * Government ID photos (license, passport) * Proof of address documents * Bank statements or financial verification * Selfie verification ### Response Values | Value | Meaning | User Experience | | ------- | ---------------------------------------------------- | ----------------------------------- | | `true` | No documents needed for this amount | ⚡ Fast completion (2-5 minutes) | | `false` | Documents required | 📋 Standard process (10-30 minutes) | | `null` | Provider doesn't specify or amount exceeds threshold | 🤷 Unknown requirements | ### Implementation Guidelines **Badge Messaging**: * `lowKyc: true` → "⚡ No Documents Required" * `lowKyc: false` → Standard presentation * `lowKyc: null` → No special treatment **Code Example**: ```typescript theme={null} function getLowKycBadge(lowKyc: boolean | null): string { return lowKyc === true ? "⚡ No Documents Required" : ""; } function shouldHighlightForSpeed(quote: Quote): boolean { return quote.lowKyc === true; } ``` ### **Conversion Impact**: Low KYC options can improve completion rates by 40-60% for new users, making them valuable for customer acquisition. ## Implementation Example Here's a realistic quote response showing how optimization parameters work together: ```json theme={null} { "quotes": [ { "transactionType": "CRYPTO_PURCHASE", "sourceAmount": 200.00, "sourceAmountWithoutFees": 195.83, "destinationAmount": 0.04308219, "destinationCurrencyCode": "ETH", "exchangeRate": 4642.29, "paymentMethodType": "CREDIT_DEBIT_CARD", "serviceProvider": "UNLIMIT", "partnerFee": 2, "rampIntelligence": { "rampScore": 78.5, "lowKyc": null } }, { "transactionType": "CRYPTO_PURCHASE", "sourceAmount": 200.00, "sourceAmountWithoutFees": 193.09, "destinationAmount": 0.04176125, "destinationCurrencyCode": "ETH", "exchangeRate": 4789.13, "paymentMethodType": "CREDIT_DEBIT_CARD", "serviceProvider": "KRYPTONIM", "partnerFee": 2, "rampIntelligence": { "rampScore": 45.2, "lowKyc": true } }, { "transactionType": "CRYPTO_PURCHASE", "sourceAmount": 200.00, "sourceAmountWithoutFees": 190.07, "destinationAmount": 0.041154, "destinationCurrencyCode": "ETH", "exchangeRate": 4859.80, "paymentMethodType": "CREDIT_DEBIT_CARD", "serviceProvider": "STRIPE", "partnerFee": 2, "rampIntelligence": { "rampScore": 12.1, "lowKyc": false } } ] } ``` ### Analysis & UI Recommendations **1. UNLIMIT (Score: 78.5)** * 🏆 **Primary Recommendation**: Highest customer score indicates strong success history * **UI Treatment**: Featured position, "Your Best Choice" badge * **Conversion Expectation**: Very high (user familiarity) **2. KRYPTONIM (Score: 45.2)** * ⚡ **Speed Advantage**: `lowKyc: true` means fast completion for new users * **UI Treatment**: "No Documents Required" badge * **Conversion Expectation**: Good for new users, moderate for returning **3. STRIPE (Score: 12.1)** * **Standard Treatment**: Lowest score, no special advantages * **UI Treatment**: Regular presentation * **Conversion Expectation**: Lower (unknown user history) ### Optimal Implementation Strategy ```typescript theme={null} // Sort quotes optimally function optimizeQuoteDisplay(quotes: Quote[]): Quote[] { return quotes .sort((a, b) => b.rampScore - a.rampScore) .map(quote => ({ ...quote, recommendationBadge: getRecommendationBadge(quote.rampScore), speedBadge: getLowKycBadge(quote.lowKyc), isPrimary: quote.rampScore >= 70, isRecommended: quote.rampScore >= 30 })); } ``` ### **Key Principle**: Rank by `rampScore` first, then enhance with `lowKyc` badges. Never let low KYC override strong user history - familiarity typically wins over convenience. ## Summary Meld's conversion optimization combines user history intelligence with friction analysis to maximize transaction success rates. By implementing proper quote ranking and visual emphasis, developers can achieve industry-leading conversion performance. **Implementation Checklist:** * ✅ Always include `walletAddress` in quote requests * ✅ Sort quotes by `rampScore` (highest first) * ✅ Add visual badges for scores > 30 * ✅ Highlight `lowKyc: true` options with speed messaging * ✅ Never override high rampScore with other factors * ✅ Monitor conversion metrics and iterate # Supporting Multiple Downstream Applications Source: https://docs.meld.io/docs/stablecoins/white-label-api-integration/whitelabel-api-guide/supporting-multiple-downstream-applications This page is for platforms and aggregators whose Meld integration serves multiple downstream partners, applications, or developers. It explains the two architectural options Meld supports and how each affects fees, onramp coverage, and reporting. If your use case involves supporting multiple downstream partners / applications / developers, there are 2 ways to handle that. 1. Having 1 Meld account 2. Having multiple Meld accounts In both approaches, you should pass in an `externalCustomerId` that corresponds to the end user making the transaction. This is just a reference id you provide, and can have any `string` value. ## Option 1: A single Meld account Can your downstream applications use the same set of onramps with the same fee set by you? If so, then the easiest way to support this is to use a single Meld account. * Meld (and you) will still need to know which application is responsible for each transaction. When you call `POST /crypto/session/widget`, use the `externalSubaccountCustomerId` field to pass in an identifier corresponding to the application that made the call. The `externalSubaccountCustomerId` you pass in will be tied to the transaction the user ends up making. This is useful for compliance, metrics, and revenue calculation. * Several providers require whitelisting the domains that users will get redirected to after completing a transaction. If you will have many applications, it may not make sense to whitelist each new domain with the provider. In this case, it would make sense for you to have an intermediary redirect url with a domain that you whitelist, that then automatically redirects to the actual target url. An example would be [https://yourcompanyurl.com/?redirectUrl=https://yourapplicationurl.com](https://yourcompanyurl.com/?redirectUrl=https://yourapplicationurl.com). Here you would only have to whitelist [https://yourcompanyurl.com](https://yourcompanyurl.com), but you would build an automatic redirect so that the above redirect sends the user to [https://yourapplicationurl.com](https://yourapplicationurl.com). ## Option 2: Multiple Meld accounts If your downstream applications need different sets of service provider and want to charge different partner fees, you may need multiple Meld accounts. Meld will need to create each account for you. Contact Meld about this. If you use 1 Meld account per downstream application, you do not need to pass in an `externalSubaccountCustomerId`. The `externalSubaccountCustomerId` will be returned in the transaction webhooks and you can also query `GET /payments/transactions` with this value. This allows you to see all transactions made by a particular downstream application. ## Implementation Instructions Steps to using `externalSubaccountCustomerId`: First, create a customer with `POST /accounts/customers`, using the string value as the `externalId`. A sub-account customer must be created before it can be referenced on a widget session. Sample request: ```json theme={null} { "externalId": "business1", "type": "BUSINESS" } ``` When you call `POST /crypto/session/widget`, pass in the `externalSubaccountCustomerId` corresponding to the downstream app in the body of the call. Sample request: ```json theme={null} { "sessionData": { "walletAddress": "2N3oefVeg6stiTb5Kh3ozCSkaqmx91FDbsm", "countryCode": "US", "sourceCurrencyCode": "USD", "sourceAmount": "100", "destinationCurrencyCode": "USDC", "serviceProvider": "TRANSAK" }, "externalCustomerId": "user1", "externalSubaccountCustomerId": "business1" } ``` Track which transaction was made by which `externalSubaccountCustomerId` by reading that value in the webhooks, the `GET /payments/transactions` response, and the dashboard CSV export. # Customization Source: https://docs.meld.io/docs/stablecoins/white-label-api-integration/whitelabel-api-guide/white-label-customization # Locking fields This page is for developers who want their users to land in the onramp's widget with specific fields pre-filled and non-editable. Some service providers support locking the token and wallet address fields within their widgets. To do this, pass the `lockFields` parameter to the `POST /crypto/session/widget` endpoint with one or both of `destinationCurrencyCode`/`sourceCurrencyCode` (depending on buy vs sell flow) and `walletAddress`. The table below shows which service providers support locking which fields.
Locking cryptocurrency
Locking wallet address
**Banxa**
Not supported
Not supported
**BTC Direct**
Supported
Supported
**Onramp Money**
Not supported
Always locked when passed in
**Transak**
Supported
Supported
**Binance Connect**
Always locked
Not supported
**TransFi**
Always locked when passed in
Always locked when passed in
**Unlimit**
Supported
Supported
**Paybis**
Not supported
Not supported
**Fonbnk**
Not supported
Supported
**Koywe**
Always locked when passed in
Always locked when passed in
**Robinhood**
Always locked when passed in
Always locked when passed in
**Coinbase**
Not supported
Not supported
**Blockchain**
Always locked when passed in
Always locked when passed in
**Kryptonim**
Supported
Supported
**Topper**
Supported
Supported
**Mercuryo**
Supported
Not Supported
**Swapped**
Not Supported
Not Supported
**Guardarian**
Not Supported
Supported
Here's a sample buy request with both the token and wallet address fields locked: ```json theme={null} { "sessionData": { "walletAddress": "2N3oefVeg6stiTb5Kh3ozCSkaqmx91FDbsm", "countryCode": "US", "sourceCurrencyCode": "USD", "sourceAmount": "100", "destinationCurrencyCode": "USDC", "serviceProvider": "TRANSAK", "lockFields": ["destinationCurrencyCode","walletAddress"] }, "externalCustomerId": "test1", "externalSessionId": "test1" } ```
# Quickstart Source: https://docs.meld.io/docs/stablecoins/white-label-api-integration/whitelabel-quickstart This is the 30 minute quickstart guide for executing the white-label API flow for the first time. # Integration Quickstart This quickstart is for developers integrating Meld's White-Label API for the first time. You'll go from zero credentials to a successfully completed sandbox transaction in about 30 minutes. **Time required:** 30 minutes **Difficulty:** API knowledge required ## Before you begin * A Meld dashboard invitation (check your email) * Ability to make authenticated REST calls (curl, Postman, or your language of choice) * A publicly reachable webhook URL (optional; you can poll the API instead) **Sandbox base URL:** `https://api-sb.meld.io`. Production credentials and URLs are separate — sandbox credentials will not work in production and vice versa. ## Step 1: Get Your API Key Your API key is required for all Meld API calls. ### Process: 1. **Check your email** for the Meld dashboard invitation 2. **Click the invitation link** and log in 3. In the dashboard [https://dashboard.meld.io](https://dashboard.meld.io) , **navigate to Developer > API Keys** 4. **Click "Reveal Key"** 5. **Copy and save** your API key securely **Important:** Always add `BASIC` before your API key in all requests **Example:** `BASIC W9kZTT7332okCEc1A9aqAq:3sYKoXQv6oHVHSts7G2agw9vTCXz` *** ## Step 2: Set Up Webhooks Webhooks notify your server when transactions are created and updated. This step can be completed later. ### Requirements: * **A publicly accessible URL** (localhost will not work) * For testing, consider using [ngrok](https://ngrok.com/), [webhook.site](https://webhook.site/) or similar services ### Setup Process: 1. Navigate to **Developer > Webhooks** in the dashboard 2. Click **"Add Endpoint"** 3. Enter your **webhook URL** (must start with http/https) 4. Give your webhook a **descriptive name** 5. Select **"Subscribe to all events"** 6. Click **"Add endpoint"** ### **Note:** You will receive notifications to this URL for all transaction creations and updates. *** ## Step 3: Test Your API Connection Verify your API key by requesting a price quote. ### API Call Details: * **Endpoint:** `POST /payments/crypto/quote` * **Authorization:** `BASIC [your-api-key]` ### Request Body: ```json theme={null} { "countryCode": "US", "sourceCurrencyCode": "USD", "destinationCurrencyCode": "BTC", "paymentMethodType": "CREDIT_DEBIT_CARD", "sourceAmount": 100 } ``` ### Testing Notes: * **Use the interactive docs** at the endpoint URL ### Expected Response: ✅ **200 status code** with quote details Here is an example of a single quote. You can expect multiple quotes, 1 per onramp, in the response. ```json theme={null} { "quotes": [ { "transactionType": "CRYPTO_PURCHASE", "sourceAmount": 100.00, "sourceAmountWithoutFees": 95.82, "fiatAmountWithoutFees": 95.82, "destinationAmountWithoutFees": null, "sourceCurrencyCode": "USD", "countryCode": "US", "totalFee": 4.18, "networkFee": 0.28, "transactionFee": 2.9, "partnerFee": 1, "destinationAmount": 0.00105423, "destinationCurrencyCode": "BTC", "exchangeRate": 94856.0, "paymentMethodType": "CREDIT_DEBIT_CARD", "serviceProvider": "TOPPER", "rampIntelligence": { "rampScore": 21.66, "lowKyc": false } } ] } ``` *** ## Step 4: Create a Test Transaction Create a test transaction without using real money. ### API Call Details: * **Endpoint:** `POST /crypto/session/widget` * **Authorization:** `BASIC [your-api-key]` ### Request Body: ```json theme={null} { "countryCode": "US", "sourceCurrencyCode": "USD", "destinationCurrencyCode": "BTC", "paymentMethodType": "CREDIT_DEBIT_CARD", "sourceAmount": 100, "serviceProvider": "TRANSAK", "walletAddress": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh" } ``` ### **Note:** Replace `walletAddress` with your own or use the test address provided. ### Expected Response: ✅ **200 status code** with onramp url details ```json theme={null} { "id": "WePjVaT4iBHPpqW49F419x", "externalSessionId": "testSession", "externalCustomerId": "testCustomer", "customerId": "WePZCYZjAK97cJWokfH3Jc", "widgetUrl": "https://meldcrypto.com?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtZWxkLmlvIiwiaWF0IjoxNzY1MjM2MjExLCJzdWIiOiJjcnlwdG8iLCJleHAiOjE3NjUyMzgwMTEsImFjY291bnRJZCI6IldRNVJ5aGRGekU0NXFqc29tZHpRMXUiLCJzZXNzaW9uSWQiOiJXZVBqVmFUNGlCSFBwcVc0OUY0MTlhIn0.TH6P9KVKN4GNu4CNsDAN9uicjMBashgA9QY7jiMiDEF", "serviceProviderWidgetUrl": "https://topper.com?apiKey=1234&sessionId=eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJvdHQiOiIxZDFjOTM1ODY0YTQ0NGM0YTJiMmQ5ODJkYjc0Njg1NCIsImlhdCI6MTc2NTIzNjIxMiwiZXhwIjoxNzY1MjM2NTEyfQ.6ZnmR5FAxzr9bG3n_I54L1EmHYljfhPJNeqp97WZPI7GUm9VCksbKv_rWK4KiB6YkAAJR6_C1Xsnn-fliUrABC", "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtZWxkLmlvIiwiaWF0IjoxNzY1MjM2MjExLCJzdWIiOiJjcnlwdG8iLCJleHAiOjE3NjUyMzgwMTEsImFjY291bnRJZCI6IldRNVJ5aGRGekU0NXFqc29tZHpRMXUiLCJzZXNzaW9uSWQiOiJXZVBqVmFUNGlCSFBwcVc0OUY0MTlhIn0.TH6P9KVKN4GNu4CNsDAN9uicjMBashgA9QY7jiMiDEF" } ``` ### Complete the Test Flow: 1. **Copy the`widgetUrl`** from the API response 2. **Open the URL** in your browser 3. **For KYC testing:** * **SSN:** Use any fake number * **ID Upload:** Any image file works 4. **For payment testing:** * **Card Number:** `4111 1111 1111 1111` * **Expiry:** Any future date * **CVV:** Any 3 digits 📚 **Full test data reference:** [Sandbox testing credentials](/docs/stablecoins/sandbox-guide/test) *** ## Step 5: Verify Your Transaction ### Fetch Transaction Details after Receiving Webhooks 1. **Check your webhook endpoint** for a webhook that the transaction has been created. ### Expected Webhook Response ```json theme={null} { "eventType": "TRANSACTION_CRYPTO_PENDING", "eventId": "AAsuLXHXD3mS1cjNBuHHzv", "timestamp": "2022-02-24T16:36:41.717262Z", "accountId": "WQ5RyhdFzE45qjsomdzQ1u", "profileId": "W9ka8vLE4ufBkSg3BEciZb", "version": "2025-03-01", "payload": { "requestId": "f07f1accb7404aec9bd9a5d64975eed1", "accountId": "W2aRZnYGPwhBWB94iFsZus", "paymentTransactionId": "WePZCYJW7cdXR7SxUMp8mE", "customerId": "WePZCYZjAK97cJWokfH3Jc", "externalCustomerId": "testCustomer", "externalSessionId": "testSession", "paymentTransactionStatus": "PENDING", "transactionType": "CRYPTO_PURCHASE", "sessionId": "WePjVaT4iBHPpqW49F419x" } } ``` 2. **Extract the`paymentTransactionId`** from the webhook payload 3. **Call** `GET /payments/transactions/{transactionId}` ### Expected Response: ✅ **200 status code** with transaction details ```json theme={null} { "transaction": { "id": "WePZCYJW7cdXR7SxUMp8mE", "parentPaymentTransactionId": null, "accountId": "WQ5RyhdFzE45qjsomdzQ1u", "isPassthrough": false, "passthroughReference": null, "isImported": false, "customer": { "id": "WePZCYZjAK97cJWokfH3Cc", "accountId": "WQ5RyhdFzE45qjsomdzQ1u", "externalId": "YC100" }, "transactionType": "CRYPTO_PURCHASE", "status": "SETTLED", "sourceAmount": 100.00, "sourceCurrencyCode": "USD", "destinationAmount": 0.00105423, "destinationCurrencyCode": "BTC", "paymentMethodType": "CREDIT_DEBIT_CARD", "serviceProvider": "TOPPER", "serviceTransactionId": "1f0d470c-f5c2-6fc2-a54d-b6cdd6b6f014", "orderId": null, "description": null, "externalReferenceId": "testSession", "serviceProviderDetails": { **raw details from the onramp** }, "multiFactorAuthorizationStatus": null, "createdAt": "2025-12-08T20:03:07.173223Z", "updatedAt": "2025-12-08T20:08:08.877106Z", "countryCode": "US", "sessionId": "WePZCbr96gAUxgu7Auvm4q", "externalSessionId": "testSession", "paymentDetails": null, "externalCustomerId": "testCustomer", "fiatAmountInUsd": 100.00, "sessionClientTags": null, "serviceProviderTransactionUrl": null, "serviceProviderCreatedAt": "2025-12-08T20:02:22Z", "cryptoDetails": { "sourceWalletAddress": null, "destinationWalletAddress": "0xd72cc3468979360e31bc83b84f0887deccfd81d5", "sessionWalletAddress": "0xd72cc3468979360e31bc83b84f0887deccfd81d5", "totalFee": 4.18, "networkFee": 0.28, "transactionFee": 2.9, "partnerFee": 1, "totalFeeInUsd": 4.18, "networkFeeInUsd": 0.28, "transactionFeeInUsd": 2.9, "partnerFeeInUsd": 1, "blockchainTransactionId": "0x553d295955a978ed3e9fc1717b5bcb903c69577e49c8ad255abece945ffa9ba0", "institution": null, "chainId": "1" } } } ``` ### Dashboard Verification: 1. Navigate to the **Transactions tab** 2. If your transaction isn't visible: * Click the **Status dropdown** * Select **"Select All"** * Your transaction should appear ✅ **White-Label API Integration Complete!** You can now build custom crypto experiences. ➡️ **[Build Custom UI Guide](/docs/stablecoins/white-label-api-integration/whitelabel-api-guide)** — complete implementation guide ## Next steps If you're ready to begin your integration, follow the end-to-end [White-Label API Guide](/docs/stablecoins/white-label-api-integration/whitelabel-api-guide). *** ## Troubleshooting ### Common Issues and Solutions: #### 🚫 401 Unauthorized * Ensure `BASIC` is added before your API key * Check for extra spaces or incorrect formatting #### 🚫 Webhook Not Received * Verify URL is publicly accessible * Check firewall settings * Ensure webhook endpoint returns 200 status #### 🚫 Transaction Not Visible * Change status filter to "Select All" in dashboard * Wait 30 seconds and refresh * Check if using correct environment (sandbox vs production) # Build with AI Source: https://docs.meld.io/docs/welcome/build-with-ai Meld's Stablecoins & Digital Assets docs are AI-friendly. Plug them into Claude, Cursor, ChatGPT, or any LLM tool to ship your crypto integration faster. The Meld Stablecoins documentation is published in an agent-ready format so coding assistants and AI agents can read, search, and reason over it directly — without copy-pasting pages or relying on stale training data. This page covers four ways to bring Meld docs into your AI workflow: Connect Claude, Cursor, and other tools to a live, searchable index of the docs. Drop-in context files that summarize or inline the entire docs site. Get any page as clean Markdown by appending `.md` to its URL. Machine-readable capability file for agents that act on Meld's APIs. All endpoints below serve the full Meld documentation, including the Stablecoins & Digital Assets, Meld API, and API reference sections. ## MCP server The Meld docs are exposed as a hosted [Model Context Protocol](https://modelcontextprotocol.io) server. Once connected, your AI tool can search the docs and read full pages on demand instead of guessing. **Server URL** ``` https://docs.meld.io/mcp ``` **Tools the server exposes** * `search_meld` — semantic search across every page; returns titles, snippets, and links. * `query_docs_filesystem_meld` — shell-style read access (`ls`, `tree`, `cat`, `head`, `rg`) over the docs as a virtual filesystem, including the OpenAPI specs. ### Add it to your tool Run from your project: ```bash theme={null} claude mcp add --transport http meld-docs https://docs.meld.io/mcp ``` Then in a session, ask: *"Use the meld-docs MCP to find the White-Label API quickstart and summarize the request payload."* Add to `claude_desktop_config.json`: ```json theme={null} { "mcpServers": { "meld-docs": { "type": "http", "url": "https://docs.meld.io/mcp" } } } ``` Restart Claude Desktop. The `meld-docs` tools will appear in the tool picker. Add to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` in your project: ```json theme={null} { "mcpServers": { "meld-docs": { "url": "https://docs.meld.io/mcp" } } } ``` Add to your workspace `.vscode/mcp.json`: ```json theme={null} { "servers": { "meld-docs": { "type": "http", "url": "https://docs.meld.io/mcp" } } } ``` Open a chat and attach the Meld docs MCP under **Connectors → Add connector** (available on Plus, Pro, Business, and Enterprise). Use: ``` https://docs.meld.io/mcp ``` No auth required. A discovery endpoint is published at `https://docs.meld.io/.well-known/mcp` so MCP-aware tools can find the server automatically. ## llms.txt The docs are also published as flat text files designed for direct LLM consumption — useful when you want to drop the whole site into a context window, a RAG pipeline, or a fine-tuning corpus. | File | Use it when | | -------------------------------------------------------------------------- | ------------------------------------------------------------ | | [`https://docs.meld.io/llms.txt`](https://docs.meld.io/llms.txt) | You want a structured index of every page with descriptions. | | [`https://docs.meld.io/llms-full.txt`](https://docs.meld.io/llms-full.txt) | You want the entire docs site inlined as one Markdown file. | **Example — load the full docs into a Claude API request:** ```python theme={null} import anthropic, urllib.request docs = urllib.request.urlopen("https://docs.meld.io/llms-full.txt").read().decode() client = anthropic.Anthropic() msg = client.messages.create( model="claude-opus-4-8", max_tokens=1024, system=[ {"type": "text", "text": "You are a Meld integration assistant."}, {"type": "text", "text": docs, "cache_control": {"type": "ephemeral"}}, ], messages=[{"role": "user", "content": "How do I create a crypto quote for a USD → USDC buy?"}], ) print(next((b.text for b in msg.content if b.type == "text"), "")) ``` Use prompt caching (as shown above) when you call repeatedly — `llms-full.txt` is large, and caching cuts cost and latency dramatically. ## Markdown export Any page on `docs.meld.io` can be fetched as raw Markdown — no HTML, no nav chrome — by appending `.md` to its URL. **Examples** ``` https://docs.meld.io/docs/stablecoins/crypto-overview_/quickstart.md https://docs.meld.io/docs/stablecoins/white-label-api-integration/whitelabel-quickstart.md https://docs.meld.io/api-reference/crypto/retail-ramp/crypto-quote-get.md ``` You can also: * Send `Accept: text/markdown` on any docs request to get the Markdown variant. * Press Cmd+C (Ctrl+C on Windows) on any page to copy it as Markdown. * Use the **Copy as Markdown** / **View as Markdown** options in the page's contextual menu. API reference pages include the full OpenAPI operation in the Markdown export, which is what most coding agents need to generate a correct request. ## skill.md For agents that *act* on Meld (not just read about it), the docs publish an [agentskills.io](https://agentskills.io/specification)–compatible capability file: ``` https://docs.meld.io/skill.md ``` It describes what an agent can accomplish with the Meld API, the inputs each capability needs, and the constraints that apply. MCP-connected agents discover it automatically as a resource on the server above; you can also add it manually: ```bash theme={null} npx skills add https://docs.meld.io ``` ## Recommended setup for crypto integrations Add `https://docs.meld.io/mcp` to your editor or chat tool. This is the single best upgrade — your assistant can now look up exact request shapes, status codes, and webhook payloads on demand. Bookmark these Markdown URLs and feed them to your agent at the start of a session: * `https://docs.meld.io/docs/stablecoins/crypto-overview_/index.md` — product overview and which integration to pick * `https://docs.meld.io/docs/stablecoins/crypto-overview_/quickstart.md` — first API call * `https://docs.meld.io/docs/stablecoins/for-all-products/webhook-events.md` — webhook event catalog When you want a clean-slate model (no MCP) to do something self-contained — a one-off script, a code review, a migration — paste `llms-full.txt` into context with caching enabled. The MCP filesystem includes the OpenAPI JSON for every product. Ask your agent to validate generated requests against `/openapi/crypto-20260203.json` before you ship. ## Questions or feedback Found a docs gap your agent stumbled on, or want a new endpoint covered? Reach out to your Meld contact — agent-readability is a first-class goal for this section of the docs. # Overview Source: https://docs.meld.io/docs/welcome/overview Welcome to Meld This page introduces what Meld does and the use cases it supports. It is for developers and product teams evaluating Meld, and by the end you should know whether Meld's Digital Assets or Bank Linking products fit your needs and where to go next. # Meld empowers you to 1. Buy, sell, or transfer digital assets, including stablecoins globally. 2. Have your users connect to their banks and pull their banking information.
## Common Digital Assets Use Cases 1. You are a wallet or fintech app that wants your users to be able to buy or sell crypto anywhere. 2. You are an employer who needs to pay out employees / contractors across the globe. 3. You are a marketplace that needs to collect payment globally using digital assets. 4. You currently support one or a few onramps but need many more for global coverage and better conversion.
## Common Bank Linking Use Cases 1. You offer your customers financial planning and need their transaction history and current balance. 2. You need your customers to verify their identity by connecting to their bank. 3. You need your customer's iban information (or account number / routing number) to facilitate global bank transfers.
## Terminology | Term | Meaning | | :------------- | :--------------- | | Onramps | Network Partners | | Digital Assets | Crypto | | Onramping | Buying crypto | | Offramp | Selling crypto | These terms are used consistently across all Meld documentation. If you see "onramp" or "network partner" in a guide, they refer to the same thing.
Next: View [Meld's products](/docs/welcome/see-all-products) to see which ones are right for you.
# Reference Source: https://docs.meld.io/docs/welcome/reference This page is a quick reference for environments, authentication, status codes, errors, security, and date formats. It is for developers who have already started integrating Meld and need to look up exact values while coding. If you have not started the integration process, you should skip this page and start with the [Overview](/docs/welcome/overview). # Meld Environments Meld offers both production and sandbox environments. The below table contains the URL for each environment: | Environment | API Base URL | | :---------- | :----------------------------------------------- | | Sandbox | [https://api-sb.meld.io](https://api-sb.meld.io) | | Production | [https://api.meld.io](https://api.meld.io) | Sandbox and Production use separate API keys and separate data. A key issued for one environment will not work against the other. Test transactions in Sandbox do not move real funds. To obtain your API key for either Production or Sandbox environments, work with your Meld contact. Your API Key is a secret, treat it as such. Do not share it or send it through a front end call. # Authentication Meld uses API keys to authenticate requests. These keys carry many privileges such as authorizing payments and accessing financial accounts data. It is important to keep them private and secure during both storage and transmission. Authentication is handled via HTTP headers, using the `Authorization` header. Example request: ```bash theme={null} curl --location --request \ GET 'https://api.meld.io/' \ --header 'Authorization: BASIC {{Your API Key}}' ``` Example successful response: ```json theme={null} { "id": "abc123", "status": "OK" } ``` "BASIC" Authorization When submitting your API key for authentication, you must specify "BASIC " before the key value pair. Note the trailing space between `BASIC` and your key. To help keep your API keys secure, follow these best practices: * Do not embed API keys directly in code, because they can be accidentally exposed to the public. Instead, store them in environment variables or in files outside of your application's source tree. * Do not store your API keys in files inside your application's source tree. If you must store API keys in files, keep the files outside your source tree to ensure your keys do not end up in your source code control system, especially if you use a public one such as GitHub. * Delete unneeded API keys to minimize exposure to attacks. * Review your code before publicly releasing it. Ensure that it does not contain API keys or any other private information before you make it publicly available. # API Status Codes The following table lists the status code you will receive from our APIs.
Status code Description
200 Successful, with response data as defined by the `Content-Type` header
201 Successful, with response data as defined by the `Content-Type` header
400 Bad Request
401 Unauthorized
403 Forbidden
404 No Resource Found
422 Input Validation Failed
425 Failure. TOO\_EARLY You might see this error when the same idempotent key is used twice and the first transaction is still being processed.
429 Too Many Requests
500 Unexpected Issue

# API Error Schema Any status code of `400` or higher returns an error payload. Inspect the `code` and `errors` fields to determine how to handle the failure, and surface `requestId` when contacting Meld support so we can trace the exact call. All errors are returned in the form of JSON and contain the following data: | Key | Description | | :---------- | :-------------------------------------------------------------------------------------------------------------------- | | `code` | A categorization of the error | | `message` | A developer-friendly representation of the error code. This may change over time and is not safe for programmatic use | | `errors` | A user-friendly representation of the error code. This may change over time and is not safe for programmatic use. | | `requestId` | The request Id | | `timestamp` | The date and time when the request was made | Below is a sample error response: ```json theme={null} { "code": "BAD_REQUEST", "message": "Bad request", "errors": [ "[amount] Must be a decimal value greater than zero" ], "requestId": "eb6aaa76bd7103cf6c5b090610c31913", "timestamp": "2022-01-19T20:32:30.784928Z" } ``` # Security 1. CORS -- Meld does not need to whitelist any of our customer's URL or IPs for them to call our public Production & Sandbox APIs. You can use whichever URL you desire, as we authenticate via your Meld API Key. 2. For security reasons, Meld recommends using your backend server to make the calls to Meld's API. If you make these calls from your frontend instead, it may not work and you may get back a CORS error. This is because making calls to Meld APIs requires that you pass in an `Authorization` header with the API Key we issued you. It is insecure to keep this API Key hardcoded in your mobile app or web app. 3. All our customers need to treat the Meld API Key they've been issued like any other password. It is an extremely sensitive credential that needs to be protected at all cost. The security measures you need to ensure are: a) strict controls to the backend server (as it has access to your Meld API Key), b) a way to authenticate your FE/app to your backend server, c) reject/ignore all other calls to your backend server. Never call Meld APIs directly from a browser, mobile app, or any other untrusted client. Calls must originate from your backend server so your API key is never exposed. # Dates All Meld dates and timestamps returned via Meld's API are in UTC time and formatted using ISO 8601 (for example, `2022-01-19T20:32:30.784928Z`). # See All Products Source: https://docs.meld.io/docs/welcome/see-all-products List of Meld Products This page lists every product Meld offers so you can pick the right integration path before you start building. It is for developers comparing flows, and by the end you should know which product (or combination) matches your use case and where its quickstart lives. You may choose to integrate more than one product. It is important to understand the distinction between the products before proceeding. 1. [Digital Assets: White Label API Flow](/docs/stablecoins/white-label-api-integration/index) 1. Enables your users to buy, sell, and transfer crypto using onramps. Users KYC and complete the transaction within the onramp's UI. Meld powers the flow and offers recommendation of which onramp is best for the user. You can initiate this flow either from Meld's UI (which you can embed in your app / site) or build your own UI on top of Meld's APIs. 2. Use this flow if you are a wallet or fintech app that wants your users to be able to buy or sell any digital asset, and **you want to build your own UI**. Examples of customers using this flow are Phantom Wallet, Uniswap Wallet, and Metamask Wallet. 3. Start building with the [White Label API flow](/docs/stablecoins/white-label-api-integration/whitelabel-quickstart). 2. [Digital Assets: Meld Checkout Flow](/docs/stablecoins/meld-checkout-integration/index) 1. Enables your users to buy, sell, and transfer crypto using onramps. 2. Receive your unique Meld url that you can embed / redirect to in your app. The entire flow will happen in the Meld UI. Setup happens in minutes. 3. Use this flow if you are a wallet or fintech app that wants your users to be able to buy or sell any digital asset, and **you want to use Meld's UI**. 4. Start building with the [Meld Checkout Ramps flow](/docs/stablecoins/meld-checkout-integration/meld-checkout-quickstart). 3. [Digital Assets: Virtual Account Flow](/docs/stablecoins/virtual-account-integration/index) 1. Enables you to have a white-label API driven flow to complete buying and selling crypto. The onramp will create a virtual bank account for you to send crypto to (for buying) and receive payment from (for selling). 2. Use this flow if **you want onramps to create a virtual bank account for you and each of your users**. This unlocks being able to purchase or sell large amounts of digital assets, especially stablecoin, globally. 3. Start building with the [Virtual Account flow](/docs/stablecoins/virtual-account-integration/virtual-account-quickstart). 4. [Bank Linking](/docs/bank-linking/bank-linking-quickstart/index) 1. Allows you to have your users connect their bank account and then pull any relevant information you need, such as transaction history, balance, and more. 2. Use this flow if **you need information about your customers' bank accounts**. 3. Start building with the [Bank Linking Quickstart](/docs/bank-linking/bank-linking-quickstart/index). Do not use the Bank Linking product if your business is digital asset related. Use one of the Digital Assets flows above instead. Not sure which flow to pick? Start with the [Overview](/docs/welcome/overview) to map your use case to a product, then come back here for the quickstart link.
# Create processor token Source: https://docs.meld.io/api-reference/bank-linking/accounts/bank-linking-accounts-create-processor-token /openapi/banklinking-20231219.json post /bank-linking/accounts/{financialAccountId}/processors/create-token The token is used by the processor to access the information on this account via a service provider. Depending on the service provider and/or processor, this token represents whatever token/code/key is needed to provide access. # Get a financial account Source: https://docs.meld.io/api-reference/bank-linking/accounts/bank-linking-accounts-get /openapi/banklinking-20231219.json get /bank-linking/accounts/{financialAccountId} Use this endpoint to retrieve account information of a specific financial account. # Search financial accounts Source: https://docs.meld.io/api-reference/bank-linking/accounts/bank-linking-accounts-search /openapi/banklinking-20231219.json get /bank-linking/accounts Use this endpoint to filter financial accounts by various parameters. # Create a connection Source: https://docs.meld.io/api-reference/bank-linking/connect/bank-linking-connect-create /openapi/banklinking-20231219.json post /bank-linking/connect/start Use this endpoint to obtain a connect token, which can be used to initialize the Meld widget and start a bank linking connection. # Repair a connection Source: https://docs.meld.io/api-reference/bank-linking/connect/bank-linking-connect-repair /openapi/banklinking-20231219.json post /bank-linking/connect/repair A Service Provider's connection to the institution may sometimes degrade or new financial account(s) may be discovered for the connection. In order to fix the connection or add these newly discovered account(s), the customer must reconnect. This endpoint generates a new connect token for the connection to enable this reconnection process. This connect token can be used to invoke a new bank linking flow where the user can enter the service provider's UI to fix the connection # Update a connection Source: https://docs.meld.io/api-reference/bank-linking/connections/bank-linking-connection-update /openapi/banklinking-20231219.json post /bank-linking/connections/{connectionId}/update Update a connection and its products. Adds new products retroactively to an existing connection and triggers a refresh with this updated set of products. The products provided must not already be present on the connection and be supported by the underlying service provider and institution in order to be successful. # Delete an institution connection Source: https://docs.meld.io/api-reference/bank-linking/connections/bank-linking-connections-delete /openapi/banklinking-20231219.json delete /bank-linking/connections/{connectionId} Use this endpoint to delete a connection and all of its financial accounts. This will also delete the connection with the service provider used to make the connection, thus stopping any updates for the connection and its financial accounts. Listen for the associated [BANK_LINKING_CONNECTION_DELETED](https://docs.meld.io/docs/webhook-events-bank-linking#bank_linking_connection_deleted) webhook for acknowledgement that the connection was deleted. *Note:* Financial accounts may belong to multiple connections. This can occur if a customer connects to their institution multiple times in separate sessions, either through the same or a different service provider. If a connection is deleted but its financial accounts still belong to another active connection, then they won't be deleted yet. Not until all the connections that a financial account is associated with are deleted, will the financial account then be deleted. *Note:* In some cases, connections will be deleted before ever calling this endpoint. The aforementioned webhook will notify when this occurs, and calling this endpoint for the connection will no longer be necessary. This can occur if a customer stops granting access to the service provider directly through their institution. *Note:* Duplicate provider connections occur when the same customer connects to the same institution using the same login credentials through the *same* service provider. In such cases, only the most recently refreshed of these duplicates is actively maintained and the rest are considered inactive. When deleting duplicate connections, all of the inactive duplicates must be deleted prior to deleting the main duplicate. If an attempt is made to delete the active duplicate prior to the inactive duplicates being deleted, then the next most recently aggregated duplicate will become the active one. Only once all duplicates are deleted will this sever the connection with the service provider and cease billing. Duplicate connections made through *different* service providers do not have this restriction, however. # Get an institution connection Source: https://docs.meld.io/api-reference/bank-linking/connections/bank-linking-connections-get /openapi/banklinking-20231219.json get /bank-linking/connections/{connectionId} Retrieve details of a specific institution connection. # Import existing connections with a service provider Source: https://docs.meld.io/api-reference/bank-linking/connections/bank-linking-connections-import /openapi/banklinking-20231219.json post /bank-linking/connections/import Importing is done asynchronously. Webhooks will be sent for each connection as they are imported, the same as if the connection was added via the connect widget. # Refresh accounts belonging to a connection Source: https://docs.meld.io/api-reference/bank-linking/connections/bank-linking-connections-refresh /openapi/banklinking-20231219.json post /bank-linking/connections/{connectionId}/refresh Trigger a refresh of all financial accounts for a given institution connection with the latest information from the service provider. This is a premium service for most service providers and should be used sparingly for requesting up-to-date data as of the time of the call to this endpoint. Transaction and balance data is updated daily by service providers, and Meld's webhooks will notify of these updates so that product data does not become stale. *Note:* By default, historical transactions are loaded upon initial connection for all providers. This is a premium service for Finicity and MX, so if this is not desired please reach out to Meld support to disable this feature. After doing so, then historical transactions will only be loaded after the first time a refresh is requested for a Finicity/MX connection that has transactions as an available product. *Note:* Duplicate provider connections occur when the same customer connects to the same institution using the same login credentials through the *same* service provider. In such cases, only the most recently refreshed of these duplicates is actively maintained and the rest are considered inactive. Choosing to refresh an inactive duplicate will cause it to become the new active duplicate and it will assume the same connection status as the previous active duplicate. The previous active duplicate will now be considered inactive. # Search institution connections Source: https://docs.meld.io/api-reference/bank-linking/connections/bank-linking-connections-search /openapi/banklinking-20231219.json get /bank-linking/connections Retrieve institution connections filtered by various parameters. # Get institution Source: https://docs.meld.io/api-reference/bank-linking/institutions/bank-linking-institutions-get /openapi/banklinking-20231219.json get /institutions/{institutionId} This endpoint allows you to get an institution by id, as well as the service provider metadata such as name, URL, and logos for each service provider configured to your account that support this institution # Search institutions Source: https://docs.meld.io/api-reference/bank-linking/institutions/bank-linking-institutions-search /openapi/banklinking-20231219.json get /institutions Allows you to search across the list of Institutions supported by your service providers. This endpoint is only available in production. # Get a financial account investment holding Source: https://docs.meld.io/api-reference/bank-linking/investments/bank-linking-investment-holdings-get /openapi/banklinking-20231219.json get /bank-linking/investments/holdings/{investmentHoldingId} Use this endpoint to retrieve investment holding information of a specific financial account investment holding. # Search financial account investment holdings Source: https://docs.meld.io/api-reference/bank-linking/investments/bank-linking-investment-holdings-search /openapi/banklinking-20231219.json get /bank-linking/investments/holdings Use this endpoint to filter financial account investment holdings by various parameters. # Get a financial account investment transaction Source: https://docs.meld.io/api-reference/bank-linking/investments/bank-linking-investment-transactions-get /openapi/banklinking-20231219.json get /bank-linking/investments/transactions/{investmentTransactionId} Use this endpoint to retrieve investment transaction information of a specific financial account investment transaction. # Search financial account investment transactions Source: https://docs.meld.io/api-reference/bank-linking/investments/bank-linking-investment-transactions-search /openapi/banklinking-20231219.json get /bank-linking/investments/transactions Use this endpoint to filter financial account investment transactions by various parameters. # Get a financial account transaction Source: https://docs.meld.io/api-reference/bank-linking/transactions/bank-linking-transactions-get /openapi/banklinking-20231219.json get /bank-linking/transactions/{transactionId} Use this endpoint to retrieve transaction information of a specific financial account transaction. # Search financial account transactions Source: https://docs.meld.io/api-reference/bank-linking/transactions/bank-linking-transactions-search /openapi/banklinking-20231219.json get /bank-linking/transactions Use this endpoint to filter financial account transactions by various parameters. # Create a crypto quote Source: https://docs.meld.io/api-reference/crypto/retail-ramp/crypto-quote-get /openapi/crypto-20250304.json post /payments/crypto/quote Use this endpoint to request the current exchange rate of the selected fiat currency-cryptocurrency pair, and the required fees. Enter a fiat currency as the sourceCurrencyCode to buy crypto and enter a crypto currency in that field to sell crypto. # Create a crypto widget Source: https://docs.meld.io/api-reference/crypto/retail-ramp/crypto-session-widget-create /openapi/crypto-20231219.json post /crypto/session/widget Use this endpoint to create a crypto widget for a session to buy or sell crypto. # Get transaction by id Source: https://docs.meld.io/api-reference/crypto/retail-ramp/payments-transactions-get /openapi/crypto-20231219.json get /payments/transactions/{id} Search transaction by its Meld identifier # Search transactions Source: https://docs.meld.io/api-reference/crypto/retail-ramp/payments-transactions-search /openapi/crypto-20231219.json get /payments/transactions Search a list of transactions you've previously created. The transactions are ordered by the date of creation. # Fetch a transaction from the provider (for offramp use) Source: https://docs.meld.io/api-reference/crypto/retail-ramp/payments-transactions-sessions-get /openapi/crypto-20231219.json get /payments/transactions/sessions/{sessionId} Fetch a transaction from the service provider using the session ID. This endpoint is primarily used for offramp scenarios where the transaction needs to be retrieved from the provider after the user completes their action in the widget. # Create a crypto virtual account ramp quote Source: https://docs.meld.io/api-reference/crypto/virtual-account/crypto-virtual-account-quote-get /openapi/crypto-20231219.json post /payments/virtual-account/quote Use this endpoint to request the current exchange rate of the selected fiat currency-cryptocurrency pair, and the required fees. Enter a fiat currency as the sourceCurrencyCode to buy crypto and enter a crypto currency in that field to sell crypto. # Create a virtual account offramp order Source: https://docs.meld.io/api-reference/crypto/virtual-account/payments-virtual-account-ramp-offramp-order-create /openapi/crypto-20231219.json post /payments/virtual-account/offramp-order Create a virtual account offramp order to initiate crypto to fiat transfer # Create a virtual account onramp order Source: https://docs.meld.io/api-reference/crypto/virtual-account/payments-virtual-account-ramp-onramp-order-create /openapi/crypto-20231219.json post /payments/virtual-account/onramp-order Create a virtual account onramp order to receive bank details for fiat transfer # Add an address to a customer Source: https://docs.meld.io/api-reference/customer/customers/accounts-customers-addresses-create /openapi/customer-20231219.json post /accounts/customers/{customerId}/addresses # Create a new customer or retrieve a customer by its external id Source: https://docs.meld.io/api-reference/customer/customers/accounts-customers-create /openapi/customer-20231219.json post /accounts/customers # Delete a customer Source: https://docs.meld.io/api-reference/customer/customers/accounts-customers-delete /openapi/customer-20231219.json delete /accounts/customers/{customerId} Use this endpoint to delete a customer with Meld and/or with any of the service providers it is linked to. This endpoint should only be used for customers using the Bank-Linking stack, as it is not yet compatible with the Payments or Crypto stack. If a customer is deleted that is linked to a Payments or Crypto service provider customer, it will still be deleted with Meld, but not with the provider. ***Note:*** For customers using the Bank-Linking stack, this will delete all of the customer's connections and financial accounts as well. # Initiate customer KYC Source: https://docs.meld.io/api-reference/customer/customers/accounts-customers-kyc-initiate /openapi/customer-20231219.json post /accounts/customers/{customerId}/kyc/initiate Retrieve a url that when launched displays a UI that commences KYC for your user # Search customers Source: https://docs.meld.io/api-reference/customer/customers/accounts-customers-search /openapi/customer-20231219.json get /accounts/customers Returns a list of customers that match the query parameters. The customers are sorted by external id. # Update a customer Source: https://docs.meld.io/api-reference/customer/customers/accounts-customers-update /openapi/customer-20231219.json patch /accounts/customers/{customerId} # Import an external payment transaction Source: https://docs.meld.io/api-reference/payments/external-transactions/payments-external-transactions-import /openapi/payments-20231219.json post /payments/external/import This is the endpoint to import a transaction on any of the supported providers. Use this endpoint if you have performed transactions on one or more providers outside of Meld, and want to import those transactions into Meld's system. Note that when performing an [external refund](https://docs.meld.io/docs/crypto-supported-service-providers-assets), the initial transaction is automatically imported into Meld's system. # Refund an external payment transaction Source: https://docs.meld.io/api-reference/payments/external-transactions/payments-external-transactions-refund /openapi/payments-20231219.json post /payments/external/refund This is the endpoint to refund an external transaction on any of the supported providers. Use this endpoint if you have created a transaction with any of these providers outside of Meld's system, but want to perform the refund using Meld. Note that this will also cause the original transaction to be imported into Meld's system. Both partial and full refunds are supported. # Create a risk analysis Source: https://docs.meld.io/api-reference/payments/risk/payments-transactions-risk-analysis /openapi/payments-20231219.json post /payments/risk-analysis Perform an ACH risk analysis and create associated records # Create a Meld payment token Source: https://docs.meld.io/api-reference/payments/transactions/payment-token-post /openapi/payments-20231219.json post /payments/tokens Use this endpoint to create long-lasting Meld payment tokens that can be used across service providers. These tokens can replace the payment method provided for future transactions. [For more details see this page](https://docs.meld.io/docs/payment-methods-and-tokenization). # Capture a payment transaction Source: https://docs.meld.io/api-reference/payments/transactions/payments-transactions-capture /openapi/payments-20231219.json post /payments/transactions/{id}/capture See [here](https://docs.meld.io/docs/authorization-capture) to learn more about authorization and capture. # Create a payment transaction Source: https://docs.meld.io/api-reference/payments/transactions/payments-transactions-create /openapi/payments-20231219.json post /payments/transactions Use this endpoint to create a payment transaction. You can find sample code in our public Postman collection, or build it in the adjacent API explorer. The link to our postman collection is: [https://www.postman.com/meldeng/workspace/meld-io-public-api-collection](https://www.postman.com/meldeng/workspace/meld-io-public-api-collection) **IDEMPOTENT REQUESTS**: Please refer to [Idempotency Key](https://docs.meld.io/docs/idempotency-key) **PASSTHROUGH ENABLED REQUESTS**: Please refer to [Passthrough Headers](https://docs.meld.io/docs/credential-passthrough) **ERRORS**: Please refer to [Error Responses](https://docs.meld.io/docs/fiat-error-responses) # Get transaction by id Source: https://docs.meld.io/api-reference/payments/transactions/payments-transactions-get /openapi/payments-20231219.json get /payments/transactions/{id} Search transaction by its Meld identifier # Refund a payment transaction Source: https://docs.meld.io/api-reference/payments/transactions/payments-transactions-refund /openapi/payments-20231219.json post /payments/transactions/{id}/refund This is the endpoint to refund a transaction on any of the supported providers. Full as well as partial refunds are supported, so it is not mandatory to inform the total value of the original transaction in the case of a full refund. Note: While the parameters within the body are optional, you must at least pass in a valid JSON body, e.g. { }. # Search transactions Source: https://docs.meld.io/api-reference/payments/transactions/payments-transactions-search /openapi/payments-20231219.json get /payments/transactions Search a list of transactions you've previously created. The transactions are ordered by the date of creation. # Create a payout transaction Source: https://docs.meld.io/api-reference/payments/transactions/payouts-transactions-create /openapi/payments-20231219.json post /payments/transactions/payout Use this endpoint to create a payout transaction. You can find sample code in our public Postman collection, or build it in the adjacent API explorer. The link to our postman collection is: [https://www.postman.com/meldeng/workspace/meld-io-public-api-collection](https://www.postman.com/meldeng/workspace/meld-io-public-api-collection) **IDEMPOTENT REQUESTS**: Please refer to [Idempotency Key](https://docs.meld.io/docs/idempotency-key) **ERRORS**: Please refer to [Error Responses](https://docs.meld.io/docs/fiat-error-responses) # Search countries Source: https://docs.meld.io/api-reference/service-providers/service-provider/service-providers-properties-country-search /openapi/serviceproviders-20231219.json get /service-providers/properties/countries Returns a list of properties which meet the search criteria. # Search crypto currencies Source: https://docs.meld.io/api-reference/service-providers/service-provider/service-providers-properties-crypto-currency-search /openapi/serviceproviders-20231219.json get /service-providers/properties/crypto-currencies Returns a list of properties which meet the search criteria. # Search defaults by country Source: https://docs.meld.io/api-reference/service-providers/service-provider/service-providers-properties-defaults-by-country-search /openapi/serviceproviders-20231219.json get /service-providers/properties/defaults/by-country Returns a list of default fiat currencies and payment methods per country. # Search service providers Source: https://docs.meld.io/api-reference/service-providers/service-provider/service-providers-search /openapi/serviceproviders-20231219.json get /service-providers Returns a list of service providers which meet the search criteria. # FAQ Source: https://docs.meld.io/docs/bank-linking/bank-linking-faq Answers to the most common questions developers ask while integrating Meld Bank Linking. If you don't see your question here, check the [Activity Log](/docs/bank-linking/testing-and-debugging/debugging-activity-log) for connection-level detail or reach out to Meld support. Follow the [Creating Connections](/docs/bank-linking/creating-connections) guide for end-to-end instructions on making a connection. There are several likely candidates: * The provider doesn't support the institution for all of the products specified. * Your account with that provider may not be configured for a particular product or for OAuth institutions. * If you are using Smart Routing, Meld may know that the connection between that provider and institution is currently unstable and is therefore routing you to a different provider. Assuming the institution is supported by one of the providers you have enabled, the likely issue is that the institution doesn't support all of the products you requested when calling [/connect/start](/api-reference/bank-linking/connect/bank-linking-connect-create). Meld filters the institutions returned in the picker to only the ones that support all products requested. To check if this is the issue, try making a request for only `BALANCES` and `TRANSACTIONS` and see if your institution shows up. The most likely reason is your connection is in a status other than `ACTIVE`. Check the status of your connection — it might be `RECONNECT_REQUIRED` (the user must log in again in the repair flow) or `PARTIALLY_ACTIVE` (for example, because it's a duplicate). See [Connection Statuses and Errors](/docs/bank-linking/get-connection-data/connection-statuses-and-errors) for the full list. Plaid forces the user to select their institution in the Plaid picker even if it was already selected in the Meld picker. No other provider does this. The best Meld can do is have the institution you picked in the Meld picker be the first one in the list of the Plaid picker, which it does. Yes. Pass `"accountPreferenceOverride": {"selectionStyle": "TILE"}` as part of your [/connect/start request](/docs/bank-linking/creating-connections/create-a-connect-token). No. You can skip the Meld picker by passing in the ID of the institution the user will connect to. See [Create a Connect Token](/docs/bank-linking/creating-connections/create-a-connect-token) for how to prepopulate the picker. # Connection Errors and Reasons Source: https://docs.meld.io/docs/bank-linking/get-connection-data/connection-statuses-and-errors/connection-errors-and-reasons Each connection status has an associated `statusReason` that provides additional insight into **why** the connection is in that status and **what action**, if any, you should take. This page is for developers writing lifecycle handlers — use the recommended action column to decide whether to surface a reconnect prompt, schedule a retry, or treat the connection as terminal. For the high-level status definitions, see [Connection Statuses and Errors](/docs/bank-linking/get-connection-data/connection-statuses-and-errors). ## Status and reason reference | Status | Status Reason | Description | Can be refreshed? | Can reconnect? | Recommended developer action | | :------------------- | :--------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------- | :------------- | :---------------------------------------------------------------------------------------------------------- | | `IN_PROGRESS` | `null` | Customer is in the process of connecting within the widget. | No | No | Wait for the widget flow to finish; do not call refresh or repair. | | `EXPIRED` | `null` | Customer abandoned the widget or did not complete the connection in 4 hours. | No | No | Terminal. Create a new connection if the customer returns. | | `ACTIVE` | `null` | Connection completed and is healthy. Wait for webhooks to know when aggregation finishes. | Yes | No | Normal operation. Listen for `BANK_LINKING_ACCOUNTS_UPDATED` and `BANK_LINKING_TRANSACTIONS_AGGREGATED`. | | `ACTIVE` | `ACCESS_EXPIRING_SOON` | Access will expire eventually. Customer can reconnect to prevent expiry. | Yes | Yes | Prompt the customer to reconnect in your UI; call the repair endpoint to generate a connect token. | | `ACTIVE` | `NEW_ACCOUNTS_AVAILABLE` | New accounts can be added if the customer reconnects. | Yes | Yes | Optionally surface a "link new accounts" prompt and call the repair endpoint. | | `PARTIALLY_ACTIVE` | `PARTIAL_AGGREGATION_SUCCESS` | Aggregation was partially successful; some data or products are missing. | Yes | Yes | Refresh to try again; if it persists, repair the connection. | | `PARTIALLY_ACTIVE` | `INACTIVE_DUPLICATE` | The connection is a duplicate of another connection through the same service provider. See [Duplicate Connections](/docs/bank-linking/get-connection-data/duplicate-connections). | Yes | Yes | Decide whether to keep this duplicate. Delete it if unused; otherwise refresh to promote it to `ACTIVE`. | | `DEGRADED` | `INSTITUTION_CURRENTLY_UNAVAILABLE` | Temporary institution issues. | Yes | Yes | Retry later with exponential backoff; do not prompt the customer immediately. | | `DEGRADED` | `INSTITUTION_ACTION_REQUIRED` | Customer action with their institution is required before a refresh can succeed (e.g., re-authorize access in the bank's app). | Yes | Yes | Forward the message from the `BANK_LINKING_CONNECTION_STATUS_CHANGE` webhook to the customer; then refresh. | | `DEGRADED` | `SERVICE_PROVIDER_CURRENTLY_UNAVAILABLE` | Service provider is experiencing issues. | Yes | Yes | Retry later. If persistent, contact Meld support. | | `DEGRADED` | `INTERACTIVE_REFRESH` | Customer participation is required for every refresh on this provider/institution. | Yes | Yes | Avoid scheduled refreshes; only refresh in response to a customer action that returns them to your UI. | | `RECONNECT_REQUIRED` | `LOGIN_REQUIRED` | Credentials changed or MFA is required. | Yes | Yes | Send the customer through the repair flow. | | `RECONNECT_REQUIRED` | `REPAIRING_INSTITUTION` | Institution is being repaired on the provider's side. | Yes | Yes | Continue to attempt reconnects periodically until the institution is back. | | `RECONNECT_REQUIRED` | `TOKEN_EXPIRED` | Service provider's connection with the institution expired and needs re-authentication. | Yes | Yes | Send the customer through the repair flow. | | `RECONNECT_REQUIRED` | `OTHER` | Various other reasons requiring reconnection. | Yes | Yes | Inspect `serviceProviderDetails` for specifics; send the customer through the repair flow. | | `UNRECOVERABLE` | `CONTINUED_AGGREGATION_FAILURES` | Aggregation has failed at least three times and has not succeeded for at least two weeks. | No | Yes | Treat as broken. Optionally invite the customer to start a fresh connection. | | `UNRECOVERABLE` | `INSTITUTION_DISABLED` | The institution is no longer supported by the provider. | No | Yes | Direct the customer to connect a different institution or via a different provider via routing. | | `UNRECOVERABLE` | `OTHER` | Various other reasons that have made the connection unrecoverable. | No | Yes | Inspect `serviceProviderDetails`; consider creating a new connection. | | `UNDETERMINED` | `AGGREGATION_TIMEOUT` | A timeout prevented the service provider aggregation from completing. | Yes | Yes | Retry the refresh after a short delay. | | `UNDETERMINED` | `SUBMIT_SERVICE_PROVIDER_TICKET` | Submit a ticket to the provider; Meld cannot resolve. | Yes | Yes | Open a ticket with the underlying service provider; retry the refresh after they confirm a fix. | | `UNDETERMINED` | `UNKNOWN_SERVICE_PROVIDER_ERROR_CODE` | A provider error code Meld has not seen before. | Yes | Yes | Notify Meld support with the `connectionId` and the raw provider error; retry the refresh. | | `UNDETERMINED` | `INTERNAL_ERROR` | Meld internal error. | Yes | Yes | Notify Meld support; retry the refresh. | | `DELETED` | `null` | Connection was deleted. | No | No | Terminal. Start a new connection if the customer wants to reconnect. | The `BANK_LINKING_CONNECTION_STATUS_CHANGE` webhook fires whenever a connection moves between statuses or status reasons. Always use this webhook as the source of truth — do not poll the connections endpoint on a schedule.
# Connection Statuses and Errors Source: https://docs.meld.io/docs/bank-linking/get-connection-data/connection-statuses-and-errors/index Every Meld bank-linking connection has a **status** that reflects its current health and what actions you can take on it. Understanding these statuses is essential for any developer handling the connection lifecycle — knowing when to refresh, when to ask the customer to reconnect, and when a connection is unrecoverable. This page lists every status, what it means, and which lifecycle operations it supports. For the granular `statusReason` values and the recommended action per reason, see [Connection Errors and Reasons](/docs/bank-linking/get-connection-data/connection-statuses-and-errors/connection-errors-and-reasons). ## Connection statuses A connection is started immediately upon initialization in the [Create a Connection](/docs/bank-linking/creating-connections/create-a-connect-token) endpoint and can transition through multiple statuses over its lifespan. Connection status changes to error states typically occur during daily aggregations. ***Note*: Connection statuses are only for the connection, not its financial accounts. Financial account statuses are entirely independent and will only ever be active/inactive.** ***Note*: All connection status changes occur after the widget flow is completed/abandoned (except for when it is first created and set to `IN_PROGRESS`). Therefore any errors within the service provider widget flow that are displayed for the customer will not lead to connection status changes and are usually resolvable by the customer immediately (i.e. "wrong password, try again"). For information about these widget errors, this information is recorded in the [activity log](/docs/bank-linking/testing-and-debugging).** Below are the various connection statuses and what they mean: * `IN_PROGRESS` - A connection is in progress when it is first initialized and the customer is in the widget flow. It will remain in this status until it completes and becomes `ACTIVE` or is never finished and becomes `EXPIRED`.\ ***Note*: Connections that are being actively repaired do not go back to `IN_PROGRESS`. They will stay in their previous status until completed.**\ ***Note*: A connection with this status cannot be refreshed nor repaired as it is currently in progress with the customer.** * `EXPIRED` - A connection becomes expired if it was never completed in the allowed timeframe (4 hours) since its initialization. This usually indicates the customer abandoned the widget flow or encountered issues within the service provider widget and never finished it.\ ***Note*: A connection with this status is terminal and cannot be refreshed nor repaired.** * `ACTIVE` - A connection becomes active once the user successfully completes the widget flow and access to the underlying financial accounts is authorized. The [BANK\_LINKING\_CONNECTION\_COMPLETED](/docs/bank-linking/webhook-events-bank-linking) webhook will indicate once a connection becomes `ACTIVE`. A connection will remain active indefinitely unless an error occurs during a forced or background refresh, or it is deleted. Following the initial connection, an `ACTIVE` status does not necessarily imply that the initial aggregation of desired products has completed, but rather that the connection to the underlying institution is complete and aggregation is in progress. The [BANK\_LINKING\_ACCOUNTS\_UPDATED](/docs/bank-linking/webhook-events-bank-linking) webhook will notify of aggregation completion.\ ***Note*: A connection with this status is refreshable, but not repairable (unless the `statusReason` is `ACCESS_EXPIRING_SOON` or `NEW_ACCOUNTS_AVAILABLE`.** * `PARTIALLY_ACTIVE` - This status represents connections that may have partially aggregated, but not fully. This can occur if only some accounts belonging to the connection successfully aggregated, but not all. This status is also used for duplicate connections that have the same underlying service provider id, and only the active duplicate is routinely updated. In such case, the status reason will be `INACTIVE_DUPLICATE`.\ ***Note*: A connection with this status is refreshable and repairable.** * `DEGRADED` - The connection is experiencing temporary issues that are preventing aggregation, but it is still "connected". This can occur due to temporary institution issues, or the customer may need to take action with their institution directly (such as authorizing access). In the latter case, it is recommended to forward along this message to the customer, and the specific reasons/actions for this status can be found in the service provider details of the [BANK\_LINKING\_CONNECTION\_STATUS\_CHANGE](/docs/bank-linking/webhook-events-bank-linking) webhook.\ ***Note*: A connection with this status is refreshable and repairable.** * `RECONNECT_REQUIRED` - Indicates that a once `ACTIVE` connection is now failing and needs to be repaired in order to capture the most up to date data. When a connection enters this status, you will be notified via webhooks of the status change. Depending on the cause for error, the connection should be repaired via the [repair flow](/docs/bank-linking/manage-connection-status/repairing-connections). A common situation that might cause this is if a customer changes their bank password after connecting their accounts, or the underlying service provider consent to the accounts expired. Repairing the connection will make it `ACTIVE` again.\ ***Note*: A connection with this status cannot be refreshed and will not update until it is repaired.** * `UNRECOVERABLE` - The connection is permanently broken and can no longer be aggregated. We still allow repairing the connection in the rare chance that it can be recovered.\ ***Note*: A connection with this status is repairable but not refreshable.** * `DELETED` - A connection that is marked deleted is in a final state. This indicates that it will no longer be updated nor actively billed. A customer revoking consent to an existing connection with their bank will cause a connection to be deleted. Deleting a connection can also be triggered from the client-side, i.e. via the [delete a connection](/docs/bank-linking/manage-connection-status/deleting-connections) endpoint. Deleting a customer will also delete all of its connections. A [BANK\_LINKING\_CONNECTION\_DELETED](/docs/bank-linking/webhook-events-bank-linking) webhook will be sent when a connection has been deleted. A deleted connection cannot be recovered; a new connection must be started.\ ***Note*: A connection with this status is terminal and cannot be refreshed nor repaired.** * `UNDETERMINED` - Indicates an unforeseen service provider error, or an internal Meld error. Refreshes and reconnects are allowed as the error's true cause is not yet known.\ ***Note*: A connection with this status can be both refreshed and repaired as its true status is not yet known.** To understand why a connection may be in each of these statuses and learn if the connection can be refreshed or reconnected, see [Connection Errors and Reasons](/docs/bank-linking/get-connection-data/connection-statuses-and-errors/connection-errors-and-reasons). ## Widget Flow Errors We recommend capturing the following events to close the widget iframe when an error occurs or your customer does not complete the authentication step. * `[meld-connect]error` -- Emitted when the widget encounters an error * `[meld-connect]cancel` -- Emitted when your customer cancels the widget # Data Normalization Source: https://docs.meld.io/docs/bank-linking/get-connection-data/data-normalization Each service provider returns financial data with slightly different conventions — signage, status names, and category labels. Meld normalizes these into a single consistent shape so your code does not need provider-specific branches. This page is for developers consuming Meld's APIs who want to understand the normalized values and the rules behind them. Raw provider responses are still available in the `serviceProviderDetails` field on every endpoint and in the Activity Log. ## Balance signage Balance signage is generally **positive** for all account types — including loan, mortgage, and credit accounts, where a positive balance indicates the amount the customer owes. ## Transaction signage Service providers use different signage for deposits vs. withdrawals. Meld standardizes to: * **Positive** — money has moved **out** of the account. Includes withdrawals and debits. * **Negative** — money has moved **into** the account. Includes deposits. ## Investment types For both investment holdings and investment transactions, Meld normalizes the investment `type` in the response. * **Investment holdings:** `STOCK`, `MUTUALFUND`, `CASH`, `OTHER`, `REALESTATE`, `ETF`, `DERIVATIVE`, `DIGITALASSET`. * **Investment transactions:** `SELL`, `BUY`, `CANCEL`, `CASH`, `OTHER`, `TRANSFER`. ## Transaction status Transactions have one of the following statuses: * **Pending** — All transactions start as `PENDING`. A pending transaction has been approved but not yet fully processed. * **Posted** — `PENDING` transactions move to `POSTED` within a few days, typically one to five business days, with a rare two-week maximum. * **Expired** — On rare occasions, `PENDING` transactions are cancelled and stop appearing in provider responses. When this occurs, Meld marks them `EXPIRED`. When updating transaction data, fetch the last 14 days to capture status and amount changes. After two weeks, transactions almost never change. ## Transaction categories While providers group transactions into their own categories, Meld also normalizes transaction categories across providers. For the complete provider-to-Meld category mapping, refer to the [account transactions reference](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-transactions) and the [Bank Linking API reference](/api-reference/bank-linking). # Duplicate Connections Source: https://docs.meld.io/docs/bank-linking/get-connection-data/duplicate-connections Duplicate connections occur when multiple Meld connections reference the same underlying service provider connection. This occurs when the **same customer connects to the same bank account(s) through the same service provider in separate connect sessions using the same login credentials**. In such cases, the majority of service providers recognize that the connection already exists and will return the same id for it. However, since Meld generates connection ids immediately upon creation (prior to the customer actually linking their accounts and prior to knowing whether it will be a duplicate) then this means there will now be multiple Meld connections linked to the same underlying service provider connection id. Therefore it only becomes necessary to maintain one of these Meld connections to avoid excessive refreshing and redundancy. In such cases, the most recently connected of these duplicate Meld connections is considered `ACTIVE` and routinely maintained, with the remainder moving to status `PARTIALLY_ACTIVE` and `statusReason` `INACTIVE_DUPLICATE`. These `INACTIVE_DUPLICATE` Meld connections still share the same underlying financial accounts, transactions, etc. as the primary duplicate — the difference is that this data is refreshed once per service provider update rather than for every duplicate. At any time you can promote a `PARTIALLY_ACTIVE` connection to `ACTIVE` by [manually refreshing it](/docs/bank-linking/manage-connection-status/refreshing-connections), which demotes the previously `ACTIVE` duplicate to `PARTIALLY_ACTIVE`. Whenever a connection moves to `PARTIALLY_ACTIVE`, you will receive a `BANK_LINKING_CONNECTION_STATUS_CHANGE` [webhook](/docs/bank-linking/webhook-events-bank-linking). If duplicate connections exist, the `duplicateConnectionIds` list returned by the connections endpoint contains the ids of all of them ordered from most recently aggregated to oldest. Otherwise this list is `null`. Refer to the [Bank Linking API reference](/api-reference/bank-linking) for the connections endpoint schema. When deleting connections, all of its duplicates must be deleted in order to sever the connection with the underlying service provider and stop it from being billed. **Meld does not ever automatically delete duplicates but encourages you to do so if you are not using them.** Note: Duplicate connections will all belong to the same service provider and have the same service provider connection id. **If the user connects via different providers, then the connections will NOT be considered duplicates.** For service providers (such as Plaid) that DO NOT identify duplicate connections (and thus always use a unique id for each new connection regardless of whether the customer has already connected the same financial accounts), then the associated Meld connections will not be considered duplicates either. This is because the service provider still treats them as separate connections and they are billed separately.
# Retrieving Connection Data Source: https://docs.meld.io/docs/bank-linking/get-connection-data/retrieving-connection-data Once your customer has successfully completed a connection and Meld has signaled that data is ready (see [When Is Your Data Available](/docs/bank-linking/get-connection-data/when-is-your-data-available)), you can call the endpoints below to read their financial data. This page is for backend developers fetching connection data from Meld's REST API. ## Before you begin * You have received the `BANK_LINKING_ACCOUNTS_UPDATED` or `BANK_LINKING_TRANSACTIONS_AGGREGATED` webhook for the product you intend to query * You know the `customerId`, `externalCustomerId`, `connectionId`, or `financialAccountId` you want to scope the query to * Your API key has access to the relevant products ## Accessing customer financial data Meld's data endpoints follow two patterns: * **Get a single result by id** — pass the resource id. * **Filter through results by query parameters** — typical filters include: * `externalCustomerId` — the id you provided Meld for your customer. * `customerId` — the id Meld assigned for your customer. * `institutionId` — the Meld id for a financial institution. * `financialAccountId` — the Meld id for your customer's financial account. ### Financial data To retrieve [Balances](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-balances), [Identifiers](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-identifiers), or [Owners](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-owners), use the financial accounts endpoint. To retrieve [Transactions](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-transactions), use the transactions endpoint. Transactions are ordered newest first per financial account. To retrieve [Investment Holdings](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-investments), use the investment holdings endpoint. To retrieve **Investment Transactions**, use the investment transactions endpoint. ### Metadata To retrieve a list of connections, use the connections endpoint. For the complete request and response schema for each endpoint listed above, refer to the [Bank Linking API reference](/api-reference/bank-linking). For updating transaction data efficiently on subsequent calls, see [When Is Your Data Available — Updating Transaction Data](/docs/bank-linking/get-connection-data/when-is-your-data-available). The recommended approach is to fetch only the last 14 days when handling routine background aggregations. # Deleting Connections Source: https://docs.meld.io/docs/bank-linking/manage-connection-status/deleting-connections When a customer no longer needs a connection — they revoked access, switched banks, or the connection is a duplicate — you can delete it. This page is for developers cleaning up connections so they no longer receive webhooks or incur billing. ## Before you begin * You have the `connectionId` you want to delete * You understand deletion is **permanent** — a deleted connection cannot be recovered * For [duplicate connections](/docs/bank-linking/get-connection-data/duplicate-connections), remember to delete all duplicates if you want to fully sever the underlying provider link ## How to delete a connection Call the [delete connections endpoint](/api-reference/bank-linking) with the `connectionId`. Once you delete a connection: * It is deleted on **both** Meld and the underlying service provider. * You will no longer receive any updates (webhooks) for it. * You will no longer be billed for it. * A `BANK_LINKING_CONNECTION_DELETED` [webhook](/docs/bank-linking/webhook-events-bank-linking) is sent to confirm. You can only delete a **connection**, not individual financial accounts under a connection. To stop tracking a subset of a customer's accounts, the customer must reconnect and choose which accounts to share. For the full request and response schema, refer to the [Bank Linking API reference](/api-reference/bank-linking). ## Common errors | HTTP status | Likely cause | Developer action | | :-------------- | :------------------------------- | :------------------------------------------------------------------------------------------------------------------- | | `404 Not Found` | Invalid `connectionId` | Verify the id and the environment (sandbox vs production). | | `409 Conflict` | Connection is already `DELETED` | No action needed — confirm with a `GET` on the connection. | | `5xx` | Transient provider or Meld issue | Retry with backoff. The Meld side may succeed even if the provider call retries; the webhook is the source of truth. | # Importing Connections Source: https://docs.meld.io/docs/bank-linking/manage-connection-status/importing-connections If you already have connections with a service provider — for example, because you previously integrated directly with Plaid or Akoya — you can bring them into Meld. Importing a connection does not involve the customer; there is no widget flow and no re-authentication required. This page is for developers migrating from a direct provider integration to Meld. ## Before you begin * You have provider-level credentials (Plaid `access_token`, Akoya equivalent) for each connection you want to import * The corresponding service provider is enabled on your Meld account * You have decided what `externalCustomerId` to associate with each imported connection **Importing is currently supported for Plaid and Akoya only.** For other providers, the customer must complete the standard widget flow. ## How to import a connection Call the [import connections endpoint](/api-reference/bank-linking). Use the `externalCustomerId` field to tie each imported connection to an id of your choice for tracking. Importing a connection does the following: 1. Adds the connection and all its financial accounts to Meld. 2. Ties the connection to your `externalCustomerId`, if provided. 3. Tells the underlying provider to send webhooks for that connection to Meld going forward (instead of to your servers). 4. Refreshes the connection at the time of import. 5. Sends you the standard aggregation webhooks (`BANK_LINKING_ACCOUNTS_UPDATING`, `BANK_LINKING_ACCOUNTS_UPDATED`, `BANK_LINKING_TRANSACTIONS_AGGREGATED`) just like a fresh connection. Once a connection is imported, the provider will stop sending webhooks to your old endpoint. Make sure your Meld webhook profile is configured before importing in production. For the full request and response schema, refer to the [Bank Linking API reference](/api-reference/bank-linking). ## Common errors | Symptom | Likely cause | Developer action | | :------------------------------------------ | :---------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------- | | `400 Bad Request` — provider not supported | Importing only works for Plaid and Akoya | Use the standard widget flow for other providers. | | `401` or `403` from the underlying provider | Provider-level credentials supplied are invalid or expired | Verify the `access_token` is valid for the environment; re-issue if needed. | | Import succeeds but no webhooks arrive | Meld webhook profile not configured for bank-linking events | Configure the webhook profile per [Webhook Quickstart](/docs/bank-linking/onboarding/step-5-setting-up-webhooks/webhook-quickstart). | # Manage Connections Source: https://docs.meld.io/docs/bank-linking/manage-connection-status/index Over its lifetime, a connection may need to be **refreshed**, **repaired**, **updated**, **deleted**, or **imported**. This section is for developers building lifecycle handling on top of Meld bank linking. The four most-confused operations — refresh, repair, update, and import — all mutate the connection in different ways. The terminology matters: using the wrong endpoint will either no-op or charge for an operation that wasn't needed. ## Refresh vs. repair vs. update vs. delete vs. import | Operation | What it does | When to use it | Customer involved? | Status before | Status after | Endpoint | | :---------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------ | :---------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------- | :----------------------------------------------------------------------------------------------- | | **Refresh** | Pulls the latest balances and transactions from the underlying provider for an existing, healthy connection. | The customer just took an action in your app and you want up-to-the-minute data. Don't use for routine polling — background refresh handles that. | No | `ACTIVE`, `PARTIALLY_ACTIVE`, `DEGRADED`, `UNDETERMINED` | Same status; new aggregation webhooks | [Refreshing Connections](/docs/bank-linking/manage-connection-status/refreshing-connections) | | **Repair** | Generates a new connect token that takes the customer straight to the institution login so they can re-authenticate. Brings a broken connection back online. | The customer changed their bank password, MFA changed, or the provider consent expired. The connection moved to `RECONNECT_REQUIRED`. | **Yes** — they go through the widget again | `RECONNECT_REQUIRED`, `DEGRADED`, `UNRECOVERABLE`, or `ACTIVE` (when `ACCESS_EXPIRING_SOON` / `NEW_ACCOUNTS_AVAILABLE`) | `ACTIVE` | [Repairing Connections](/docs/bank-linking/manage-connection-status/repairing-connections) | | **Update** | Adds new **products** (e.g., `INVESTMENT_HOLDINGS`) to an existing connection. New products are added as `optionalProducts` and aggregated on a best-effort basis. | You initially requested `BALANCES` and `TRANSACTIONS`, and now you want investment data on the same connection without a fresh widget flow. | No (provider-dependent — may require repair flow) | Any non-terminal | Same status; new product aggregation kicks off | [Updating Connection Products](/docs/bank-linking/manage-connection-status/updating-connections) | | **Delete** | Severs the connection on Meld and the underlying provider. No more webhooks, no more billing. Cannot be undone. | The customer no longer wants this account linked, or you are cleaning up duplicates. | No | Any | `DELETED` | [Deleting Connections](/docs/bank-linking/manage-connection-status/deleting-connections) | | **Import** | Brings an existing provider connection into Meld without sending the customer through the widget. | You already have Plaid or Akoya connections and you are migrating to Meld. | No | n/a (new in Meld) | `ACTIVE` | [Importing Connections](/docs/bank-linking/manage-connection-status/importing-connections) | ## Quick guide 1. [Refresh a connection.](/docs/bank-linking/manage-connection-status/refreshing-connections) Use for one-off data pulls. Background refreshes run automatically; do not refresh on a schedule from your code. 2. [Repair a connection.](/docs/bank-linking/manage-connection-status/repairing-connections) Use when the connection is in `RECONNECT_REQUIRED` (or `DEGRADED` with a customer-actionable reason). 3. [Update a connection's products.](/docs/bank-linking/manage-connection-status/updating-connections) Use to add `BALANCES`, `INVESTMENT_HOLDINGS`, etc. retroactively without re-prompting the customer. 4. [Delete a connection.](/docs/bank-linking/manage-connection-status/deleting-connections) Use when the customer revokes, when cleaning up duplicates, or when the institution is no longer supported. 5. [Import a connection.](/docs/bank-linking/manage-connection-status/importing-connections) Use to migrate existing Plaid or Akoya connections into Meld. **Terminology matters.** "Refresh" pulls new data on a working connection. "Repair" (sometimes called "reconnect") fixes a broken connection by re-authenticating the customer. "Update" adds new products. These are distinct endpoints with distinct billing implications. # Refreshing Connections Source: https://docs.meld.io/docs/bank-linking/manage-connection-status/refreshing-connections A **refresh** pulls the latest balances and transactions for an existing, healthy connection from the underlying service provider. This page is for developers deciding when and how to refresh — and when **not** to (most of the time, background refresh has you covered). ## Before you begin * You have a `connectionId` for a connection that is **not** in `RECONNECT_REQUIRED`, `EXPIRED`, `DELETED`, or `IN_PROGRESS` (those statuses cannot be refreshed — see [Connection Errors and Reasons](/docs/bank-linking/get-connection-data/connection-statuses-and-errors/connection-errors-and-reasons)) * You understand that forced refreshes can incur additional charges and may be rate-limited by the provider * You have webhooks configured so you can observe the resulting aggregation events ## Automatic refreshes Service providers typically refresh connection data (balances and transactions) once or a few times a day, and Meld pulls the latest data at that time. When Meld detects a change, you receive webhooks so you can fetch the latest data. **You do not need to schedule refreshes yourself for routine freshness.** ## Forced refreshes At any time you can call the [refresh endpoint](/api-reference/bank-linking) to force a live aggregation of all accounts on a connection. You can scope the refresh to specific products. Both forced and automatic refreshes produce webhooks. Forced refreshes can incur additional charges and providers rate-limit them. For example, MX supports forced refresh only every 3 hours. Use forced refresh only when the customer just performed an action in your app that requires up-to-the-minute data. ## How to refresh a connection Call the refresh endpoint with the `connectionId` and (optionally) the list of products you want to refresh. For the full request and response schema, refer to the [Bank Linking API reference](/api-reference/bank-linking). After the call returns, listen for the standard aggregation webhooks — `BANK_LINKING_ACCOUNTS_UPDATED`, `BANK_LINKING_TRANSACTIONS_AGGREGATED` — to know when fresh data is available. ## Common errors | HTTP status | Likely cause | Developer action | | :-------------- | :---------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `404 Not Found` | Invalid `connectionId` | Verify the id and the environment (sandbox vs production). | | `409 Conflict` | Connection is in a status that cannot be refreshed (e.g. `RECONNECT_REQUIRED`, `EXPIRED`) | Check the status; if `RECONNECT_REQUIRED`, send the customer through the [repair flow](/docs/bank-linking/manage-connection-status/repairing-connections) instead. | | `429 Too Many` | Provider rate limit hit (e.g. MX 3-hour limit) | Back off and retry after the provider window. Do not loop on this error. | | `5xx` | Transient Meld or provider issue | Retry with exponential backoff; if persistent, inspect the next `BANK_LINKING_CONNECTION_STATUS_CHANGE` webhook to see if the connection moved to `DEGRADED` or `UNDETERMINED`. | # Repairing Connections Source: https://docs.meld.io/docs/bank-linking/manage-connection-status/repairing-connections A **repair** (also called reconnect) fixes a broken connection by sending the customer back into the widget straight to the institution login page, skipping the institution picker. Repair is the only way to bring a `RECONNECT_REQUIRED` connection back to `ACTIVE`. This page is for developers wiring up the reconnect prompt in their UI. ## Before you begin * You have received a `BANK_LINKING_CONNECTION_STATUS_CHANGE` webhook moving the connection to `RECONNECT_REQUIRED` (or a `DEGRADED` reason that requires customer action, or an `ACTIVE` connection with `statusReason` `ACCESS_EXPIRING_SOON` / `NEW_ACCOUNTS_AVAILABLE`) * You know the `connectionId` of the broken connection * You have UI ready to render the widget — repair generates a connect token just like Step 1 ## When connections need repair Connections that were initially successful (`ACTIVE`) can later require reconnection and move to `RECONNECT_REQUIRED`. A connection in `RECONNECT_REQUIRED` will **no longer update**; the latest data available (still queryable via Meld's endpoints) is from before the connection degraded. A connection can also remain `ACTIVE` with a `statusReason` of `NEW_ACCOUNTS_AVAILABLE` — meaning the customer has the option to add additional accounts via the repair flow. Common causes of a broken connection: * The customer changed their bank password after the initial connection. * The institution expired the consent after a set period and requires the customer to re-authenticate. * Multi-factor authentication settings changed at the institution. * Provider-level token or session expired. ## How to repair a connection Call the [repair endpoint](/api-reference/bank-linking) with the `connectionId`. The response includes a fresh `connectToken` and `widgetUrl`. Launch the widget exactly the same way you did in [Step 2: Launch the Widget](/docs/bank-linking/creating-connections/launch-the-widget) — but the customer skips the institution picker and goes straight to the login screen. Once the customer logs in successfully, you will receive a `BANK_LINKING_CONNECTION_STATUS_CHANGE` webhook indicating the connection is `ACTIVE` again, and you can retrieve up-to-date data. For the full request and response schema, refer to the [Bank Linking API reference](/api-reference/bank-linking). ## Sample response ```json theme={null} { "id": "WQ5RitxnrnggqcT8noLoZN", "connectToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyZWRpcmVjdCI6dHJ1ZSwicmVnaW9ucyI6WyJVUyJdLCJpc3MiOiJtZWxkLmlvIiwiY29ubmVjdGlvbiI6IldRNVJpdHhucm5nZ3FjVDhub0xvWk4iLCJleHAiOjE2ODcyMzM4NTgsImlhdCI6MTY4NzIxOTQ1OCwiYWNjb3VudCI6Ilc5a2JrUm1lOVZUMml6NnFBU2ZBZjIiLCJjdXN0b21lciI6IldRNVJpd2lBTkM5ZnhTdmhZRnpXOXIiLCJwcm9kdWN0cyI6WyJCQUxBTkNFUyIsIklERU5USUZJRVJTIiwiT1dORVJTIiwiVFJBTlNBQ1RJT05TIl0sInJvdXRpbmdQcm9maWxlIjoiV0d2Rlh0WGJEb0NlV3N4VXR1aXhQeSJ9.d_D-VTwLx51bT2DwtNwxpH38oaaKdnrLQD0SM-pXFpU", "institutionId": "Ntj3AwgdridJc2rtfeheku", "widgetUrl": "https://institution-connect-sb.meld.io?connectToken=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyZWRpcmVjdCI6dHJ1ZSwicmVnaW9ucyI6WyJVUyJdLCJpc3MiOiJtZWxkLmlvIiwiY29ubmVjdGlvbiI6IldRNVJpdHhucm5nZ3FjVDhub0xvWk4iLCJleHAiOjE2ODcyMzM4NTgsImlhdCI6MTY4NzIxOTQ1OCwiYWNjb3VudCI6Ilc5a2JrUm1lOVZUMml6NnFBU2ZBZjIiLCJjdXN0b21lciI6IldRNVJpd2lBTkM5ZnhTdmhZRnpXOXIiLCJwcm9kdWN0cyI6WyJCQUxBTkNFUyIsIklERU5USUZJRVJTIiwiT1dORVJTIiwiVFJBTlNBQ1RJT05TIl0sInJvdXRpbmdQcm9maWxlIjoiV0d2Rlh0WGJEb0NlV3N4VXR1aXhQeSJ9.d_D-VTwLx51bT2DwtNwxpH38oaaKdnrLQD0SM-pXFpU" } ``` The connect token returned by repair expires after 3 hours, identical to a fresh `/connect/start` token. If the customer doesn't complete the repair in that window, request a new one. ## Common errors | Symptom | Likely cause | Developer action | | :-------------------------------------------------------------- | :------------------------------------------------------ | :----------------------------------------------------------------------------------------------------------- | | `404 Not Found` on the repair endpoint | Invalid `connectionId` | Verify the id and the environment. | | `409 Conflict` — connection cannot be repaired | Connection is in `EXPIRED`, `DELETED`, or `IN_PROGRESS` | These statuses are terminal or active. Start a new connection instead. | | Customer completes repair but status stays `RECONNECT_REQUIRED` | Provider-side failure during re-authentication | Inspect the next `BANK_LINKING_CONNECTION_STATUS_CHANGE` webhook for the new reason; consider routing retry. | # Updating Connection Products Source: https://docs.meld.io/docs/bank-linking/manage-connection-status/updating-connections Products on a connection are first specified in the `/connect/start` request, but you may want to add products **retroactively** — for example, enabling `INVESTMENT_HOLDINGS` on a connection that originally only requested `BALANCES`. This page is for developers expanding the data they retrieve from an existing connection without sending the customer back through the widget. ## Before you begin * You have an existing connection in a non-terminal status (not `EXPIRED` or `DELETED`) * The product you want to add is supported for retroactive addition on the connection's underlying service provider — see the support matrix below * You understand that the new product lands in `optionalProducts` and is aggregated on a best-effort basis ## How updating works Call the [update endpoint](/api-reference/bank-linking) with the connection id and the products to add. Depending on the service provider, there may be limitations on which products can be added retroactively — the institution may not support the new product, or the provider may require additional customer consent. The newly requested products are added to `optionalProducts` on the connection and are aggregated on a best-effort basis. Adding products via the update endpoint does **not** guarantee data will be returned for those products. If the provider requires re-authentication, you may need to send the customer through the [repair flow](/docs/bank-linking/manage-connection-status/repairing-connections) to capture the new product. For the full request and response schema, refer to the [Bank Linking API reference](/api-reference/bank-linking). ## Support matrix Below are the products that can be added post-connect for each service provider (assuming the institution already supports them): | | | | | | | | | :-------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------- | | |
[Identifiers](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-identifiers)
|
[Owners](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-owners)
|
[Balances](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-balances)
|
[Transactions](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-transactions)
|
[Investment Holdings](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-investments)
|
[Investment Transactions](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-investments)
| | [Finicity](/docs/bank-linking/partner-details/finicity) |
X
|
X
|
X
|
X
|
X
|
X
| | [Plaid](/docs/bank-linking/partner-details/plaid) |
X
|
X
|
X
|
X
|
X
|
X
| | Yodlee | | | | | | | | [MX](/docs/bank-linking/partner-details/mx) | | | | only if `INVESTMENT_TRANSACTIONS` is already a product | only if `INVESTMENT_TRANSACTIONS` is already a product | only if `TRANSACTIONS` or `INVESTMENT_HOLDINGS`is already a product | | [Salt Edge Open Banking](/docs/bank-linking/partner-details/salt-edge) | | | | | | | | [Salt Edge Partners](/docs/bank-linking/partner-details/salt-edge-partners) | | | | | | | | Mesh | | | | | | | | Akoya |
X
|
X
|
X
|
X
|
X
|
X
| For MX, `INVESTMENT_TRANSACTIONS` can only be added after the connection is made if `TRANSACTIONS` was specified as a product on the original `/connect/start` call, and vice versa.
# Finicity Balances Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/finicity/finicity-balances-mapping This data comes from Finicity's [/aggregation/v1/customers /\{customerId}/institutionLogins /\{institutionLoginId}/accounts/](https://api-reference.finicity.com/#/rest/api-endpoints/accounts/get-customer-accounts-by-institution-login) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Meld uses the cached balance [GetCustomerAccountsByInstitutionsLogin](https://developer.mastercard.com/open-banking-us/documentation/api-reference/?view=api#GetCustomerAccountsByInstitutionLogin) the vast majority of the time (for daily refreshes). If you force refresh just balances, Meld calls [GetAvailableBalanceLive](https://developer.mastercard.com/open-banking-us/documentation/api-reference/?view=api#GetAvailableBalanceLive) (because it's quicker and cheaper), but if you refresh balances and transactions Meld calls [RefreshCustomerAccountsByInstitutionLogin](https://developer.mastercard.com/open-banking-us/documentation/api-reference/?view=api#RefreshCustomerAccountsByInstitutionLogin). Example values: `currency: "USD"`, `currentAmount: 1543.22`, `availableAmount: 1487.10`, `updatedAt: "2024-08-12T14:33:00Z"`.
Meld Description Finicity Response Field
currency The currency of the account balance currency
currentAmount The current amount in the account balance
availableAmount The available amount in the account Assigned in the following order if non-null: detail.availableBalanceAmount\ detail.availableCashBalance\ balance
updatedAt The last time balances were updated balanceDate
# Finicity Identifiers Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/finicity/finicity-identifiers-mapping This data comes from Finicity's [/aggregation/v1/customers /`{customerId}`/accounts /`{accountId}`/details](https://api-reference.finicity.com/#/rest/api-endpoints/payments/get-account-ach-details) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example values: `ach.accountNumber: "1234567890"`, `ach.routingNumber: "021000021"`. Finicity returns ACH identifiers only. `eft.*`, `international.iban`, `international.bic`, and `bacs.*` fields are not populated. | Meld | Description | Finicity Response Field | | :------------------- | :--------------------------------------------------------------------------------------------------- | :---------------------- | | ach | ACH data object | | |    accountNumber | The account number | realAccountNumber | |    routingNumber | The account routing number. Reference [Routing number](https://en.wikipedia.org/wiki/Routing_number) | routingNumber | | eft | EFT data object | | |    accountNumber | The account number | *None* | |    institutionNumber | The account institution number | *None* | |    branchNumber | The institution branch number | *None* | | international | International data object | | |    iban | The International Bank Account Number (IBAN) for the financial account | *None* | |    bic | The Bank Identifier Code (BIC) for the financial account | *None* | | bacs | The BACS data object | | |    accountNumber | BACS account number | *None* | |    sortCode | BACS sort code | *None* |
# Finicity Investment Holdings Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/finicity/finicity-investment-holdings-mapping This data comes from Finicity's [/aggregation/v1/customers/\{customerId}/institutionLogins/\{institutionLoginId}/accounts](https://developer.mastercard.com/open-banking-us/documentation/api-reference/?view=api#GetCustomerAccountsByInstitutionLogin) endpoint and is returned from Meld's [/bank-linking/investments/holdings](/api-reference/bank-linking/investments/bank-linking-investment-holdings-search) endpoint. Example values: `symbol: "AAPL"`, `quantity: 10`, `currentValue: 1820.50`, `costBasis: 150.25`, `currencyCode: "USD"`, `isin: "US0378331005"`, `cusip: "037833100"`, `type: "STOCK"`. Finicity does not provide `description` or `closePrice` for investment holdings. | Meld Field | Description | Finicity Response Field | | :----------- | :------------------------------------------------------------------------- | :---------------------- | | symbol | The symbol of the security | symbol | | quantity | The number of shares of the security | units | | currentValue | The total current value of the holding | marketValue | | costBasis | The purchase price of the holding, per share | cost\_basis | | currencyCode | The ISO currency code that was used to purchase the holding | securityCurrency | | updatedAt | The last time the details of this holding were updated | currentPriceDate | | description | A description of the holding | **Not Available** | | closePrice | The price of the security at last market close | **Not Available** | | isin | The global ISO number for an individual security. | Securities.isin | | cusip | A shortened version of the isin used for North American securities. | cusipNo | | type | The type of holding (ex: stock, etf). This is normalized across providers. | securityType | # Finicity Investment Transactions Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/finicity/finicity-investment-transactions-mapping This data comes from Finicity's [/investments/transactions/get](https://api-reference.finicity.com/#/rest/models/structures/transactions) endpoint and is returned from Meld's [/bank-linking/investments/transactions](/api-reference/bank-linking/investments/bank-linking-investment-transactions-search) endpoint. Example values: `symbol: "AAPL"`, `quantity: 5`, `amount: -912.50`, `currency: "USD"`, `description: "BUY AAPL"`, `status: "POSTED"`, `transactionDate: "2024-08-09"`, `postedDate: "2024-08-10"`, `type: "BUY"`. Finicity does not provide `cashBalance` for investment transactions.
Meld Field Description Finicity Response Field
symbol The symbol of the security symbol
quantity The number of shares of the security unitQuantity
costBasis The purchase price of the holding, per share costBasis
cashBalance The cash balance of the account after the transaction **Not Available**
accountId The Id of the financial account accountId
amount The total currency involved in the transaction amount
currency The ISO currency code that was used to purchase the holding currencySymbol
description A description of the transaction description
status The status of the transaction status\
  - If returns `Active`, Meld will populate the value as `POSTED`
  - If returns `Pending`, Meld will populate the value as `PENDING`
  - If returns `Shadow`, Meld will populate the value as `PENDING`\ **Note:**\ Some institutions continue to modify or delete investment transactions long after they are first posted to the institution’s data feed. This practice can cause Finicity transactions to appear as duplicates, or to continue to appear in the Finicity data after they have disappeared from the institution’s current website.\ Finicity has added the ability to identify transactions that were found in an earlier aggregation of an account, but are not found in the institution’s current data source. These `SHADOW` transactions are identified in the `transaction record`.
transactionDate The date the transaction was initiated transactionDate
postedDate The date the transaction was finalized postedDate
type The type of investment transaction investmentTransactionType
# Finicity Owners Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/finicity/finicity-owners-mapping This data comes from Finicity's [/aggregation/v1/customers /`{customerId}`/accounts /`{accountId}`/owner](https://api-reference.finicity.com/#/rest/api-endpoints/account-owner/get-account-owner) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example values: `names: ["Jane Doe"]`, `addresses.data.full: "123 Main St, Springfield, IL 62701"`. Finicity returns owner address as a single unparsed string in `ownerAddress`, mapped to `addresses.data.full`. Parsed sub-fields (`street`, `city`, `region`, `postalCode`, `country`) are not provided. Email and phone number data are also not provided.
Meld Field Description Finicity Response Field
addresses The address(es) associated with this owner
   data The address data object
      street The street and residence number *None*
      city The city *None*
      region The region/state *None*
      postalCode The postal or zip code *None*
      country The ISO 3166-1 alpha-2 country code *None*
      full The full unparsed address ownerAddress
   primary Indicates if this is the owner's primary residence Does not provide.
Meld will\ default\ to`Primary.UNKNOWN`
emails The email(s) associated with this owner
   data The email address *None*
   primary Indicates if this is the owner's primary email *None*
names The name(s) of this owner ownerName
phoneNumbers The phone number(s) associated with this owner
   data The phone number *None*
   primary Indicates if this is the owner's primary phone number *None*
# Finicity to Meld Overall Mappings Source: https://docs.meld.io/docs/bank-linking/partner-details/finicity/finicity-to-meld-mappings You can always compare the raw responses from Finicity in the `serviceProviderDetails` section of a financial account. [Financial Account Base Fields](#financial-account-base-fields) [Balances](./finicity-balances-mapping) [Identifiers](./finicity-identifiers-mapping) [Owners](./finicity-owners-mapping) [Transactions](./finicity-transactions-mapping) [Investment Holdings](./finicity-investment-holdings-mapping) [Investment Transactions](./finicity-investment-transactions-mapping) [Normalized Account Types and Subtypes](#normalized-account-types-and-subtypes) ## Financial Account Base Fields This data comes from Finicity's [/aggregation/v1/customers /`{customerId}`/institutionLogins /`{institutionLoginId}`/accounts/](https://api-reference.finicity.com/#/rest/api-endpoints/accounts/get-customer-accounts-by-institution-login) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint.
Meld Description Finicity Response Field
name The account name name
truncated\ AccountNumber The last 4 digits of the account number realAccountNumberLast4 * If null:\_ accountNumberDisplay
status The real-time status of the account status
Possible values:
- `PENDING`
- `ACTIVE`
type The type of the account. Mapped to a Meld standardized type type
subtype The subtype of the account. Mapped to a Meld standardized subtype based on the type * None\_. Meld subtype\ determined from Finicity type.
## Normalized Account Types and Subtypes # Finicity Transactions Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/finicity/finicity-transactions-mapping This data comes from Finicity's [/aggregation/v3/customers /`{customerId}`/accounts /`{accountId}`/transactions](https://api-reference.finicity.com/#/rest/api-endpoints/transactions/get-customer-account-transactions) endpoint and is returned from Meld's [/bank-linking/transactions](/api-reference/bank-linking/transactions/bank-linking-transactions-search) endpoint. To fetch transaction data, Meld calls [GetCustomerAccountTransactions](https://developer.mastercard.com/open-banking-us/documentation/api-reference/?view=api#GetCustomerAccountTransactions) the vast majority of the time, Meld only uses the paid endpoint [LoadHistoricTransactionsForCustomerAccount](https://developer.mastercard.com/open-banking-us/documentation/api-reference/?view=api#LoadHistoricTransactionsForCustomerAccount) for historical transactions (1 time per connection on the initial load). Forced refreshes are done via [RefreshCustomerAccountsByInstitutionLogin](https://developer.mastercard.com/open-banking-us/documentation/api-reference/?view=api#RefreshCustomerAccountsByInstitutionLogin). Finicity's transaction signs (positive and negative) are inverted when mapped to make the behavior consistent with other providers. You can find more information on signage [here](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-transactions#data-normalization). Example values: `amount: -42.50`, `currency: "USD"`, `description: "STARBUCKS #1234"`, `status: "POSTED"`, `transactionDate: "2024-08-10"`, `postedDate: "2024-08-11"`. Finicity may continue to surface transactions as `SHADOW` after the institution has removed them from its current data feed. Treat `SHADOW` transactions accordingly when reconciling.
Meld Description Finicity Response Field
amount The amount of the transaction amount
currency The currency used in the transaction currencySymbol * If null:\_ Meld will default\ to `USD`
description The transaction's description description
status The status of the transaction status
  - If returns `Active`, Meld will populate the value as `POSTED`
  - If returns `Pending`, Meld will populate the value as `PENDING`
  - If returns `Shadow`, Meld will populate the value as `SHADOW` **Note:**\ Some institutions continue to modify or delete transactions long after they are first posted to the institution’s data feed. This practice can cause Finicity transactions to appear as duplicates, or to continue to appear in the Finicity data after they have disappeared from the institution’s current website. Finicity has added the ability to identify transactions that were found in an earlier aggregation of an account, but are not found in the institution’s current data source. These `SHADOW` transactions are identified in the `transaction record`.
transactionDate The date and time the transaction was made transactionDate * If null:\_ postedDate
postedDate The date and time the transaction was posted postedDate
# Finicity Partner Details Source: https://docs.meld.io/docs/bank-linking/partner-details/finicity/index Finicity (a Mastercard company) is a US-focused open banking provider. Meld supports Finicity for Balances, Identifiers, Owners, Transactions, Investment Holdings, and Investment Transactions across United States and Canadian institutions. Do not attempt to set up webhooks from Finicity to Meld. Meld passes Finicity the correct webhook URL when initiating the Finicity widget (in any environment). ## Supported Countries Meld supports Finicity institutions in the United States (US) and Canada (CA). ## Configuration Settings If you have your own [Finicity](https://www.finicity.com/) account that you wish to integrate with Meld, you will need to provide the following credentials securely on the Meld Integrations Tab on the dashboard: * *App Key* * *Partner Id* * *Partner Secret* * *Experience Id* ![](https://files.readme.io/b593fe1-image.png) ## Special considerations Finicity refreshes existing connections once a day with the institution. They do not notify when these updates occur, so Meld polls daily to check for updates. Finicity bills extra for aggregating historical transactions. Meld loads them by default on the initial connection. Contact Meld if you would instead like to configure Finicity connections to only load historical transactions once the first forced refresh is triggered for the connection. # Partner Details Source: https://docs.meld.io/docs/bank-linking/partner-details/index Meld routes bank linking traffic through several service providers. Each provider has its own institution coverage, product support, and data shape. The pages in this section document how Meld maps each partner's raw data into Meld's normalized models so you can predict exactly what your application will receive. Use these pages when you need to: * Confirm which products a given provider supports. * Trace a Meld field back to the underlying provider field. * Diagnose differences between connections sourced from different providers. ## Supported partners Largest US institution coverage. Strong support for balances, transactions, identifiers, owners, and investments. Broad US coverage with a user-friendly widget and reliable identifier and balance data. Mastercard Open Banking provider with strong US bank coverage and detailed transaction data. Open Banking provider with strong international coverage, especially across Europe. Meld also supports Salt Edge Partners as a separate routing option for white-label and partner-managed Salt Edge integrations. See the Salt Edge Partners section in the sidebar for those mappings. # MX Partner Details Source: https://docs.meld.io/docs/bank-linking/partner-details/mx/index [MX](https://www.mx.com/) is a bank linking aggregator that Meld integrates with to power account aggregation in the United States and Canada. Through MX, Meld supports the following products: account base data, balances, identifiers (ACH/EFT), owners (identity), transactions, investment holdings, and investment transactions. This page covers supported countries, configuration settings, and MX webhooks. For field-by-field mappings, see the per-product mapping pages linked from [MX to Meld Overall Mappings](/docs/bank-linking/partner-details/mx/mx-to-meld-mappings). MX does not return international identifiers (IBAN/BIC) or BACS identifiers. If your use case requires those formats, see [Plaid](/docs/bank-linking/partner-details/plaid/plaid-identifiers-mapping) or [Salt Edge](/docs/bank-linking/partner-details/salt-edge). ## Supported Countries Meld supports MX institutions in the United States (US) and Canada (CA). ## Configuration Settings If you have your own [MX](https://www.mx.com/) account that you wish to integrate with Meld, you will need to provide the following credentials securely on the Meld Integrations Tab on the dashboard: * *Client ID* * *API Key* * *Webhook Username* * *Webhook Password* ![](https://files.readme.io/af21d64-image.png) ## MX Webhooks MX specifically requires a webhook username and password when setting up webhooks to Meld. However, Meld needs your MX API Key and Client ID before we can generate a url for you to use set up webhooks in the MX dashboard. Therefore when setting up an MX integration, the recommended order of operations is for you to do the following: 1. Add your API Key and Client Id for MX in the dashboard and click Add Integration. 2. Grab your Meld webhook url from the dashboard and use that to go and set up a webhook profile on MX's dashboard. You should set up webhooks for Aggregation, Balance, Connection Status, and History. 3. While still on MX's dashboard, for each webhook, choose the security type basic and set up a webhook username and password. You should use the same username and password for all webhook types within an environment. 4. Come back to Meld's dashboard and add the username and password to the MX integration credentials. ## Special Considerations * In MX production, they require whitelisting the IP addresses that will be hitting their API. This means you will need to request that Meld's IP's be whitelisted on their dashboard. Depending on the Meld environment your MX production account is pointing to, will require different IP's. They are as follows: * Meld Sandbox: `3.12.70.233`, `18.188.161.75`, `52.14.94.218` * Meld Production: `23.20.254.181`, `44.195.151.201`, `44.196.135.166`, `54.158.91.174`, `54.173.48.67`, `184.73.192.20` # MX Balances Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/mx/mx-balances-mapping This data comes from MX's [/users/\{user\_guid}/accounts](https://docs.mx.com/api#core_resources_accounts_list_accounts) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example: MX `currency_code: "USD"`, `balance: 1203.45`, `available_balance: 1150.20` maps to Meld `currency: "USD"`, `currentAmount: 1203.45`, `availableAmount: 1150.20`. | Meld Field | Description | MX Response Field | | :-------------- | :---------------------------------- | :----------------- | | currency | The currency of the account balance | currency\_code | | currentAmount | The current amount in the account | balance | | availableAmount | The available amount in the account | available\_balance | | updatedAt | The last time balances were updated | updated\_at | # MX Identifiers Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/mx/mx-identifiers-mapping This data comes from MX's [/users/`{user_guid}`/members/`{member_guid}`/account\_numbers](https://docs.mx.com/api#verification_mx_widgets_list_account_numbers_by_account) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example: MX `account_number: "1111222233330000"` and `routing_number: "011401533"` map to Meld `ach.accountNumber` and `ach.routingNumber` respectively. MX does not return international identifiers (IBAN, BIC) or BACS (UK) account details. Those fields will be `null` in the Meld response when sourced from MX. | Meld Field | Description | MX Response Field | | :------------------- | :--------------------------------------------------------------------------------------------------- | :------------------ | | ach | ACH data object | | |    accountNumber | The account number | account\_number | |    routingNumber | The account routing number. Reference [Routing number](https://en.wikipedia.org/wiki/Routing_number) | routing\_number | | eft | EFT data object | | |    accountNumber | The account number | account\_number | |    institutionNumber | The account institution number | institution\_number | |    branchNumber | The institution branch number | transit\_number | | international | International data object | | |    iban | The International Bank Account Number (IBAN) for the financial account | *None* | |    bic | The Bank Identifier Code (BIC) for the financial account | *None* | | bacs | The BACS data object | | |    accountNumber | BACS account number | *None* | |    sortCode | BACS sort code | *None* | # MX Investment Holdings Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/mx/mx-investment-holdings-mapping This data comes from MX's [/users/\{user\_guid}/members/\{member\_guid}/holdings](https://docs.mx.com/api-reference/platform-api/reference/list-holdings-by-member) endpoint and is returned from Meld's [/bank-linking/investments/holdings](/api-reference/bank-linking/investments/bank-linking-investment-holdings-search) endpoint. Example: an MX holding with `symbol: "AAPL"`, `shares: 10`, `market_value: 1850.50`, `cost_basis: 180.00`, `currency_code: "USD"` maps to Meld `symbol: "AAPL"`, `quantity: 10`, `currentValue: 1850.50`, `costBasis: 180.00`, `currencyCode: "USD"`. MX does not return holding `description` or `isin`. Those fields will be `null` in the Meld response when sourced from MX. | Meld Field | Description | Mx Response Field | | :----------- | :------------------------------------------------------------------------- | :--------------------- | | symbol | The symbol of the security | symbol | | quantity | The number of shares of the security | shares | | currentValue | The total current value of the holding | market\_value | | costBasis | The purchase price of the holding, per share | cost\_basis | | currencyCode | The ISO currency code that was used to purchase the holding | currency\_code | | updatedAt | The last time the details of this holding were updated | updated\_at | | description | A description of the holding | **Not Available** | | closePrice | The price of the security at last market close | shares / market\_value | | isin | The global ISO number for an individual security. | **Not Available** | | cusip | A shortened version of the isin used for North American securities. | cusip | | type | The type of holding (ex: stock, etf). This is normalized across providers. | holding\_type | # MX Investment Transactions Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/mx/mx-investment-transactions-mapping This data comes from MX's [/users/\{user\_guid} /accounts/\{account\_guid}/transactions](https://docs.mx.com/api-reference/platform-api/reference/list-transactions-by-account) endpoint and is returned from Meld's [/bank-linking/investments/transactions](/api-reference/bank-linking/investments/bank-linking-investment-transactions-search) endpoint. Example: an MX investment transaction with `account.balance: 5200.10`, `account.guid: "ACT-123"`, `amount: 925.25`, `currency_code: "USD"`, `description: "BUY AAPL"`, `transacted_at: "2024-01-15"`, `posted_at: "2024-01-15"`, `category: "buy"` maps to Meld `cashBalance: 5200.10`, `accountId: "ACT-123"`, `amount: 925.25`, `currency: "USD"`, `description: "BUY AAPL"`, `transactionDate: "2024-01-15"`, `postedDate: "2024-01-15"`, `type: "buy"`. MX does not return per-security details on investment transactions. `symbol`, `quantity`, and `costBasis` are not populated when sourced from MX. | Meld Field | Description | Mx Response Field | | :-------------- | :---------------------------------------------------------- | :---------------- | | symbol | The symbol of the security | **Not available** | | quantity | The number of shares of the security | **Not available** | | costBasis | The purchase price of the holding, per share | **Not available** | | cashBalance | The cash balance of the account after the transaction | account.balance | | accountId | The Id of the financial account | account.guid | | amount | The total currency involved in the transaction | amount | | currency | The ISO currency code that was used to purchase the holding | currency\_code | | description | A description of the transaction | description | | status | The status of the transaction | status | | transactionDate | The date the transaction was initiated | transacted\_at | | postedDate | The date the transaction was finalized | posted\_at | | type | The investment transaction's type | category | # MX Owners Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/mx/mx-owners-mapping This data comes from MX's [/users/`{user_guid}` /members/`{member_guid}` /account\_owners](https://docs.mx.com/api#identification_identity_list_account_owners) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example: MX `address: "2982 Frances Vineyard"`, `city: "San Francisco"`, `state: "CA"`, `postal_code: "94114"`, `country: "US"` maps to Meld `addresses.data.street`, `addresses.data.city`, `addresses.data.region`, `addresses.data.postalCode`, `addresses.data.country`. MX does not indicate which contact item is primary. Meld defaults the `primary` field to `UNKNOWN` for addresses, emails, and phone numbers sourced from MX.
Meld Field Description MX Response Field
addresses The address(es) associated with this owner
   data The address data object
      street The street and residence number address
      city The city city
      region The region/state state
      postalCode The postal or zip code postal\_code
      country The ISO 3166-1 alpha-2 country code country
   primary Indicates if this is the owner's primary residence Does not provide.
Meld will\ default\ to `UNKNOWN`
emails The email(s) associated with this owner
   data The email address email
   primary Indicates if this is the owner's primary email Does not provide.
Meld will\ default\ to `UNKNOWN`
names The name(s) of this owner owner\_name
phoneNumbers The phone number(s) associated with this owner
   data The phone number phone
   primary Indicates if this is the owner's primary phone number Does not provide.
Meld will\ default\ to `UNKNOWN`
# MX to Meld Overall Mappings Source: https://docs.meld.io/docs/bank-linking/partner-details/mx/mx-to-meld-mappings You can always compare the raw responses from MX in the `serviceProviderDetails` section of a financial account. * [Financial Account Base Fields](#financial-account-base-fields) * [Balances](/docs/bank-linking/partner-details/mx/mx-balances-mapping) * [Identifiers](/docs/bank-linking/partner-details/mx/mx-identifiers-mapping) * [Owners](/docs/bank-linking/partner-details/mx/mx-owners-mapping) * [Transactions](/docs/bank-linking/partner-details/mx/mx-transactions-mapping) * [Investment Holdings](/docs/bank-linking/partner-details/mx/mx-investment-holdings-mapping) * [Investment Transactions](/docs/bank-linking/partner-details/mx/mx-investment-transactions-mapping) * [Normalized Account Types and Subtypes](#normalized-account-types-and-subtypes) ## Financial Account Base Fields This data comes from MX's [/users/\{user\_guid}/accounts](https://docs.mx.com/api#core_resources_accounts_list_accounts) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint.
Meld Field Description MX Response Field
name The account name name
truncatedAccount\ Number The last 4 digits of the account number accountNumber
status The real-time status of the account `INACTIVE` if MX fields\`\`\` is\_closed = TRUE \`\`\`or `is_hidden = TRUE`; Otherwise `ACTIVE`
type The type of the account. Mapped to a Meld standardized type type
subtype The subtype of the account. Mapped to a Meld standardized subtype subtype
## Normalized Account Types and Subtypes # MX Transactions Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/mx/mx-transactions-mapping This data comes from MX's [/users/\{user\_guid} /accounts/\{account\_guid}/transactions](https://docs.mx.com/api-reference/platform-api/reference/list-transactions-by-account) endpoint and is returned from Meld's [/bank-linking/transactions](/api-reference/bank-linking/transactions/bank-linking-transactions-search) endpoint. Example: an MX transaction with `amount: 89.40`, `currency_code: "USD"`, `original_description: "Uber 063015 SF**POOL**"`, `status: "POSTED"`, `transacted_at: "2024-01-15"`, `posted_at: "2024-01-15"` maps to Meld `amount: 89.40`, `currency: "USD"`, `description: "Uber 063015 SF**POOL**"`, `status: "POSTED"`, `transactionDate: "2024-01-15"`, `postedDate: "2024-01-15"`. | Meld Field | Description | MX Response Field | | :-------------- | :------------------------------------------- | :-------------------- | | amount | The amount of the transaction | amount | | currency | The currency used in the transaction | currency\_code | | description | The transaction's description | original\_description | | status | The status of the transaction | status | | transactionDate | The date and time the transaction was made | transacted\_at | | postedDate | The date and time the transaction was posted | posted\_at | # Plaid Partner Details Source: https://docs.meld.io/docs/bank-linking/partner-details/plaid/index [Plaid](https://plaid.com/) is a bank linking aggregator that Meld integrates with to power account aggregation in the United States and Canada. Through Plaid, Meld supports the following products: account base data, balances, identifiers (ACH/EFT/BACS/international), owners (identity), transactions, investment holdings, and investment transactions. This page covers supported countries, configuration settings, and Plaid-specific behavior. For field-by-field mappings, see the per-product mapping pages linked from [Plaid to Meld Overall Mappings](/docs/bank-linking/partner-details/plaid/plaid-to-meld-mappings). Do not attempt to set up webhooks from Plaid to Meld. Meld will pass Plaid the correct webhook URL when initiating the Plaid widget (in any environment). ## Supported Countries Meld supports Plaid institutions in the United States (US) and Canada (CA). ## Configuration Settings If you have your own [Plaid](https://plaid.com/) account that you wish to integrate with Meld, you will need to provide the following credentials securely on the Meld Integrations Tab on the dashboard: * *Client ID* * *Client Secret* In addition, you will need to complete Plaid's OAuth Registration form in order to enable OAuth institutions. ![](https://files.readme.io/02425af-image.png) ## Product Initialization Unlike the other service providers, Plaid will filter out any accounts belonging to the customer that do not support all of the products requested, thus limiting which accounts the customer can select for aggregation within Plaid's widget flow. In order to normalize account selection behavior across service providers and reduce customer abandonment due to missing accounts, Meld aims to ensure the largest breadth of accounts is made available as possible. To do so, Meld uses a subset of the products supplied in the [/connect/start](/api-reference/bank-linking/connect/bank-linking-connect-create) request that optimizes for account availability when initializing the connection with Plaid. This means that there may be financial accounts belonging to the connection that don't support every product requested, but this can be expected and replicates the behavior exhibited by all of the other service providers. There are several factors Meld takes into consideration when determining which products requested in the `/connect/start` request will be included in the minimal subset provided to Plaid. This determination is made as follows: 1. The products supported by the widest variety of account types take precedence for inclusion in the subset, and those that aren't supported by many account types are typically excluded. For example,`TRANSACTIONS` is supported by nearly all account types, so it is always provided to Plaid if it is one of the requested products in `/connect/start`. On the other hand, `IDENTIFIERS` (account/routing numbers) is only supported by checking and savings accounts, and thus will not be included in the subset (unless it is the only product requested). 2. Products deemed distinctive enough that their inclusion alone within the `/connect/start` request likely implies their importance to the applications that request them will take precedence for inclusion in the subset. For example, the investment type products (`INVESTMENT_TRANSACTIONS` and `INVESTMENT_HOLDINGS`) are specific enough that when requested, they are most likely critical to an application's use-case, and filtering out account types that don't support investments would be desirable. On the other hand, `OWNERS` is typically a supplementary product for most applications and typically won't be included in the subset. It's nice to have if available, but not important enough that it warrants filtering out accounts that don't have this information. **Note:** This minimizing of the product set *does not* imply that the products not included in the subset Meld provides to Plaid will never be aggregated, but rather that the omitted products are not being used to restrict which accounts the customer can select in Plaid's widget. Meld will still attempt to load all the requested products after connection completion, there is just no guarantee that every linked account supports all of them. You can read more about how Plaid handles product initialization on their own documentation [here](https://plaid.com/docs/link/initializing-products/#required-if-supported-products). ## Special considerations * You must complete several Plaid onboarding steps prior to integrating Plaid with Meld. These steps can be completed on the Plaid dashboard or by reaching out to your account manager. The steps are as follows: * Complete the [Security Questionnaire ](https://dashboard.plaid.com/overview/questionnaire-start)form * Complete the OAuth registration. See [here](https://plaid.com/docs/link/oauth/#complete-the-registration-requirements). * Enable Plaid's `transaction_refresh` product if triggering real-time refreshes is desired. Plaid will still auto-refresh transactions daily, but if forcing refreshes is applicable to your use case, then this is an additional product that must be enabled. You can do so by submitting a [product request form](https://dashboard.plaid.com/overview/request-products) on the plaid dashboard * Plaid is the only provider for which the user must select their institution again in their widget even if already selected previously in the Meld picker * If transactions is a specified product, historical transactions will be loaded by default In Plaid sandbox, use the test credentials `user_good` / `pass_good` to link mock institutions. Production data and behavior may differ — particularly for OAuth institutions, which require full registration before use. # Plaid Balances Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/plaid/plaid-balances-mapping This data comes from Plaid's [/accounts/balance/get](https://plaid.com/docs/api/accounts/) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example: a Plaid `accounts.balances.iso_currency_code` of `"USD"` maps to Meld `currency: "USD"`. A Plaid `accounts.balances.current` of `1203.45` maps to Meld `currentAmount: 1203.45`.
Meld Field Description Plaid Response Field
currency The currency of the account balance accounts.balances\ .iso\_currency\_code
currentAmount The current amount in the account accounts.balances.current
availableAmount The available amount in the account accounts.balances.available
updatedAt The last time balances were updated accounts.balances\ .last\_updated\_datetime
# Plaid Identifiers Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/plaid/plaid-identifiers-mapping This data comes from Plaid's [/auth/get](https://plaid.com/docs/api/products/auth/#auth-get-response-numbers-ach-account) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example: Plaid `numbers.ach.account: "1111222233330000"` and `numbers.ach.routing: "011401533"` map to Meld `ach.accountNumber` and `ach.routingNumber` respectively. | Meld Field | Description | Plaid Response Field | | :------------------- | :--------------------------------------------------------------------------------------------------- | :------------------------- | | ach | ACH data object | | |    accountNumber | The account number | numbers.ach.account | |    routingNumber | The account routing number. Reference [Routing number](https://en.wikipedia.org/wiki/Routing_number) | numbers.ach.routing | | eft | EFT data object | | |    accountNumber | The account number | numbers.eft.account | |    institutionNumber | The account institution number | numbers.eft.institution | |    branchNumber | The institution branch number | numbers.eft.branch | | international | International data object | | |    iban | The International Bank Account Number (IBAN) for the financial account | numbers.international.iban | |    bic | The Bank Identifier Code (BIC) for the financial account | numbers.international.bic | | bacs | The BACS data object | | |    accountNumber | BACS account number | numbers.bacs.account | |    sortCode | BACS sort code | numbers.bacs.sort\_code |
# Plaid Investment Holdings Mappings Source: https://docs.meld.io/docs/bank-linking/partner-details/plaid/plaid-investment-holdings-mappings This data comes from Plaid's [/investments/holdings/get](https://plaid.com/docs/api/products/investments/#investmentsholdingsget) endpoint and is returned from Meld's [/bank-linking/investments/holdings](/api-reference/bank-linking/investments/bank-linking-investment-holdings-search) endpoint. Example: a Plaid holding with `Securities.ticker_symbol: "AAPL"`, `Holdings.quantity: 10`, `Holdings.institution_value: 1850.50`, `Securities.iso_currency_code: "USD"` maps to Meld `symbol: "AAPL"`, `quantity: 10`, `currentValue: 1850.50`, `currencyCode: "USD"`. | Meld Field | Description | Plaid Response Field | | :----------- | :------------------------------------------------------------------------- | :---------------------------------- | | symbol | The symbol of the security | Securities.ticker\_symbol | | quantity | The number of shares of the security | Holdings.quantity | | currentValue | The total current value of the holding | Holdings.institution\_value | | costBasis | The purchase price of the holding, per share | Holdings.cost\_basis | | currencyCode | The ISO currency code that was used to purchase the holding | Securities.iso\_currency\_code | | updatedAt | The last time the details of this holding were updated | Holdings.institution\_price\_as\_of | | description | A description of the holding | Securities.name | | closePrice | The price of the security at last market close | Securities.close\_price | | isin | The global ISO number for an individual security. | Securities.isin | | cusip | A shortened version of the isin used for North American securities. | Securities.cusip | | type | The type of holding (ex: stock, etf). This is normalized across providers. | Securities.type | # Plaid Investment Transactions Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/plaid/plaid-investment-transactions-mapping This data comes from Plaid's [/investments/transactions/get](https://plaid.com/docs/api/products/investments/#investmentstransactionsget) endpoint and is returned from Meld's [/bank-linking/investments/transactions](/api-reference/bank-linking/investments/bank-linking-investment-transactions-search) endpoint. Example: a Plaid investment transaction with `Securities.ticker_symbol: "AAPL"`, `Investment_transactions.quantity: 5`, `Investment_transactions.amount: 925.25`, `Investment_transactions.iso_currency_code: "USD"`, `Investment_transactions.date: "2024-01-15"`, `Investment_transactions.type: "buy"` maps to Meld `symbol: "AAPL"`, `quantity: 5`, `amount: 925.25`, `currency: "USD"`, `transactionDate: "2024-01-15"`, `type: "buy"`. | Meld Field | Description | Plaid Response Field | | :-------------- | :---------------------------------------------------------- | :------------------------------------------- | | symbol | The symbol of the security | Securities.ticker\_symbol | | quantity | The number of shares of the security | Investment\_transactions.quantity | | costBasis | The purchase price of the holding, per share | Investment\_transactions.price | | cashBalance | The cash balance of the account after the transaction | Accounts.balances.current | | accountId | The Id of the financial account | Accounts.account\_id | | amount | The total currency involved in the transaction | Investment\_transactions.amount | | currency | The ISO currency code that was used to purchase the holding | Investment\_transactions.iso\_currency\_code | | description | A description of the transaction | Securities.name | | status | The status of the transaction | **Not Available** (always set to POSTED) | | transactionDate | The date the transaction was initiated | Investment\_transactions.date | | postedDate | The date the transaction was finalized | **Not Available** | | type | The type of investment transaction | Investment\_transactions.type | # Plaid Owners Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/plaid/plaid-owners-mapping This data comes from Plaid's [/identity/get](https://plaid.com/docs/api/products/identity/#identity-get-response-accounts-owners-addresses-data-street) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example: Plaid `accounts.owners.addresses.data.street: "2982 Frances Vineyard"` maps to Meld `addresses.data.street`. Plaid `accounts.owners.emails.primary: true` maps to Meld `emails.primary: true`.
Meld Field Description Plaid Response Field
addresses The address(es) associated with this owner
   data The address data object
      street The street and residence number accounts.owners.addresses\ .data.street
      city The city accounts.owners.addresses\ .data.city
      region The region/state accounts.owners.addresses\ .data.region
      postalCode The postal or zip code accounts.owners.addresses\ .data.postal\_code
      country The ISO 3166-1 alpha-2 country code accounts.owners.addresses\ .data.country
   primary Indicates if this is the owner's primary residence accounts.owners.addresses\ .primary
emails The email(s) associated with this owner
   data The email address accounts.owners.emails.data
   primary Indicates if this is the owner's primary email accounts.owners.emails.primary
names The name(s) of this owner accounts.owners.names
phoneNumbers The phone number(s) associated with this owner
   data The phone number accounts.owners\ .phone\_numbers.data
   primary Indicates if this is the owner's primary phone number accounts.owners\ .phone\_numbers.primary
# Plaid to Meld Overall Mappings Source: https://docs.meld.io/docs/bank-linking/partner-details/plaid/plaid-to-meld-mappings You can always compare the raw responses from Plaid in the `serviceProviderDetails` section of a financial account. * [Financial Account Base Fields](#financial-account-base-fields) * [Balances](/docs/bank-linking/partner-details/plaid/plaid-balances-mapping) * [Identifiers](/docs/bank-linking/partner-details/plaid/plaid-identifiers-mapping) * [Owners](/docs/bank-linking/partner-details/plaid/plaid-owners-mapping) * [Transactions](/docs/bank-linking/partner-details/plaid/plaid-transactions-mapping) * [Investment Holdings](/docs/bank-linking/partner-details/plaid/plaid-investment-holdings-mappings) * [Investment Transactions](/docs/bank-linking/partner-details/plaid/plaid-investment-transactions-mapping) * [Normalized Account Types and Subtypes](#normalized-account-types-and-subtypes) ## Financial Account Base Fields This data comes from Plaid's [/accounts/get](https://plaid.com/docs/api/accounts/#accounts-get-response-accounts-name) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. | Meld Field | Description | Plaid Response Field | | :--------------------- | :---------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------- | | name | The account name | accounts.name | | truncatedAccountNumber | The last 4 digits of the account number | accounts.mask | | status | The real-time status of the account | Due to Plaid only returning active bank accounts, it does not have this field.
Meld will populate the status as `ACTIVE` | | type | The type of the account. Mapped to a Meld standardized type | accounts.type | | subtype | The subtype of the account. Mapped to a Meld standardized subtype | accounts.subtype | ## Normalized Account Types and Subtypes # Plaid Transactions Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/plaid/plaid-transactions-mapping This data comes from Plaid's [/transactions/get](https://plaid.com/docs/api/products/transactions/) endpoint and is returned from Meld's [/bank-linking/transactions](/api-reference/bank-linking/transactions/bank-linking-transactions-search) endpoint. Example: a Plaid transaction with `amount: 89.40`, `iso_currency_code: "USD"`, `name: "Uber 063015 SF**POOL**"`, `pending: false`, `date: "2024-01-15"` maps to Meld `amount: 89.40`, `currency: "USD"`, `description: "Uber 063015 SF**POOL**"`, `status: "POSTED"`, `postedDate: "2024-01-15"`.
Meld Field Description Plaid Response Field
amount The amount of the transaction amount
currency The currency used in the transaction iso\_currency\_code unofficial\_currency\_code (if iso\_currency\_code is null)
description The transaction's description name
status The status of the transaction pending
* If returns `True`, Meld will populate the value as `PENDING`
* If returns `False`, Meld will populate the value as `POSTED`
transactionDate The date and time the transaction was made * For transactions with `PENDING` status: `date`
* For transactions with `POSTED` status: `authorizedDate`
* If the authorized date returns `null`, Meld will use the data from `date`
postedDate The date and time the transaction was posted * For transactions with `PENDING` status, it will return `null`
* For transactions with `POSTED` status: `date`
# Salt Edge Partners - Partner Details Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge-partners/index Salt Edge Partners is Salt Edge's PSD2-compliant open banking product, used for EEA and UK regulated bank connections. Meld supports Salt Edge Partners for Balances, Identifiers, Owners, and Transactions. **Salt Edge Partners vs Salt Edge** — Meld integrates with two distinct Salt Edge products: * **Salt Edge Partners** (this page) uses the PSD2-compliant Partners API ([/api/partners/v1](https://docs.saltedge.com/partners/v1/)) and is the correct choice for EEA-regulated open banking connections. * **[Salt Edge](../salt-edge)** uses Salt Edge's global Account Information API ([/api/v5](https://docs.saltedge.com/account_information/v5/)) and covers a wider set of non-EEA countries. The two products require separate credentials and use separate endpoints. If you have both, double-check which set you are configuring. ## Supported Countries Meld supports Salt Edge Partners institutions in Argentina (AR), Australia (AU), Austria (AT), Belarus (BY), Belgium (BE), Brazil (BR), Bulgaria (BG), Croatia (HR), Cyprus (CY), Czechia (CZ), Denmark (DK), Estonia (EE), Finland (FI), France (FR), Germany (DE), Greece (GR), Hungary (HU), Iceland (IS), Ireland (IE), Italy (IT), Latvia (LV), Liechtenstein (LI), Lithuania (LT), Luxembourg (LU), Malta (MT), Netherlands (NL), Norway (NO), Poland (PL), Portugal (PT), Romania (RO), Slovakia (SK), Slovenia (SI), Spain (ES), Sweden (SE), and the United Kingdom (UK). ## Configuration Settings If you have your own [Salt Edge Partners](https://docs.saltedge.com/partners/v1/) account that you wish to integrate with Meld, you will need to provide the following credentials securely on the Meld Integrations Tab on the dashboard: * *App ID* * *Secret* * *Signature Key* ![](https://files.readme.io/8d1d6a1-image.png) If you have both a Salt Edge and Salt Edge Partners account, the keys are different. Verify you are entering the correct credentials for each integration. ## Salt Edge Partners Webhooks Salt Edge Partners needs several webhook urls, which can be seen in the Meld dashboard: ![](https://files.readme.io/472c5e9-image.png) All of the urls will start with `/webhook/saltedgepartners//` but at the end will have a different event, such as `success`, `fail`, `destroy`, `notify`, `interactive`, or `service`. Here is how they would look in the Salt Edge Partners dashboard: ![](https://files.readme.io/276f837-image.png) ## Special considerations Salt Edge Partners does not support forced refreshes — only daily automatic refreshes. If you need on-demand refresh behavior, use a different provider for the same institution where available. Meld loads the last 90 days of transaction data by default for Salt Edge Partners connections (if transactions is a requested product). # Salt Edge Partners Balance Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge-partners/salt-edge-partners-balance-mapping This data comes from Salt Edge Partners' [api/partners/v1/accounts](https://docs.saltedge.com/partners/v1/#accounts-attributes) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example values: `currency: "EUR"`, `currentAmount: 2410.55`, `availableAmount: 2380.00`, `updatedAt: "2024-08-12T14:33:00Z"`. | Meld Field | Description | SaltEdge Partners Response Field | | :-------------- | :---------------------------------- | :------------------------------- | | currency | The currency of the account balance | currency\_code | | currentAmount | The current amount in the account | balance | | availableAmount | The available amount in the account | extra.available\_amount | | updatedAt | The last time balances were updated | updated\_at | # Salt Edge Partners Identifiers Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge-partners/salt-edge-partners-identifiers-mapping This data comes from Salt Edge Partners' [api/partners/v1/accounts](https://docs.saltedge.com/partners/v1/#accounts-extra) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example values: `international.iban: "DE89370400440532013000"`, `international.bic: "COBADEFFXXX"`, `bacs.accountNumber: "31926819"`, `bacs.sortCode: "601613"`. Salt Edge Partners does not provide US ACH (`ach.*`) or Canadian EFT (`eft.*`) identifiers. Identifier coverage varies per institution within the `extra` object. | Meld Field | Description | SaltEdge Partners Response Field | | :------------------- | :--------------------------------------------------------------------------------------------------- | :------------------------------- | | ach | ACH data object | | |    accountNumber | The account number | *None* | |    routingNumber | The account routing number. Reference [Routing number](https://en.wikipedia.org/wiki/Routing_number) | *None* | | eft | EFT data object | | |    accountNumber | The account number | *None* | |    institutionNumber | The account institution number | *None* | |    branchNumber | The institution branch number | *None* | | international | International data object | | |    iban | The International Bank Account Number (IBAN) for the financial account | extra.iban | |    bic | The Bank Identifier Code (BIC) for the financial account | extra.swift | | bacs | The BACS data object | | |    accountNumber | BACS account number | extra.account\_number | |    sortCode | BACS sort code | extra.sort\_code |
# Salt Edge Partners Owners Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge-partners/salt-edge-partners-owners-mapping This data comes from Salt Edge Partners' [api/partners/v1/holder\_info](https://docs.saltedge.com/partners/v1/#holder_info-show) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example values: `names: ["Jane Doe"]`, `addresses.data: { street: "10 Downing St", city: "London", region: "Greater London", postalCode: "SW1A 2AA", country: "GB" }`, `emails.data: ["jane.doe@example.com"]`. Salt Edge Partners does not indicate whether an address, email, or phone number is the owner's primary record. Meld defaults `addresses.primary`, `emails.primary`, and `phoneNumbers.primary` to `UNKNOWN`. | Meld Field | Description | SaltEdge Partners Response Field | | :--------------- | :---------------------------------------------------- | :---------------------------------------------------- | | addresses | The address(es) associated with this owner | addresses | |    data | The address data object | | |       street | The street and residence number | street | |       city | The city | city | |       region | The region/state | state | |       postalCode | The postal or zip code | post\_code | |       country | The ISO 3166-1 alpha-2 country code | country\_code | |    primary | Indicates if this is the owner's primary residence | Does not provide.
Meld will default to `UNKNOWN` | | emails | The email(s) associated with this owner | emails | |    data | The email address | | |    primary | Indicates if this is the owner's primary email | Does not provide.
Meld will default to `UNKNOWN` | | names | The name(s) of this owner | names | | phoneNumbers | The phone number(s) associated with this owner | phone\_numbers | |    data | The phone number | | |    primary | Indicates if this is the owner's primary phone number | Does not provide.
Meld will default to `UNKNOWN` | # Salt Edge Partners to Meld Overall Mappings Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge-partners/salt-edge-partners-to-meld-mappings You can always compare the raw responses from Salt Edge Partners in the `serviceProviderDetails` section of a financial account. [Financial Account Base Fields](#financial-account-base-fields) [Balances](./salt-edge-partners-balance-mapping) [Identifiers](./salt-edge-partners-identifiers-mapping) [Owners](./salt-edge-partners-owners-mapping) [Transactions](./salt-edge-partners-transactions-mapping) [Normalized Account Types and Subtypes](#normalized-account-types-and-subtypes) ## Financial Account Base Fields This data comes from SaltEdge Partners [api/partners/v1/accounts](https://docs.saltedge.com/partners/v1/#accounts-list) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint.
Meld Field Description SaltEdge Partners Response Field
name The account name name
truncatedAccountNumber The last 4 digits of the account number extra.account\_number
If extra.account\_number is null then:
extra.iban
If extra.iban is null then:
extra.bban
If extra.bban is null then:
extra.cards
status The real-time status of the account extra.status
Possible values:
* `active`
* `inactive`
* `unauthorized`
If null: `active`
type The type of the account. Mapped to a Meld standardized type nature
subtype The subtype of the account. Mapped to a Meld standardized subtype None. Meld subtype determined from SaltEdge Partners type.
## Normalized Account Types and Subtypes # Salt Edge Partners Transactions Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge-partners/salt-edge-partners-transactions-mapping This data comes from Salt Edge Partners' [api/partners/v1/transactions](https://docs.saltedge.com/partners/v1/#transactions-list) endpoint and is returned from Meld's [/bank-linking/transactions](/api-reference/bank-linking/transactions/bank-linking-transactions-search) endpoint. Example values: `amount: -22.40`, `currency: "EUR"`, `status: "POSTED"`, `transactionDate: "2024-08-10"`, `postedDate: "2024-08-11"`.
Meld Field Description SaltEdge Partners Response Field
amount The amount of the transaction amount
currency The currency used in the transaction currency\_code
payee The recipient of the transaction description
status The status of the transaction status
transactionDate The date and time the transaction was made made\_on
postedDate The date and time the transaction was posted extra.posting\_date\ *If extra.posting\_date is null then:*\ made\_on
# Salt Edge Partner Details Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge/index Salt Edge is a global open banking provider with broad coverage across the Americas, EMEA, and APAC. Meld supports Salt Edge for Balances, Identifiers, Owners, and Transactions. **Salt Edge vs Salt Edge Partners** — Meld integrates with two distinct Salt Edge products: * **Salt Edge** (this page) uses Salt Edge's global Account Information API ([/api/v5](https://docs.saltedge.com/account_information/v5/)) and covers a wider set of non-EEA countries. * **[Salt Edge Partners](../salt-edge-partners)** uses the PSD2-compliant Partners API ([/api/partners/v1](https://docs.saltedge.com/partners/v1/)) and is the correct choice for EEA-regulated open banking connections. The two products require separate credentials and use separate endpoints. If you have both, double-check which set you are configuring. ## Supported Countries Meld supports Salt Edge institutions in the United States (US), Argentina (AR), Australia (AU), Austria (AT), Belarus (BY), Brazil (BR), Bulgaria (BG), Canada (CA), Chile (CL), Dominican Republic (DR), Ecuador (EC), Egypt (EG), France (FR), Germany (DE), Hong Kong (HK), Hungary (HU), India (IN), Ireland (IE), Israel (IL), Italy (IT), Malaysia (MY), Mexico (MX), Netherlands (NL), New Zealand (NZ), North Macedonia (MK), Pakistan (PJ), Philippines (PH), Poland (PL), Republic of Moldova (MD), Romania (RO), Saudi Arabia (SA), Singapore (SG), Slovakia (SK), South Africa (ZA), Spain (ES), Switzerland (CH), Thailand (TH), Turkey (TR), Ukraine (UA), United Arab Emirates (AE), and United Kingdom (GB). ## Configuration Settings If you have your own [Salt Edge](https://www.saltedge.com/) (Open Banking) account that you wish to integrate with Meld, you will need to provide the following credentials securely on the Meld Integrations Tab on the dashboard: * *App ID* * *Secret* * *Signature Key* ![](https://files.readme.io/555c2a2-image.png) ## Salt Edge Webhooks Salt Edge needs several webhook urls, which can be seen in the Meld dashboard: ![](https://files.readme.io/936ae11-image.png) All of the urls will start with \/webhook/saltedge/`{accountId}`/ but at the end will have a different event, such as `success`, `fail`, `destroy`, `notify`, `interactive`, or `service`. Here is how they would look in the Salt Edge dashboard: ![](https://files.readme.io/b54472a-image.png) ## Special Considerations Meld loads the last 90 days of transaction data by default for Salt Edge connections (if transactions is a requested product). Salt Edge may require a request signature on certain calls. See [Salt Edge signature documentation](https://docs.saltedge.com/general/v5/#signature) for details. # Salt Edge Balances Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge/salt-edge-balances-mapping This data comes from Salt Edge's [/api/v5/accounts](https://docs.saltedge.com/account_information/v5/#accounts-attributes) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example values: `currency: "EUR"`, `currentAmount: 2410.55`, `availableAmount: 2380.00`, `updatedAt: "2024-08-12T14:33:00Z"`. | Meld Field | Description | SaltEdge Response Field | | :-------------- | :---------------------------------- | :---------------------- | | currency | The currency of the account balance | currency\_code | | currentAmount | The current amount in the account | balance | | availableAmount | The available amount in the account | extra.available\_amount | | updatedAt | The last time balances were updated | updated\_at | # Salt Edge Identifiers Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge/salt-edge-identifiers-mapping This data comes from Salt Edge's [/api/v5/accounts](https://docs.saltedge.com/account_information/v5/#accounts-extra) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example values: `international.iban: "GB29NWBK60161331926819"`, `international.bic: "NWBKGB2L"`, `bacs.accountNumber: "31926819"`, `bacs.sortCode: "601613"`. Salt Edge does not provide US ACH (`ach.*`) or Canadian EFT (`eft.*`) identifiers. Identifier coverage varies per institution within the `extra` object. | Meld Field | Description | SaltEdge Response Field | | :------------------- | :--------------------------------------------------------------------------------------------------- | :---------------------- | | ach | ACH data object | | |    accountNumber | The account number | *None* | |    routingNumber | The account routing number. Reference [Routing number](https://en.wikipedia.org/wiki/Routing_number) | *None* | | eft | EFT data object | | |    accountNumber | The account number | *None* | |    institutionNumber | The account institution number | *None* | |    branchNumber | The institution branch number | *None* | | international | International data object | | |    iban | The International Bank Account Number (IBAN) for the financial account | extra.iban | |    bic | The Bank Identifier Code (BIC) for the financial account | extra.swift | | bacs | The BACS data object | | |    accountNumber | BACS account number | extra.account\_number | |    sortCode | BACS sort code | extra.sort\_code |
# Salt Edge Owners Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge/salt-edge-owners-mapping This data comes from Salt Edge's [/api/v5/holder\_info](https://docs.saltedge.com/account_information/v5/#holder_info-show) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example values: `names: ["Jane Doe"]`, `addresses.data: { street: "10 Downing St", city: "London", region: "Greater London", postalCode: "SW1A 2AA", country: "GB" }`, `emails.data: ["jane.doe@example.com"]`. Salt Edge does not indicate whether an address, email, or phone number is the owner's primary record. Meld defaults `addresses.primary`, `emails.primary`, and `phoneNumbers.primary` to `UNKNOWN`. | Meld Field | Description | SaltEdge Response Field | | :--------------- | :---------------------------------------------------- | :---------------------------------------------------- | | addresses | The address(es) associated with this owner | addresses | |    data | The address data object | | |       street | The street and residence number | street | |       city | The city | city | |       region | The region/state | state | |       postalCode | The postal or zip code | post\_code | |       country | The ISO 3166-1 alpha-2 country code | country\_code | |    primary | Indicates if this is the owner's primary residence | Does not provide.
Meld will default to `UNKNOWN` | | emails | The email(s) associated with this owner | emails | |    data | The email address | | |    primary | Indicates if this is the owner's primary email | Does not provide.
Meld will default to `UNKNOWN` | | names | The name(s) of this owner | names | | phoneNumbers | The phone number(s) associated with this owner | phone\_numbers | |    data | The phone number | | |    primary | Indicates if this is the owner's primary phone number | Does not provide.
Meld will default to `UNKNOWN` | # Salt Edge to Meld Overall Mappings Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge/salt-edge-to-meld-mappings You can always compare the raw responses from Salt Edge in the `serviceProviderDetails` section of a financial account. [Financial Account Base Fields](#financial-account-base-fields) [Balances](./salt-edge-balances-mapping) [Identifiers](./salt-edge-identifiers-mapping) [Owners](./salt-edge-owners-mapping) [Transactions](./salt-edge-transactions-mapping) [Normalized Account Types and Subtypes](#normalized-account-types-and-subtypes) ## Financial Account Base Fields This data comes from SaltEdge's [/api/v5/accounts](https://docs.saltedge.com/account_information/v5/#accounts-attributes) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint.
Meld Field Description SaltEdge Response Field
name The account name name
truncatedAccount\ Number The last 4 digits of the account number extra.account\_number\ If extra.account\_number is null then:\*\ extra.iban\ *If extra.iban is null then:*\ extra.bban\ *If extra.bban is null then:*\ extra.cards
status The real-time status of the account extra.status\
Possible values:
- `active`
- `inactive`
- `unauthorized` * If null:\_ `active`
type The type of the account. Mapped to a Meld standardized type nature
subtype The subtype of the account. Mapped to a Meld standardized subtype * None\_. Meld subtype\ determined from SaltEdge type.
## Normalized Account Types and Subtypes # Salt Edge Transactions Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge/salt-edge-transactions-mapping This data comes from Salt Edge's [/api/v5/transactions](https://docs.saltedge.com/account_information/v5/#transactions-list) endpoint and is returned from Meld's [/bank-linking/transactions](/api-reference/bank-linking/transactions/bank-linking-transactions-search) endpoint. Example values: `amount: -22.40`, `currency: "EUR"`, `status: "POSTED"`, `transactionDate: "2024-08-10"`, `postedDate: "2024-08-11"`.
Meld Field Description SaltEdge Response Field
amount The amount of the transaction amount
currency The currency used in the transaction currency\_code
payee The recipient of the transaction description
status The status of the transaction status
transactionDate The date and time the transaction was made made\_on
postedDate The date and time the transaction was posted extra.posting\_date\ *If extra.posting\_date is null then:*\ made\_on
# Bank Linking Sandbox Testing Guide Source: https://docs.meld.io/docs/bank-linking/testing-and-debugging/bank-linking-sandbox-testing-guide # Testing Information This page lists the test institutions and credentials each bank linking service provider exposes in their sandbox environment. Use it to validate end-to-end connection flows in your integration before pointing at production. Service providers have sandboxes that you can use to make test connections and fetch test data. Each service provider has its own set of test institutions. Some service provider sandboxes have limitations, for example Finicity's does not return investment data. Testing the repair flow is not possible in sandbox. Repair flows can only be exercised against production connections that have entered a broken state. You can also find current test credentials in your Meld dashboard sandbox if a provider updates them. ## Plaid Plaid has a very comprehensive sandbox environment that can be used to test a lot of features. You can check out their [testing guide](https://plaid.com/docs/sandbox/test-credentials/), or see the test credentials below. | Service Provider | Test Institutions | OAuth? | Username | Password | Pin | | :--------------- | :---------------- | :--------------- | ----------- | :---------- | :---------------- | | Plaid | Any | some (ex. Chase) | `user_good` | `pass_good` | `credential_good` | You can select any institution in Plaid's sandbox and use those credentials to complete a connection. Most institutions will connect using OAuth with Plaid's fake bank behind the scenes, called Platypus Bank. Additionally, Plaid makes it very clear when you are in their sandbox by displaying "You are currently in Sandbox mode." at the bottom of their widget, which is not seen in their Production environment. ![](https://files.readme.io/dbf66c9-image.png) In addition to Sandbox and Production, Plaid also has a third environment, Development. Plaid's development environment has several restrictions, such as not supporting the `OWNERS` product (which Plaid calls Identities), and needing to connect with real bank credentials. As such, Meld recommends using Plaid's sandbox for testing, and Plaid's production when going live. ### Plaid Investments Testing Plaid's sandbox supports creating [custom users](https://plaid.com/docs/sandbox/user-custom/) with custom data that Plaid will return when you successfully connect. This can be useful when wanting to test a specific feature with fake data, such as Investments from Plaid. Simply create a user with a custom username and pass that username instead of `user_good` when creating a connection. ## Mesh Mesh's sandbox supports the same list of institutions as in production (with a few exceptions). What differentiates sandbox connections from production connections are the credentials entered. These test credentials are institution-dependent, but are provided as "hints" in the Mesh widget. The username/password combinations are typically either of the following: | Service Provider | Test Institution Type | Username | Password | | :--------------- | :----------------------------- | ------------- | :--------------- | | Mesh | Username/Password integrations | SandboxUser | SandboxPassword | | Mesh | API key integrations | SandboxApiKey | SandboxSecretKey | If there are any exceptions, they will still be displayed as "hints" in the Mesh widget. ### Mesh Investments Testing Mesh's sandbox supports testing investments with the above test credentials. ## Finicity Finicity does not support using the same customer ID for connecting to a test account and to a real bank account, so if you see the error "Existing Finicity customer is not a test customer so cannot connect with test institutions" while trying to make a connection, use a different customer ID. Only Finicity's production environment supports connecting to real bank accounts. Finicity has a set of passwords that return data from various types of financial accounts. You can check out their [testing guide](https://developer.mastercard.com/open-banking-us/documentation/test-the-apis/), or see the test credentials below. | Service Provider | Test Institutions | OAuth | Username | Password | Account Types | | :--------------- | :---------------------------------------------- | :---- | -------- | :----------- | :---------------------------------------------------------------------------------------------- | | Finicity | `FinBank Profiles -A` or `FinBank Profiles - B` | Yes | anything | `profile_02` | Savings, IRA, 401k, Credit Card | | Finicity | `FinBank Profiles -A` or `FinBank Profiles - B` | Yes | anything | `profile_03` | Checking, Personal Investment, 401K, Roth, Savings (Joint Account owners) | | Finicity | `FinBank Profiles -A` or `FinBank Profiles - B` | Yes | anything | `profile_04` | Checking, 403B, 529, Rollover, Mortgage | | Finicity | `FinBank Profiles -A` or `FinBank Profiles - B` | Yes | anything | `profile_05` | Checking, Investment, Stocks, UGMA, UTMA (Joint Account owners) | | Finicity | `FinBank Profiles -A` or `FinBank Profiles - B` | Yes | anything | `profile_06` | Checking, Retirement, KEOGH, 457, Credit Card | | Finicity | `FinBank Profiles -A` or `FinBank Profiles - B` | Yes | anything | `profile_07` | Checking, Stocks, CD, Investment Tax-Deferred, Employee Stock | | Finicity | `FinBank Profiles -A` or `FinBank Profiles - B` | Yes | anything | `profile_08` | Checking, Primary Savings, Money Market, 401A, Line of credit | | Finicity | `FinBank Profiles -A` or `FinBank Profiles - B` | Yes | anything | `profile_09` | Checking, Savings, Checking Failed Report. Errors returned in the report include 102, 103, 185. | Finicity also returns the same set of data via an OAuth flow. Before you can connect this way, you must first register for FinBank OAuth through your Finicity account. | Service Provider | Test Institutions | OAuth | Username | Password | Account Types | | :--------------- | :---------------- | :---- | ------------ | :----------- | :---------------------------------------------------------------------------------------------- | | Finicity | `FinBank OAuth` | No | `profile_02` | `profile_02` | Savings, IRA, 401k, Credit Card | | Finicity | `FinBank OAuth` | No | `profile_03` | `profile_03` | Checking, Personal Investment, 401K, Roth, Savings (Joint Account owners) | | Finicity | `FinBank OAuth` | No | `profile_04` | `profile_04` | Checking, 403B, 529, Rollover, Mortgage | | Finicity | `FinBank OAuth` | No | `profile_05` | `profile_05` | Checking, Investment, Stocks, UGMA, UTMA (Joint Account owners) | | Finicity | `FinBank OAuth` | No | `profile_06` | `profile_06` | Checking, Retirement, KEOGH, 457, Credit Card | | Finicity | `FinBank OAuth` | No | `profile_07` | `profile_07` | Checking, Stocks, CD, Investment Tax-Deferred, Employee Stock | | Finicity | `FinBank OAuth` | No | `profile_08` | `profile_08` | Checking, Primary Savings, Money Market, 401A, Line of credit | | Finicity | `FinBank OAuth` | No | `profile_09` | `profile_09` | Checking, Savings, Checking Failed Report. Errors returned in the report include 102, 103, 185. | ### Finicity Investments Testing Testing Finicity investments is very difficult using Finicity's test accounts due to the lack of test data available. Therefore Meld recommends using real investment accounts to test Finicity investments. Note that Finicity does enforce a third party screen before the bank's login page, as shown below: ![](https://files.readme.io/eb82585-image.png) Sometimes, Finicity's sandbox may connect very quickly and cause an error. In this case, after entering the login username and password, you may see a blank white screen. This only happens when connecting test accounts (not real bank accounts) and can be ignored. ## MX Before testing with MX, note that MX's sandbox supports connecting to both real and test bank accounts, although the number of real connections is very limited. If you wish to connect to a test account, make sure you select a test institution. MX has a very comprehensive sandbox environment that can be used to test a lot of features. You can check out their [testing guide](https://docs.mx.com/testing/guides/testing), or see the test credentials below. | Service Provider | Test Institutions | OAuth? | Username | Password | | :--------------- | :---------------------------------------------------------------------------------------- | :----- | -------- | :------- | | MX | MX Bank | No | `mxuser` | anything | | MX | MX Bank (OAuth) | Yes | `mxuser` | anything | | MX | [MXCU (OAuth)](https://docs.mx.com/resources/test-platform/mxcu/testing-oauth-with-mxcu/) | Yes | `mxuser` | anything | MX does finish their aggregation within their widget, so if you are connecting an account with a lot of transactions, the widget may stay open a little longer than you expect. ### MX Investments Testing MX does have some investment data that can be accessed with the above test credentials, but it's pretty spotty. Meld recommends testing MX Investments with real investment accounts. ## Akoya Akoya has several test accounts in their sandbox environment that can be used to test different data types. You can check out their [testing guide](https://docs.akoya.com/docs/mikomo), or see the test credentials below. The only test institution Akoya supports is `Mikomo`. Meld recommends testing with `mikomo_9` as some Akoya test accounts return data in a different format from what is documented, which can cause errors. | Service Provider | Test Institutions | Username | Password | Account Types | | :--------------- | :---------------- | ------------- | :------------ | :----------------------------------------------------------- | | Akoya | `Mikomo` | `mikomo_1` | `mikomo_1` | Investment (I, TODI) | | Akoya | `Mikomo` | `mikomo_2` | `mikomo_2` | Investment (HSA, I, TODI) | | Akoya | `Mikomo` | `mikomo_3` | `mikomo_3` | Investment (I, IRRL, TIC, IRAB, IRA, TODJ, ROTH, TODI, 401K) | | Akoya | `Mikomo` | `mikomo_5` | `mikomo_5` | Investment (J, HSA, ROTH) | | Akoya | `Mikomo` | `mikomo_6` | `mikomo_6` | Investment (HSA, TODI, IRA, IRRL, NONP, NRMA, 401K) | | Akoya | `Mikomo` | `mikomo_7` | `mikomo_7` | Checking, Commercial Loan, Credit Card, 401k, J | | Akoya | `Mikomo` | `mikomo_9` | `mikomo_9` | Checking | | Akoya | `Mikomo` | `mikomo_10` | `mikomo_10` | Checking, College Savings, Brokerage, CD, Savings | | Akoya | `Mikomo` | `mikomo_11` | `mikomo_11` | Checking | | Akoya | `Mikomo` | `mikomo_2023` | `mikomo_2023` | Checking, College Savings, Brokerage, CD, Savings | ### Akoya Investments Testing Akoya supports testing investments through certain test users, such as mikomo\_2023. However some fields that would appear with real investment data may be missing in the test data. ## Yodlee Yodlee has several test accounts in their sandbox environment that can be used to test different data types. You can check out their [testing guide](https://developer.yodlee.com/), or see the test credentials below. | Service Provider | Test Institutions | Provider Id | Username | Password | Account Types | MFA Response | | :--------------- | :-------------------- | :---------- | --------------------- | :------------ | :---------------------------------------------- | :---------------------- | | Yodlee | `DAG Site` | 16441 | `YodTest.site16441.1` | `site16441.1` | Checking, Savings | N/A | | Yodlee | `DAG Site` | 16441 | `YodTest.site16441.2` | `site16441.2` | Checking, Savings, Credit Card, Loan, Brokerage | N/A | | Yodlee | `DAG Site SecurityQA` | 16486 | `YodTest.site16486.1` | `site16486.1` | Security Question Login | `Texas` and `w3schools` | | Yodlee | `DAG Site Multilevel` | 16442 | `YodTest.site16442.1` | `site16442.1` | OTP Login | `123456` | ### Yodlee Investments Testing Yodlee does have some investment data that can be accessed with the above test credentials, but it's pretty spotty. Meld recommends testing Yodlee Investments with real investment accounts. ## Salt Edge Salt Edge does not have a testing guide, but does have a [guide](https://docs.saltedge.com/account_information/v5/#dynamic_registration) to follow before going live. Besides that, their test credentials can be found below: | Service Provider | Test Institutions | Username | Password | Pin | | :--------------- | :------------------------------------------------------------------------- | ---------- | :------- | :------ | | Salt Edge | Anything starting with `Fake Bank` except for `Fake Bank with Client Keys` | `username` | `secret` | `12345` | ## Salt Edge Partners Salt Edge Partners does not have a testing guide, but does have a [guide](https://docs.saltedge.com/partners/v1/#before_going_live) to follow before going live. Besides that, their test credentials can be found below: | Service Provider | Test Institutions | Username | Password | Pin | | :----------------- | :--------------------------- | ---------- | :------- | :------ | | Salt Edge Partners | `Fake Bank with Client Keys` | `username` | `secret` | `12345` | # Activity Log Source: https://docs.meld.io/docs/bank-linking/testing-and-debugging/debugging-activity-log This feature is in beta. The Activity Log is Meld's searchable audit trail of every request, webhook, and event that flows between your application, Meld, and the underlying service providers. Developers use it to trace what happened on a specific connection, replay webhook delivery, and pinpoint where in the pipeline something went wrong. The Activity Log is currently in beta. Behavior and search parameters may change. One powerful tool to help you debug potential issues or see the paper trail is Meld's [Activity Log](/api-reference/beta/activity-log/activity-log-search). The activity log registers all activities performed by you or performed by Meld on behalf of you and presents them in an easy to trace manner. Some of the major types of activities captured by the activity log are: 1. [Webhooks](#track-webhooks): 1. Webhooks that the provider sends Meld. 2. Webhooks that Meld sends you. 2. [API calls Meld makes to a service provider on your behalf and the response](#raw-service-provider-data). # How to Access the Activity Log You can interact with the Activity Log in two ways: * **Meld dashboard** — log in to your Meld dashboard and navigate to the Activity Log section to browse recent activity in the UI. * **API** — call the [Activity Log Search](/api-reference/beta/activity-log/activity-log-search) and [Activity Log Details](/api-reference/beta/activity-log/activity-log-details-get) endpoints to query programmatically. # How to Use the Activity Log 1. Start by hitting Meld's [Activity Log endpoint](/api-reference/beta/activity-log/activity-log-search) with whatever search parameters you would like. The date params (`start` and `end`) are required but all other params are optional. The activities are ordered chronologically with the most recent at the top, and every activity has a timestamp. You should typically filter using an `actions` and either a `connectionId` or `financialAccountId`. If you don't have either of those, then a `customerId` or `externalCustomerId` can work as well, but those searches will take longer. Examples of `actions` you should filter by are: | Action | Meaning | | :------------------------------------- | :-------------------------------------------------------------------------------------- | | `INBOUND_REQUEST` | An API call you made to Meld | | `OUTBOUND_REQUEST_SERVICE_PROVIDER` | An API call that Meld made to a service provider on your behalf, including the response | | `INBOUND_REQUEST_WEBHOOK` | A webhook sent from the service provider to Meld | | `OUTBOUND_REQUEST_WEBHOOK` | A webhook that Meld sent to you | | `INSTITUTION_CONNECTION_IMPORT_FAILED` | A notification that an import for a particular connection has failed | 2. Once you find a particular activity that you would like more information about, grab the key of the activity and head to Meld's [Activity Log Details endpoint](/api-reference/beta/activity-log/activity-log-details-get). Pass in the key as a query param and you will now see all the details for that particular activity. These details include the request and response headers, the request and response body, the response code, and more. You can also see the raw response Meld received from a particular service provider here. This can help debug if for example data that looks suspicious is due to Meld's transformation or is the exact data that Meld received from the provider. # Use Cases Here are some potential use cases of the activity log: ## Track Webhooks See which webhooks were sent for a particular connection from the provider to Meld as well as from Meld to you. This can help if you might've missed a webhook, or if you want to see the full reason a connection has an issue (which typically comes to Meld via webhook). ## Raw Service Provider Data See the raw data the provider sent Meld to evaluate if an issue with a particular connection is on Meld's side or the provider's side. ## Why a Connection is No Longer Active See why a connection moved from `ACTIVE` to another status. This often uses one of the two examples above to debug the root cause for why this happened. ## Why an Import Failed When you import connections over to Meld, you will receive webhooks for connections that were successfully imported. However, to see which connections failed to be imported, use the activity log and search using the `INSTITUTION_CONNECTION_IMPORT_FAILED` action, which will also include the Meld reason for the import failing. One trick is to find the action for the import failing then look at the details of the action that happened right before it which will likely have the service provider's error, if there is one. The activity log is a very powerful tool. Please make your requests as specific as possible by leveraging the query params, or there may be too much data to return or to be of use. # Testing and Debugging Source: https://docs.meld.io/docs/bank-linking/testing-and-debugging/index Developer tools for testing and debugging bank linking connections Meld provides two primary tools to help you validate connections before going live and diagnose issues after launch. * [Sandbox Testing Guide](/docs/bank-linking/testing-and-debugging/bank-linking-sandbox-testing-guide): test institutions, credentials, and tips for testing with Meld and each bank linking service provider in sandbox. * [Activity Log](/docs/bank-linking/testing-and-debugging/debugging-activity-log): search and review raw connection-level data exchanged between Meld and service providers. # Bank Linking Webhooks Source: https://docs.meld.io/docs/bank-linking/webhook-events-bank-linking This page lists every webhook event Meld sends over the lifetime of a bank linking connection. For each event you'll see when it fires and what your application should do on receipt. For the end-to-end flow and the order webhooks arrive in, see [When is your data available?](/docs/bank-linking/get-connection-data/when-is-your-data-available). Make sure your webhook endpoint is configured before triggering connections in production. See [Setting Up Webhooks](/docs/bank-linking/onboarding/step-5-setting-up-webhooks) for setup, authentication, and retry behavior. ## Attributes All webhook events have the same attributes. | Key | Type | Description | | :---------- | :------------- | :----------------------------------------------------------------------------------- | | `eventType` | String | Type of event | | `eventId` | String | Meld's unique identifier for the event | | `timestamp` | OffsetDateTime | The date and time the event was created | | `accountId` | String | Account id | | `profileId` | String | Id of the webhook profile responsible for this event to be sent | | `version` | Date | API version of the payload | | `payload` | Object | An object containing additional information of the event depending on the event type | # Connection Webhooks See the [API Reference](/api-reference/bank-linking) for the full payload schema of each connection webhook. ### `BANK_LINKING_CONNECTION_COMPLETED` **When it fires:** Sent when the customer completes the connection with their institution either in the initial connect flow or a reconnect flow. This does not imply that financial account data has completed aggregating or is ready to be fetched, just that the customer has completed the widget flow. **What to do:** Record that the customer finished the widget so you can update your UI. Wait for `BANK_LINKING_ACCOUNTS_UPDATED` before attempting to fetch product data. The `BANK_LINKING_CONNECTION_STATUS_CHANGE` webhook will be sent in tandem with this one (with the `newStatus` being `ACTIVE`). However, if a connection goes from a broken state to `ACTIVE` without the user needing to reconnect (i.e. sometimes connections may break due to temporary institution unavailability but are eventually resolved on their own), then only `BANK_LINKING_CONNECTION_STATUS_CHANGE` will be issued containing both the old status and the new `ACTIVE` status. This is because `BANK_LINKING_CONNECTION_COMPLETED` is reserved solely for when the customer completes the widget flow. ```json theme={null} { "eventType": "BANK_LINKING_CONNECTION_COMPLETED", "eventId": "NGoTSGJYpd3cLv1iyHWSw9", "timestamp": "2021-12-09T21:58:29.329186Z", "accountId": "QfdzpX3te1cDaviihXA4D5dA6ghnT3", "profileId": "W9kBgKB7afMxYoYuy3rJuZ", "version": "2021-10-28", "payload": { "requestId": "91d7fb355e319532b178acc8a7ae1a69", "customerId": "Qg56BnxskKokRV8rVo21Af3T4k6QKY", "externalCustomerId": "testCustomer1", "connectionId": "WQ4mBt3BEX2cmhCvSPyfTu", "institutionId": "Ntj3AwgdridJc2rtfeheku", "institutionName": "Chase", "serviceProviderDetails": { "serviceProvider": "PLAID", "serviceProviderConnectionId": "NXrZpLwd1eUQQqymvl9nSkkV8DpeLLUWoXdZx" } } } ``` ### `BANK_LINKING_CONNECTION_STATUS_CHANGE` **When it fires:** Sent whenever a connection changes status. This webhook essentially encompasses all of the other connection status related webhooks and will be sent alongside them. Any service provider errors that occur post-completion will trigger this webhook as well, either due to an attempted aggregation failing or if the service provider notifies Meld via their own webhooks that a connection is in a broken state. This can happen when fetching products following an initial connection, or during daily/forced refreshes of existing connections. **What to do:** This is the webhook to subscribe to for all connection-health tracking. Inspect `newStatus` and `newStatusReason` and route the customer to the [repair flow](/docs/bank-linking/manage-connection-status/repairing-connections) if the connection is now `RECONNECT_REQUIRED` or `CUSTOMER_ACTION_REQUIRED`. The `serviceProviderDetails` will provide additional information on what caused the connection to end up in such state. A list of potential reasons and actions to take for each of them can be found on the [Connection Statuses and Errors](/docs/bank-linking/get-connection-data/connection-statuses-and-errors) page. If a connection requires a reconnect, you can still request information (such as balances, transactions, etc.) about the connection and you will receive the data from the last time the connection was still operational. ```json theme={null} { "eventType": "BANK_LINKING_CONNECTION_STATUS_CHANGE", "eventId": "UhK4iqKP57FeLPgqBi8EUk", "timestamp": "2023-08-22T20:54:02.960285Z", "accountId": "W9ju7atKP5Jaf5RPweGeis", "profileId": "W9kBgKB7afMxYoYuy3rJuZ", "version": "2023-07-31", "payload": { "customerId": "WQ4KqLo3SRSPeTVjxxwo2o", "externalCustomerId": "20230822-67a", "connectionId": "WQ4KqLuBYYTYHi2YATXYdB", "institutionId": "W4AAAFPgUVB85QnQDu5xhU", "oldStatus": "ACTIVE", "oldStatusReason": null, "newStatus": "RECONNECT_REQUIRED", "newStatusReason": "LOGIN_REQUIRED", "activeDuplicateConnectionId": null, "serviceProviderDetails": { "environment": "sandbox", "error": { "error_type": "ITEM_ERROR", "error_code": "ITEM_LOGIN_REQUIRED", "error_message": "the login details of this item have changed (credentials, MFA, or required user action) and a user login is required to update this information. use Link's update mode to restore the item to a good state", "display_message": null, "request_id": null, "causes": null, "status": 400, "documentation_url": null, "suggested_action": null }, "item_id": "NXrZpLwd1eUQQqymvl9nSkkV8DpeLLUWoXdZx", "serviceProvider": "PLAID", "serviceProviderConnectionId": "NXrZpLwd1eUQQqymvl9nSkkV8DpeLLUWoXdZx", "webhook_code": "ERROR", "webhook_type": "ITEM" } } } ``` # Financial Account Webhooks See the [API Reference](/api-reference/bank-linking) for the full payload schema of each account webhook. ### `BANK_LINKING_ACCOUNTS_UPDATING` **When it fires:** Sent once the connect flow is completed and the accounts are in the process of aggregating. At this time, only basic account info is available. Also sent during subsequent daily/forced refreshes when the accounts are in the process of aggregating again. **What to do:** Treat this as a signal that aggregation has started — you can show a loading state but should not yet fetch product data. Wait for `BANK_LINKING_ACCOUNTS_UPDATED` before calling product endpoints. *Financial Account fields for this event type:* | Key | Type | Description | | :--------------------------- | :--------------- | :--------------------------------------------------------------------------------------------------------- | | `financialAccounts` | Array of objects | | |     `id` | String | Unique identifier for the financial account | |     `name` | String | If present, the name of the account as provided by the institution. | |     `truncatedAccountNumber` | String | If present, the last 4 digits of the account number, sometimes referred to as the account mask. | |     `newAccount` | Boolean | Indicates if the account was newly added or is an existing account getting updated (i.e. during a refresh) | ```json theme={null} { "eventType": "BANK_LINKING_ACCOUNTS_UPDATING", "eventId": "38oVQntsutXnfzn6fZ3mxW", "timestamp": "2022-09-06T17:42:49.703620Z", "accountId": "W9ju7atKP5Jaf5RPweGeis", "profileId": "W9kBgKB7afMxYoYuy3rJuZ", "version": "2022-08-29", "payload": { "customerId": "WGti3bpsethEWYJrm2icuo", "externalCustomerId": "20220906-206", "connectionId": "WGu7we7dVTCnVkjJrpzVNm", "institutionId": "29KL1L2iYUV3zUCoSHhwvr", "requestId": "1f97b51f3771328bc07187c8a2c1fe30", "financialAccounts": [ { "id": "WGti3dJFc6A9r1pYcBmE6e", "name": "Plaid Checking", "truncatedAccountNumber": "0000", "newAccount": true }, { "id": "WGti3cCpeiAtWxL6pFQPTg", "name": "Plaid Saving", "truncatedAccountNumber": "1111", "newAccount": true } ], "serviceProviderDetails": { "serviceProvider": "PLAID", "serviceProviderConnectionId": "NXrZpLwd1eUQQqymvl9nSkkV8DpeLLUWoXdZx" } } } ``` ### `BANK_LINKING_ACCOUNTS_UPDATED` **When it fires:** Sent once aggregation has completed for the accounts belonging to the connection. Products are now available to be fetched for the accounts. This webhook should arrive shortly after the `BANK_LINKING_ACCOUNTS_UPDATING` webhook, but sometimes products may take a while to aggregate for certain institutions. Note that if no products have been updated, then the `financialAccounts` object in this webhook will be empty. **What to do:** Fetch the listed products for each financial account. See [Retrieving Connection Data](/docs/bank-linking/get-connection-data/retrieving-connection-data) and the per-account [aggregation overview](/docs/bank-linking/get-connection-data/when-is-your-data-available). *Financial Account fields for this event type:* | Key | Type | Description | | :------------------ | :--------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `financialAccounts` | Array of objects | | |     `id` | String | Unique identifier for the financial account | |     `products` | Array of Strings | The products that were updated for the account. This list will never include `TRANSACTIONS` because their updates are handled separately within the transactions webhooks. Products are considered updated even if nothing changed, and it is solely meant to indicate that the updated products were fetched from the service provider. | ```json theme={null} { "eventType": "BANK_LINKING_ACCOUNTS_UPDATED", "eventId": "Ja2HXauns8LHPYR1NhEbrh", "timestamp": "2022-09-06T17:42:51.853020Z", "accountId": "W9ju7atKP5Jaf5RPweGeis", "profileId": "W9kBgKB7afMxYoYuy3rJuZ", "version": "2022-08-29", "payload": { "customerId": "WGti3bpsethEWYJrm2icuo", "externalCustomerId": "20220906-206", "connectionId": "WGu7we7dVTCnVkjJrpzVNm", "institutionId": "29KL1L2iYUV3zUCoSHhwvr", "requestId": "1f97b51f3771328bc07187c8a2c1fe30", "successfullyAggregatedAt": "2023-08-22T19:48:55Z", "financialAccounts": [ { "id": "WGti3cCpeiAtWxL6pFQPTg", "products": [ "BALANCES", "IDENTIFIERS", "OWNERS" ] }, { "id": "WGti3dJFc6A9r1pYcBmE6e", "products": [ "BALANCES", "IDENTIFIERS", "OWNERS" ] } ], "serviceProviderDetails": { "serviceProvider": "PLAID", "serviceProviderConnectionId": "NXrZpLwd1eUQQqymvl9nSkkV8DpeLLUWoXdZx" } } } ``` ### `BANK_LINKING_ACCOUNTS_REMOVED` **When it fires:** Sent when individual financial accounts belonging to a connection have been deleted. This can occur when the customer revokes access to individual accounts or they close a financial account with their bank altogether. This webhook will always be issued following the `BANK_LINKING_CONNECTION_DELETED` event, as a connection deletion implies all of its financial accounts are deleted as well. **What to do:** Remove the listed financial account IDs from your local data store and stop displaying them to the customer. ```json theme={null} { "eventType": "BANK_LINKING_ACCOUNTS_REMOVED", "eventId": "GUVQ5N9tQpLFALKpRevt6C", "timestamp": "2021-12-09T19:09:23.283931Z", "accountId": "QfdzpX3te1cDaviihXA4D5dA6ghnT3", "profileId": "W9kBgKB7afMxYoYuy3rJuZ", "version": "2021-10-28", "payload": { "customerId": "WGuEzKYdKukkUFxmzVDUvm", "externalCustomerId": "20221031-34", "connectionId": "WGuEzMHgUFDzumu6zVRyw5", "institutionId": "W9kgBBLULSJFSKTwCpb5Gf", "financialAccounts": [ { "id": "WGuEzL3AC3sF155f2ASWpu" }, { "id": "WGuEzHzJzEiDTBhCcniN4D" }, { "id": "WGuEzHoQA6UgG1Nq1qQ1Qy" }, { "id": "WGuEzHswGCZCn2bS1N9Mkh" } ], "serviceProviderDetails": { "serviceProvider": "FINICITY", "serviceProviderConnectionId": "6026862732" } } } ``` # Transaction Webhooks See the [API Reference](/api-reference/bank-linking) for the full payload schema of each transaction webhook. ### `BANK_LINKING_TRANSACTIONS_AGGREGATED` **When it fires:** Captures net transactional effects for each financial account belonging to the connection. Sent after each aggregation in which transactions are added or modified. **What to do:** If you cache transactions locally, re-fetch the affected financial accounts using the `oldestTransactionUpdatedSearchKey` so you pick up new and updated transactions. See the [webhook flow](/docs/bank-linking/get-connection-data/when-is-your-data-available) for more details. ```json theme={null} { "eventType": "BANK_LINKING_TRANSACTIONS_AGGREGATED", "eventId": "FEfSjVLXjM81CkG9ejkWBvu3Kb6ND5", "timestamp": "2022-01-31T22:05:04.068964Z", "accountId": "QfdzpX3te1cDaviihXA4D5dA6ghnT3", "version": "2022-03-09", "payload": { "customerId": "WGv8FTrTroky93FYwAJNXc", "externalCustomerId": "testCustomer", "connectionId": "WGumJ72d21SDCyma5Ax51k", "requestId": "1f97b51f3771328bc07187c8a2c1fe30", "successfullyAggregatedAt": "2023-08-22T19:48:55Z", "isPartial": false, "financialAccounts": [ { "financialAccountId": "WGv8FSGqrwvgVtqY5CARE9", "oldestTransactionUpdatedSearchKey": "3ztRY2PPYjEyzuuAfttKi9omi7UbsLxTbPTDV8MDpPhg9trwrsLZkHCJtb7QPs7cq35DJWSGqnRPP8UxGDmy2o2DRZJnG826oCuePdcnkAaCFyUEqc2QLeteoqJ3xBKetdApYwoFtA39ZpMxyL77LPxZEyUsWa4NWpxAEMQUHV9pSqZcgG", "numTransactionsAdded": 3, "numInvestmentTransactionsAdded": 0, "numTransactionsUpdated": 1, "numInvestmentTransactionsUpdated": 0, "transactionIdsRemoved": [], "investmentTransactionIdsRemoved": [] }, { "financialAccountId": "WGv8FSsWTrgAXBtS2cVPWR", "oldestTransactionUpdatedSearchKey": "3ztRY2PPYjEyzuuAfttKi9omi7UbsLxTbPTDV8MDpPhg9trwrsLZkHCJtb7QPs7cq35DJWSGqnRPP8UxGDmy2o2DRZJnG826oCuePdcnkAaCFyUEqc2QLeteoqJ3xBKetdApYwoFtA39ZpMxyL77LPxZEyUsWa4NWpxAEMQUHV9pSqZcgG", "numTransactionsAdded": 0, "numInvestmentTransactionsAdded": 0, "numTransactionsUpdated": 17, "numInvestmentTransactionsUpdated": 0, "transactionIdsRemoved": [], "investmentTransactionIdsRemoved": [] } ], "serviceProviderDetails": { "environment": "sandbox", "item_id": "zqyK7Abwv7cXJnr5Xk9xS31lMgqdXDuo3ZDxb", "new_transactions": 8, "serviceProvider": "PLAID", "webhook_code": "INITIAL_UPDATE", "webhook_type": "TRANSACTIONS" } } } ``` ### `BANK_LINKING_HISTORICAL_TRANSACTIONS_AGGREGATED` **When it fires:** Captures net historical transaction effects for each financial account belonging to the connection. Sent at most once per connection after historical transactions (transactions older than 30 days) have been aggregated. By default, historical transaction aggregation is initiated after the connection is made. **What to do:** Fetch the older transactions if your application surfaces extended history. Check `historicalAggregationSucceeded` per account — some institutions do not support extended history and will report `false` with an explanatory `historicalAggregationError`. See the [webhook flow](/docs/bank-linking/get-connection-data/when-is-your-data-available) for more details. ```json theme={null} { "eventType": "BANK_LINKING_HISTORICAL_TRANSACTIONS_AGGREGATED", "eventId": "FEfSjVLXjM81CkG9ejkWBvu3Kb6ND5", "timestamp": "2022-01-31T22:05:04.068964Z", "accountId": "QfdzpX3te1cDaviihXA4D5dA6ghnT3", "version": "2022-03-09", "payload": { "customerId": "WGv8FTrTroky93FYwAJNXc", "externalCustomerId": "testCustomer", "connectionId": "WGumJ72d21SDCyma5Ax51k", "requestId": "1f97b51f3771328bc07187c8a2c1fe30", "successfullyAggregatedAt": "2023-08-22T19:48:55Z", "isPartial": false, "financialAccounts": [ { "financialAccountId": "WGv8FSGqrwvgVtqY5CARE9", "oldestTransactionUpdatedSearchKey": "3ztRY2PPYjEyzuuAfttKi9omi7UbsLxTbPTDV8MDpPhg9trwrsLZkHCJtb7QPs7cq35DJWSGqnRPP8UxGDmy2o2DRZJnG826oCuePdcnkAaCFyUEqc2QLeteoqJ3xBKetdApYwoFtA39ZpMxyL77LPxZEyUsWa4NWpxAEMQUHV9pSqZcgG", "numTransactionsAdded": 84, "numInvestmentTransactionsAdded": 0, "numTransactionsUpdated": 1, "numInvestmentTransactionsUpdated": 0, "transactionIdsRemoved": [], "investmentTransactionIdsRemoved": [], "historicalAggregationSucceeded": false, "historicalAggregationError": { "error": { "message": "Member's institution does not support extended transaction history.", "status": "bad_request", "type": "bad_request_error" }, "serviceProvider": "MX" } }, { "financialAccountId": "WGv8FSsWTrgAXBtS2cVPWR", "oldestTransactionUpdatedSearchKey": "3ztRY2PPYjEyzuuAfttKi9omi7UbsLxTbPTDV8MDpPhg9trwrsLZkHCJtb7QPs7cq35DJWSGqnRPP8UxGDmy2o2DRZJnG826oCuePdcnkAaCFyUEqc2QLeteoqJ3xBKetdApYwoFtA39ZpMxyL77LPxZEyUsWa4NWpxAEMQUHV9pSqZcgG", "numTransactionsAdded": 150, "numInvestmentTransactionsAdded": 0, "numTransactionsUpdated": 1, "numInvestmentTransactionsUpdated": 0, "transactionIdsRemoved": [], "investmentTransactionIdsRemoved": [], "historicalAggregationSucceeded": false, "historicalAggregationError": { "error": { "message": "Member's institution does not support extended transaction history.", "status": "bad_request", "type": "bad_request_error" }, "serviceProvider": "MX" } } ], "serviceProviderDetails": { "serviceProvider": "FINICITY", "serviceProviderConnectionId": "6026862732" } } } ``` # Gets details for activities Source: https://docs.meld.io/api-reference/beta/activity-log/activity-log-details-get /openapi/beta-20231219.json get /activity-log/details # Search activity log Source: https://docs.meld.io/api-reference/beta/activity-log/activity-log-search /openapi/beta-20231219.json get /activity-log Retrieve logged activity filtered by various parameters over a date range. See [here](https://docs.meld.io/docs/debugging-activity-log) for information on how to use the activity log and why. # Get crypto sell limits for crypto currencies Source: https://docs.meld.io/api-reference/service-providers/service-provider/service-providers-limits-crypto-currency-sells-get /openapi/serviceproviders-20231219.json get /service-providers/limits/crypto-currency-sells Returns a list of limits (minimums and maximums) in terms of crypto tokens for selling crypto. # Get crypto purchase limits for fiat currencies Source: https://docs.meld.io/api-reference/service-providers/service-provider/service-providers-limits-fiat-currency-purchase-get /openapi/serviceproviders-20231219.json get /service-providers/limits/fiat-currency-purchases Returns a list of limits (minimums and maximums) in terms of fiat currencies tokens for buying crypto. # Get KYC levels for fiat currencies Source: https://docs.meld.io/api-reference/service-providers/service-provider/service-providers-limits-kyc-fiat-level-get /openapi/serviceproviders-20231219.json get /service-providers/limits/kyc-fiat-levels Returns a list of KYC limits in terms of fiat currencies tokens for buying crypto. # Search fiat currencies Source: https://docs.meld.io/api-reference/service-providers/service-provider/service-providers-properties-fiat-currency-search /openapi/serviceproviders-20231219.json get /service-providers/properties/fiat-currencies Returns a list of properties which meet the search criteria. # Search payment methods Source: https://docs.meld.io/api-reference/service-providers/service-provider/service-providers-properties-payment-method-search /openapi/serviceproviders-20231219.json get /service-providers/properties/payment-methods Returns a list of properties which meet the search criteria. # Get all webhook event types that can be sent Source: https://docs.meld.io/api-reference/webhooks/profile/webhooks-events-get /openapi/webhooks-20231219.json get /notifications/webhooks/event-types # Create a new webhook profile Source: https://docs.meld.io/api-reference/webhooks/profile/webhooks-profiles-create /openapi/webhooks-20231219.json post /notifications/webhooks # Get an existing webhook profile Source: https://docs.meld.io/api-reference/webhooks/profile/webhooks-profiles-get /openapi/webhooks-20231219.json get /notifications/webhooks/{webhookProfileId} # Search webhook profiles Source: https://docs.meld.io/api-reference/webhooks/profile/webhooks-profiles-search /openapi/webhooks-20231219.json get /notifications/webhooks Returns a list of webhook profiles that match the query parameters. The webhook profiles are sorted by name. # Test an existing webhook profile Source: https://docs.meld.io/api-reference/webhooks/profile/webhooks-profiles-test /openapi/webhooks-20231219.json post /notifications/webhooks/{webhookProfileId}/test Sends a WEBHOOK_TEST event to the URL in the profile # Update an existing webhook profile Source: https://docs.meld.io/api-reference/webhooks/profile/webhooks-profiles-update /openapi/webhooks-20231219.json patch /notifications/webhooks/{webhookProfileId} # Account Verification Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/account-verification Account verification confirms that a user owns and has access to a specific bank account. Developers use it most commonly before initiating an ACH transaction so that they know the user is authorized to move money from the bank account in question. ## How it works Account verification involves creating a connection that requests the [OWNERS](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-owners) and [IDENTIFIERS](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-identifiers) products: * Through the **OWNERS** product you get the user's name and address. * Through the **IDENTIFIERS** product you get the user's account number and routing number. Once the connection completes, you have verified the user's identity and access to the bank account and can perform an ACH transaction. Every bank linking connection has a Meld `customerId` associated with it. Use that same `customerId` when creating a payments customer so that the bank account and payments customer are linked. # Android App to App Authentication Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/app-to-app-authentication/android-app-to-app-authentication This page outlines the steps to load the Meld bank linking widget inside your Android mobile app using a WebView, and to support App to App authentication with banks that have implemented it. ## Before you begin * An Android project (Kotlin) where you can integrate a `WebView`. * A domain you control for [Android App Links](https://developer.android.com/training/app-links). * A Meld API key and the ability to call `/bank-linking/connect/start` to obtain a widget URL. # Project Setup ## Step 1: App Links Setup App Links are the Android equivalent of iOS's universal links. ### App Links Step 1: Setup Android app links following this [guide](https://developer.android.com/training/app-links). * Here is an example of what **AndroidManifest.xml** will look like: ```xml XML theme={null} ``` ### App Links Step 2: Redirect URL * We need to pass the `redirectUrl` in Meld's `/bank-linking/connect/start` endpoint. * Ensure that the redirectUrl parameter is an HTTP URL and not a deep link. It should specifically be formatted as an Android App Link. For detailed guidance on creating App Links, refer to the instructions provided in this [guide](https://developer.android.com/training/app-links). ### App Links Step 3: Opening the Widget URL in a Webview * Add the following settings to the webview to give the proper permissions: ```kotlin Kotlin theme={null} private lateinit var webView : WebView private lateinit var childWebView : WebView override fun onCreate(savedInstanceState: Bundle?) { // ... webView = findViewById(R.id.webView); // please name accordingly // Using a custom WebView Client as we have to override the url loading functionality webView.webViewClient = CustomWebViewClient() webView.settings.apply { domStorageEnabled = true javaScriptEnabled = true javaScriptCanOpenWindowsAutomatically =true } // This webView will handle the login screen childWebView = findViewById(R.id.childWebView); // please name accordingly childWebView.webViewClient = CustomWebViewClient() childWebView.settings.apply { domStorageEnabled = true javaScriptEnabled = true javaScriptCanOpenWindowsAutomatically =true } // This will handle the incoming meld events like connect_complete, handover, cancel etc // Follow step #3 for the implementation webView.addJavascriptInterface(JsObject(), "Android") } ``` * Here’s the definition of the **CustomWebViewClient** class: ```kotlin Kotlin theme={null} private inner class CustomWebViewClient :WebViewClient() { override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean { // we'll add the code here based on different providers val parsedUri = request?.url val uriString = parsedUri.toString() return false } } ``` # Enable Visibility To enable app to app authentication, you may need to declare specific apps as visible to your app by adding queries in your **AndroidManifest.xml** file. For example, to enable visibility for the Chase app, include the following: ```xml XML theme={null} // this will make the chase app visible to your app ``` The above example is specific to the Chase app. If you wish to enable app to app authentication with additional apps, include the respective package entries for each additional app in the `` section. ## Important Considerations for QUERY\_ALL\_PACKAGES Permission From Android 11 (API level 30), the **QUERY\_ALL\_PACKAGES** permission is restricted to apps that target API level 30 or later on devices running Android 11 or newer. To use this permission, your app must fulfill specific criteria outlined by the Google Play policy. This includes having a core purpose that requires visibility of all installed apps on the device and providing a sufficient justification as to why alternative, less intrusive methods of app visibility would not enable the app’s policy-compliant, user-facing functionality. In some cases, Android may restrict visibility of certain apps to your app. If your app needs to access a comprehensive list of all installed apps on the device, include the **QUERY\_ALL\_PACKAGES** permission in the Android manifest. Be aware that if you intend to publish your app on Google Play, the usage of this permission is subject to [Google Play’s approval process](https://support.google.com/googleplay/android-developer/answer/10158779). # Provider Specific Code Depending on which provider(s) your app uses, you will have to configure some provider specific code in order to make app to app authentication works. This section details what that code is. ## MX 1. To handle the MX service provider appropriately, incorporate the following code snippet within the **shouldOverrideUrlLoading** function: ```kotlin Kotlin theme={null} override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean { // ... val isMessageFromConnect = uriString.startsWith("meldapp://") || uriString.startsWith("atrium://") if (isMessageFromConnect) { // 2. Handle OAuth redirects if the URL is related to OAuth return mxHandler(parsedUri, view); } } fun mxHandler(uri: Uri?, view: WebView?): Boolean { if (uri?.path == "/oauthRequested") { try { val mxMetaData = JSONObject(uri?.getQueryParameter("metadata")) val oauthURL = mxMetaData.getString("url") val oauthPage = Uri.parse(oauthURL) if (isAppLink(context, oauthURL)) { val intent = Intent(Intent.ACTION_VIEW, oauthPage) view?.context?.startActivity(intent) } else { view?.loadUrl(oauthPage.toString()) } } catch (err: Exception) { Log.e("MX:Error with OAuth URL", err.message!!) } } return true } ``` ## Plaid and Finicity 1. For Plaid and Finicity, integrate the following code into the **shouldOverrideUrlLoading** function. This addition functions as an “else” case subsequent to the “if” condition handling the MX provider: ```kotlin Kotlin theme={null} val isMessageFromConnect = uriString.startsWith("://") || uriString.startsWith("atrium://") if (isMessageFromConnect) { // 2. Handle OAuth redirects if the URL is related to OAuth return mxHandler(parsedUri, view); } else { // 3. Handle HTTP(S) URLs (open in browser or load in WebView) if (parsedUri?.scheme == "https" || parsedUri?.scheme == "http" || uriString != widgetUrl) { if(parsedUri.toString().contains("") && uriString.contains("oauth_state_id")) { val oauthStateId = parsedUri?.getQueryParameter("oauth_state_id") val newWidgetUrl = Uri.parse(widgetUrl).buildUpon().appendQueryParameter("plaidStateId", oauthStateId) webView.visibility = View.GONE childWebView.visibility = View.VISIBLE childWebView.loadUrl(uriString) return true } if (isAppLink(context, parsedUri.toString())) { // checking if an app is available to handle this url view?.context?.startActivity(Intent(Intent.ACTION_VIEW, parsedUri)) return true } if(uriString != widgetUrl) { webView.visibility = View.GONE childWebView.visibility = View.VISIBLE childWebView.loadUrl(uriString) return true } } } ``` ```kotlin Kotlin theme={null} // Helper function to check if there is an app that can handle this URL fun isAppLink(context: Context, url: String): Boolean { val uri = Uri.parse(url) val intent = Intent(Intent.ACTION_VIEW, uri) val resolveInfo = context.packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) return resolveInfo != null } ``` # Listening to Front End Events 1. Currently these events work for only Plaid & Finicity, not MX. After a successful OAuth process Meld’s widget will emit a handful of events which will need to be appropriately handled when control returns back to the app of origin. 2. You can listen to couple of pre-defined events which can be used to detect if the authentication process is completed, canceled, or if there was an error. ```kotlin Kotlin theme={null} enum class ConnectHandlerResponse(val message: String) { /** * Emitted when the widget initializes */ INIT("[meld-connect]init"), /** * Emitted at various times with debug information: * When your customer cancels an integrated service provider's embedded widget * Whenever your customer navigates to another screen within an integrated service provider's embedded widget */ DEBUG("[meld-connect]debug"), /** * when the widget initializes */ INITIALIZE("[meld-connect]init"), /** * Emitted when the widget encounters an error * Will be accompanied by query-string metadata, containing details and reason keys, as available */ ERROR("[meld-connect]error"), /** * Emitted once the call to /connect/complete has finished * Will be accompanied by query-string metadata, containing: * institutionId: Meld's institution ID for the institution your customer chose to connect with * institutionName: The name of the same institution * accountCount: The number of accounts your customer has connected * accounts: Where available, an array of connected accounts including details such as name, type, and mask; null otherwise */ COMPLETE("[meld-connect]connect_complete"), /** * Emitted when the connect has been completed */ HANDOVER("[meld-connect]handover"), /** * Emitted when your customer cancels the widget */ CANCEL("[meld-connect]cancel") } private inner class JsObject { private val TAG = "MeldActivity" @JavascriptInterface fun postMessage( event : String) { if (!event.isNullOrEmpty() && event.startsWith("[meld-connect]")){ var eventData : String? = null val event = if (event.contains("?")) { val splitResult = event.split("?") eventData = splitResult[1] splitResult.firstOrNull() } else event when(connectHandlerResponseFindValueOf(event.orEmpty())){ ConnectHandlerResponse.DEBUG -> {} ConnectHandlerResponse.INITIALIZE -> {} ConnectHandlerResponse.ERROR -> {} ConnectHandlerResponse.COMPLETE -> { Log.d("connectComplete", eventData.toString()) } ConnectHandlerResponse.HANDOVER -> { // close the Meld Connect Widget setResult(RESULT_OK) finish() } ConnectHandlerResponse.CANCEL -> { setResult(RESULT_CANCELED) finish() } else -> {} } } else { Log.d(TAG, "postMessage: MELD: got unknown response: $event") } } } ```
# App to App Authentication Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/app-to-app-authentication/index App to App Authentication lets a user who launches the Meld bank picker inside your mobile app jump directly into the bank's native app to log in (using biometrics, for example), then return to your app once authenticated. Developers integrate it to improve conversion and user trust during bank linking. Meld supports this flow for Plaid, MX, and Finicity, with provider-specific code on each platform. ## How it works When enabled, a user who sees Meld's bank picker in your mobile app gets redirected to the bank's app to log in, then back to your app of origin afterward. This makes authentication easier and more secure — the user can log in with biometrics rather than typing a username and password, and they can feel more secure that their bank credentials are protected because they are using the bank's own app. If the user does not have their bank app installed on their phone, the bank login screen is launched in a webview instead. ## Supported banks App to app authentication is currently only supported for Chase, as it is a feature developed by each bank. Other banks are following suit and will have this feature available in the future. | | Plaid | Finicity | MX | | :------ | :------------ | :------------ | :---------- | | iOS | **Supported** | **Supported** | Coming Soon | | Android | **Supported** | Coming Soon | Coming Soon | ## Platform guides The subpages describe how to implement App to App Authentication in iOS Native (Swift) and Android Native (Kotlin) mobile apps. If you are using other technologies such as Flutter or React Native, you will need to adapt the code samples to fit your framework. * [iOS App to App Authentication](/docs/bank-linking/bank-linking-quickstart/app-to-app-authentication/ios-app-to-app-authentication) * [Android App to App Authentication](/docs/bank-linking/bank-linking-quickstart/app-to-app-authentication/android-app-to-app-authentication) # iOS App to App Authentication Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/app-to-app-authentication/ios-app-to-app-authentication This page outlines the steps to load the Meld bank linking widget inside your iOS mobile app using a `WKWebView`, and to support App to App authentication with banks that have implemented it. ## Before you begin * An iOS project in Xcode where you can integrate a `WKWebView`. * Access to your website's server to host the `apple-app-site-association` file. * An Apple Developer account to create and use App IDs and provisioning profiles. * A Meld API key and the ability to call `/bank-linking/connect/start` to obtain a widget URL. # Project Setup ## Step 1: Universal Links Setup Universal Links provide a seamless user experience by connecting a URL to a specific part of your iOS app, bypassing the browser. This guide is designed for beginners and explains how to set up and use Universal Links in your iOS applications. ### Universal Links Step 1: Enable Associated Domains * Create App ID: Log into the [Apple Developer Portal](https://developer.apple.com/), go to “Certificates, Identifiers & Profiles,” and create a new App ID for your project. Ensure that you enable “Associated Domains". * Update Xcode Project: In Xcode, open your project settings, select your target, and go to the “Signing & Capabilities” tab. Add the “Associated Domains” capability by clicking the “+” icon and selecting it from the list. ### Universal Links Step 2: Configure Your Website * Create Apple-App-Site-Association File: You need to create a JSON file named apple-app-site-association (without any file extension) and host it at the root of your HTTPS-enabled web server or in the .well-known\ subdirectory. The content should look like this: ```json JSON theme={null} com.apple.developer.associated-domains applinks:*.yourdomain.com ``` * Replace TEAMID.BUNDLEID in the above code with your actual team ID and bundle identifier. The paths array should contain the URLs you want to link to your app. * Host the File: Ensure the apple-app-site-association file is accessible via HTTPS without any redirects at https\://``.com/apple-app-site-association. ### Universal Links Step 3: Configure Associated Domains in Xcode * Modify Entitlements: In your Xcode project, under the “Associated Domains” capability you added earlier, add an entry: applinks:`` ### Universal Links Step 4: Handle Universal Links in Your App * Update AppDelegate: Open AppDelegate.swift and implement the following\ function to handle incoming universal links: ```swift Swift theme={null} func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { if userActivity.activityType == NSUserActivityTypeBrowsingWeb, let url = userActivity.webpageURL { // Handle URL and parse parameters if needed return true } return false } ``` This code checks if the incoming activity type is a web browsing session and prints the URL, but you’ll likely want to redirect the user to the appropriate part of your app based on the URL. ### Universal Links Step 5: Test Your Universal Links * Prepare Your Device: To test on a real device, ensure the device has the provisioning profile that includes the Associated Domains capability. * Test the Link: Send yourself an email or message with the link you configured earlier (e.g., https\://``.com/path/to/content). Tap the link on your device, and it should open your app directly, bypassing the browser. ### Troubleshooting Tips * Verify Your AASA File: Use online tools like Apple’s AASA validator to ensure your apple-app-site-association file is correctly configured and accessible. * Check Console Logs: If the universal link is not working, check the device’s console logs for any errors related to universal links or associated domains. By following these steps, you should be able to implement and test Universal Links in your iOS apps effectively, providing users with a direct link into your app from web content. ## Step 2: WKWebview Setup ### WKWebview Step 1: Adding to View Controller * Start by setting up a standard Xcode project and add a WKWebView to your ViewController. Ensure you configure the WKWebView’s javaScriptCanOpenWindowsAutomatically setting to true. ```swift Swift theme={null} let preferences = WKPreferences() preferences.javaScriptCanOpenWindowsAutomatically = true let webConfiguration = WKWebViewConfiguration() webConfiguration.preferences = preferences webConfiguration.userContentController.add(self, name: meldMessageHandler) webView = WKWebView(frame: .zero, configuration: webConfiguration) webView.navigationDelegate = self webView.uiDelegate = self ``` ### WKWebview Step 2: Load the Widget URL * After adding the WKWebView to your ViewController as described in the previous step, load the Widget URL, which you can obtain from the connect/start API. Once the Widget URL is successfully loaded into the WebView, it will display the bank picker interface. Ensure proper error handling and network checks are in place to manage the loading process smoothly. ### WKWebview Step 3: Updating the Code * Add the following code: ```swift theme={null} let appScheme = "meldapp://" // Your apps custom scheme let atriumScheme = "atrium://" // MX atrium's default scheme (deprecated) let mxScheme = "mx://" // MX default scheme ``` ### WKWebview Step 4: Manage Navigation Requests * To capture and address specific use-cases for specific service providers, developers need to manage all navigation requests within the WKWebView. This requires implementing the WKNavigationDelegate protocol, which provides methods to track and control the loading of web content. A crucial method to implement is **webView(\_:decidePolicyFor:decisionHandler:)**. This method determines whether a navigation request should proceed, be canceled, or handled differently based on the request’s characteristics. # Provider Specific Code Depending on which provider(s) your app uses, you will have to configure some provider specific code in order to make app to app authentication work. This section details what that code is. ## MX 1. This code needed since MX uses the custom URL schema and relies on loading the content inside the MX widget. It also extracts the url parts and loads inside the MX widget and decision handler cancels the navigating requests. ```swift theme={null} let isPostMessageFromMX = (urlString?.hasPrefix(appScheme) == true || urlString?.hasPrefix(atriumScheme) == true || urlString?.hasPrefix(mxScheme) == true) if (isPostMessageFromMX){ let urlc = URLComponents(string: urlString ?? "") let path = urlc?.path ?? "" // there is only one query param ("metadata") with each url, so just grab the first let metaDataQueryItem = urlc?.queryItems?.first if path == "/oauthRequested" { handleOauthRedirect(payload: metaDataQueryItem) } decisionHandler(.cancel) return } ``` 2. handleOauthRedirect handles the APP to APP or APP to web passing ```swift theme={null} func handleOauthRedirect(payload: URLQueryItem?) { let metadataString = payload?.value do { if let json = try JSONSerialization.jsonObject(with: Data(metadataString.utf8), options: []) as? [String: Any] { if let url = json["url"] as? String { print("Intercepted Request inside:handleOauthRedirect \(url)") let options: [UIApplication.OpenExternalURLOptionsKey: Any] = [ .universalLinksOnly: true, // Try to open the app via universal link ] UIApplication.shared.open(URL(string: url)!, options: options) { (success) in if success { print("The URL was successfully opened.") } else { print("The URL could not be opened.") self.presentWebView(with: URL(string: url)!) } } } } } catch let error as NSError { print("Failed to parse payload: \(error.localizedDescription)") } } ``` 3. The web view loaded in the case of MX is handled as follows in **webView(\_:decidePolicyFor:decisionHandler:)**: ```swift theme={null} let urlString = navigationAction.request.url?.absoluteString currentURLOpened = navigationAction.request.url let currentURLOpenedString = currentURLOpened?.absoluteString if(currentURLOpenedString!.contains("{{universal-link}}/?status")){ self.popupWebViewControllerExternal?.dismiss(animated: true) popupWebView = nil } ``` ## Plaid and Finicity 1. To manage HTTPS requests from Plaid and Finicity, handle the navigation within the **webView(\_:decidePolicyFor:decisionHandler:)** method of the WKNavigationDelegate. Use the decision handler to allow navigation when these requests occur, enabling the WKWebView to securely load the relevant webpage. 2. **Plaid specific:** Upon completing the Plaid process, it is essential to pass the **oauth\_state\_id** to the Meld backend. This should be done along with the initial URL that was saved earlier in the process. This specific code block is only for Plaid, and does not need to be implemented for Finicity. ```swift theme={null} if url.absoluteString.contains("{{universal-link}}/?oauth_state_id=") { let urlString = url.absoluteString self.dismiss(animated: true, completion: nil) if let range = urlString.range(of: "=") { let substring = urlString[range.upperBound...] Let request = URLRequest(url: URL(string: connectUrlToSave!.absoluteString + "&plaidStateId=\(substring)")!) webView.load(request)// Output: 11834d6d-7d09-459b-b757-068697a5a07b } decisionHandler(.cancel) return } ``` All of the code under this message will need to be implemented for both Plaid and Finicity. Complete implementation for the **webView(\_:decidePolicyFor:decisionHandler:)**: ```swift theme={null} // handle navigation requests extension ConnectViewController: WKNavigationDelegate { // Capture request URLs public // Intercept all requests before they are loaded func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { let urlString = navigationAction.request.url?.absoluteString currentURLOpened = navigationAction.request.url let currentURLOpenedString = currentURLOpened?.absoluteString if(currentURLOpenedString!.contains("{{universal-link}}/?status")){ self.popupWebViewControllerExternal?.dismiss(animated: true) popupWebView = nil } let isPostMessageFromMX = (urlString?.hasPrefix(appScheme) == true || urlString?.hasPrefix(atriumScheme) == true || urlString?.hasPrefix(mxScheme) == true) if (isPostMessageFromMX){ let urlc = URLComponents(string: urlString ?? "") let path = urlc?.path ?? "" // there is only one query param ("metadata") with each url, so just grab the first let metaDataQueryItem = urlc?.queryItems?.first if path == "/oauthRequested" { handleOauthRedirect(payload: metaDataQueryItem) } decisionHandler(.cancel) return } else{ if let url = navigationAction.request.url { // If needed, modify the request or block it if url.absoluteString.contains("{{universal-link}}/?oauth_state_id=") { let urlString = url.absoluteString self.dismiss(animated: true, completion: nil) if let range = urlString.range(of: "=") { let substring = urlString[range.upperBound...] Let request = URLRequest(url: URL(string: connectUrlToSave!.absoluteString + "&plaidStateId=\(substring)")!) webView.load(request)// Output: 11834d6d-7d09-459b-b757-068697a5a07b } decisionHandler(.cancel) return } // Check if the URL scheme is something other than http or https if url.absoluteString.contains("closemyapp") { // Close the WebView or dismiss the view controller self.dismiss(animated: true, completion: nil) } } } decisionHandler(.allow) } ``` # Listening to Front End Events 1. This section applies to all service providers. Handle the UI requests so that app to web request can be handled internally. ```swift theme={null} / handle UI requests extension ConnectViewController: WKUIDelegate { public func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures _: WKWindowFeatures) -> WKWebView? { // create a new webView guard webView == view else { debugPrint("MELD: popup cannot launch another webview") return nil } let popup = WKWebView(frame: webView.bounds, configuration: configuration) popup.uiDelegate = self popup.navigationDelegate = self popupWebView = popup DispatchQueue.main.async { let popupWebViewController = PopupWebViewController(with: popup) popupWebViewController.delegate = self self.present(popupWebViewController, animated: true) } return popupWebView } public func webViewDidClose(_ webView: WKWebView) { // handle closing. make sure to pop if it's the root webView // root view if view == webView { delegate?.cancel() return } // close child view } ``` 2. Meld emits events to inform the status of the handshake.intercept. ```swift theme={null} private enum ConnectHandlerResponse: String { case debug = "[meld-connect]debug" // debug information case initialize = "[meld-connect]init" // when the widget initializes case error = "[meld-connect]error" // when the widget encounters an error case complete = "[meld-connect]connect_complete" // once the call to /connect/complete has finished case handover = "[meld-connect]handover" // when the connect has been completed case cancel = "[meld-connect]cancel" // when your customer cancels the widget } public func userContentController(_: WKUserContentController, didReceive message: WKScriptMessage) { guard message.name == meldMessageHandler else { return } guard let body = message.body as? String, body.hasPrefix("[meld-connect]") else { return // ignore } let parts = body.split(separator: "?", maxSplits: 1) guard parts.count <= 2 else { return } guard let response = ConnectHandlerResponse(rawValue: String(parts[0])) else { return // ignore } let payload: Any? if parts.count >= 2, let payloadData = parts[1].data(using: .utf8) { payload = try? JSONSerialization.jsonObject(with: payloadData) } else { payload = nil } switch response { case .debug: // Debug. Ignore debugPrint("MELD:debug") case .initialize: // Initialization. Ignore. debugPrint("MELD:init") case .error: // An Error debugPrint("MELD:error") case .complete: // Complete — linking is done debugPrint("MELD:complete") DispatchQueue.main.async { [weak self] in debugPrint("got payload \(payload ?? "")") self?.delegate?.handover() } case .handover: // DONE. Dismiss and proceed debugPrint("MELD:handover") DispatchQueue.main.async { [weak self] in debugPrint("got payload \(payload ?? "")") self?.delegate?.handover() } case .cancel: debugPrint("MELD:cancel") // Cancel. Dismiss DispatchQueue.main.async { [weak self] in self?.delegate?.cancel() } } } ```
# Bank Linking Glossary Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/bank-linking-glossary This page defines the terms used throughout Meld's Bank Linking documentation so that you and your team can speak the same language as the API. Use the diagram at the bottom to see how each entity relates to the others. ## Terms * **Account** — Your Meld account, which represents your company. * **Customer** — A customer you create with Meld. A single customer usually represents one person or business and can have multiple bank connections. Track customers using `customerId` (a Meld-generated unique ID), or pass your own ID in as `externalCustomerId`. * **Service Provider** — The entity responsible for completing a connection between a user and a bank account. Plaid, Finicity, MX, Yodlee, Salt Edge, Salt Edge Partners, and Akoya are all service providers. * **Connection** — A link between a service provider and a login to a bank. A connection can have several financial accounts associated with it (for example, a checking account and a savings account). A connection is tied to a particular institution. * **Institution** — A financial entity where money is stored. A bank is the most common type of institution; credit unions are another. * **Financial Account** — Includes checking accounts, savings accounts, 401(k)s, IRAs, credit card accounts, and so on. A single connection can have multiple financial accounts, and individual accounts may support different sets of products. For example, credit card accounts don't support identifiers (i.e. routing and account numbers). ## Relationship diagram ![Relationship diagram showing Account, Customer, Connection, Institution, and Financial Account relationships](https://files.readme.io/ba3de38-image.png) # Balances Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-balances The Balances product returns the cached or realtime balances for a customer's financial account. Use it to determine if an account has sufficient funds before using it as a funding source for a transaction. **API Reference** * Search financial accounts: `GET /bank-linking/accounts` * Get a specific financial account: `GET /bank-linking/accounts/{accountId}` * Refresh customer accounts: `POST /bank-linking/connections/{connectionId}/refresh` ## Overview There are two types of balance data: current balance and available balance. * **Current balance** — The total amount of money in the account. This does not mean it is all available to spend. Some funds may be from deposits or checks that have not cleared yet. * **Available balance** — The total amount of money in the account that is available for your customer to spend. For fraud detection and insufficient-funds (NSF) prevention use cases, use available balance to determine fund sufficiency — it represents the predicted balance net of any pending transactions. Current balance does not take pending transactions into account. In some cases, a financial institution may not return available balance information. If that happens, you can calculate it by starting with the current balance, then using the transactions endpoint to detect pending transactions and adjusting the balance accordingly. ## Balance data When calling the financial accounts endpoint, you can access the `currency`, `currentAmount`, and `availableAmount` for that account. See [Retrieving Connection Data](/docs/bank-linking/get-connection-data/retrieving-connection-data) for an example. ## Sample response ```json theme={null} { "id": "W9ja25UmrZF25xbG22wdd", "accountId": "W9kbkRme9VT2iz6qdsaww2", "customerId": "W9ja24edcTHBks8h2e2dww", "institutionId": "Ntj3AwgdridJc2rasdssdd", "institutionName": "Chase", "status": "ACTIVE", "type": "DEPOSITORY", "subtype": "CHECKING", "name": "Checking", "truncatedAccountNumber": "1234", "identifiers": { // data }, "balances": { "currency": "USD", "currentAmount": 3578.41, "availableAmount": 3578.41, "updatedAt": "2022-03-08T23:19:23Z" }, "owners": [ // data ], "serviceProviderDetails": [ // data ] } ``` ## Account balance updates Account balance data is constantly updated as new transactions are made and previous transactions are processed. Use Meld's webhooks to receive updates on the data — see [Webhooks Quickstart](/docs/bank-linking/onboarding/step-5-setting-up-webhooks/webhook-quickstart) for setup details. # Identifiers Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-identifiers The Identifiers product returns the account number, routing number, and related identifiers for a customer's financial account. Use it to ensure that subsequent transactions (such as ACH transfers) end up in the right place. **API Reference** * Search financial accounts: `GET /bank-linking/accounts` * Get a specific financial account: `GET /bank-linking/accounts/{accountId}` ## Overview Each checking or savings account is assigned a unique account number and routing number. * **Account number** — Identifies your customer's unique bank account. Typically a 10–12 digit string that tells the bank which account to withdraw funds from or deposit funds in. * **Routing number** — A nine-digit number that financial institutions use to identify other financial institutions. It indicates which bank holds your customer's financial account. ## Use cases There are many use cases that require a customer's account and routing number, such as: * Your app allows users to cash out a credit balance to a bank account. * Your app allows users to pay you directly from their checking or savings account. * Your app allows an end-to-end ACH transfer. Account authentication lets you request a customer's account number and bank identification number (such as routing number, for US accounts) in a seamless and validated way without having to look up and type their account numbers. Account authentication generally supports checking and savings accounts only. ## Account authentication data When calling the financial account endpoint, you can access the `accountNumber` and `routingNumber` for that account. See [Retrieving Connection Data](/docs/bank-linking/get-connection-data/retrieving-connection-data) for an example. **Tokenized Account and Routing Numbers** Service providers may provide "tokenized" account and routing numbers when working with institutions that support an OAuth flow. This may be due to the provider's or institution's policies. A tokenized account number is not the actual number but a representation of it. These tokenized numbers work identically to normal account numbers. The digits returned in the `mask` field continue to reflect the actual account number, not the tokenized account number. For this reason, when displaying account numbers in your UI, always use the `mask` rather than truncating the account number. If a user revokes permissions to your app, the tokenized numbers will continue to work for ACH deposits but not withdrawals. ## Sample response ```json theme={null} { "id": "W9ja25UmrZF25xbG22wdd", "accountId": "W9kbkRme9VT2iz6qdsaww2", "customerId": "W9ja24edcTHBks8h2e2dww", "institutionId": "Ntj3AwgdridJc2rasdssdd", "institutionName": "Chase", "status": "ACTIVE", "type": "DEPOSITORY", "subtype": "CHECKING", "name": "Checking", "truncatedAccountNumber": "1234", "identifiers": { "ach": { "accountNumber": "00100000034561234", "routingNumber": "023000797" }, "eft": null, "international": null, "bacs": null }, "balances": { // data }, "owners": [ // data ], "serviceProviderDetails": [ // data ] } ``` # Investments Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-investments The Investments product lets developers connect to a customer's investment (brokerage) account and retrieve the current holdings and trade history. Use it for wealth-management, portfolio tracking, and financial-advice use cases. **API Reference** * Search investment holdings: `GET /bank-linking/investment-holdings` * Search investment transactions: `GET /bank-linking/investment-transactions` ## Overview Investments refers to an investment account (also known as a brokerage account) where one has various stocks and other securities such as mutual funds, CDs, bonds, and ETFs. The investment product lets you connect an investment account and get back all relevant information about the account. There are two types of investment data: investment holdings and investment transactions. See [Retrieving Connection Data](/docs/bank-linking/get-connection-data/retrieving-connection-data) for an example on fetching the data. ## Investment Holdings Investment Holdings refers to the current holdings of an investment account — what securities are currently in the account, along with other relevant information such as the number of shares, the cost basis, and the current value of the holding. This is similar to the Balances product but is a breakdown, since it includes all the securities in an account. ### Sample response for a single holding ```json theme={null} { "id": "WQ57LwnwzUo5WMM5Fi7nn2", "accountId": "W9kbkRme9VT2iz6qRT3212", "customerId": "WQ43wxs1bjUhohY8ekjeje", "financialAccountId": "WQ44KLVNHt9ARfp7aRWCC4", "symbol": "RKLB", "description": "Rocket Lab USA", "quantity": 2.00000000000000000000, "currentValue": 11.12000000000000000000, "costBasis": 11.11000000000000000000, "currency": "USD", "isin": "US7731221062", "cusip": "773122106", "type": "stock", "serviceProviderDetails": [ // data ] } ``` ## Investment Transactions Investment Transactions are the trade history of an account. For example, if the account owner purchased 10 shares of Google in a single trade, that would be a transaction. Unlike holdings, which are a snapshot of the present, transactions are the history of the account and give insight into how the current holdings were assembled. ### Sample response for a single transaction ```json theme={null} { "id": "WQ57iFJpqR524P82QZrFiW", "accountId": "W9kbkRme9VT2iz6qRT3212", "customerId": "WQ43wxs1bjUhohY8ekjeje", "financialAccountId": "WQ44KLVNHt9ARfp7aRWCC4", "amount": 11.11000000000000000000, "currency": "USD", "description": "Rocket Lab USA Limit Buy", "transactionDate": "2023-06-16T00:00:00Z", "postedDate": "2023-06-16T00:00:00Z", "symbol": "RKLB", "quantity": 2.00000000000000000000, "costBasis": 5.56000000000000000000, "type": "buy", "status": "POSTED", "serviceProviderDetails": [ // data ] } ``` Investments support varies by service provider. See the provider-specific mapping pages under [Partner Details](/docs/bank-linking/partner-details/index) for what is supported. # Owners Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-owners The Owners product returns the account-identity data recorded with a customer's financial institution — full name, phone number, mailing address, and email address. Developers use it to verify customer identity for ACH, KYC, and risk workflows. **API Reference** * Search financial accounts: `GET /bank-linking/accounts` * Get a specific financial account: `GET /bank-linking/accounts/{accountId}` ## Overview Account identity information helps you verify your customer's identity by accessing the data recorded with their financial institution. ## Use cases Common use cases for verifying a customer's identity: * Verify an account before initiating an ACH transaction. * Verify depository-type accounts (such as checking and savings) and credit-type accounts (such as credit cards). * Verify the information provided by your customer with a trusted source when opening a new account. * Verify customers you have identified as higher risk based on data such as email address, location, financial institution, or activity patterns. ## Account identity data When calling the financial account endpoint, you can access the account's identity data for that account. See [Retrieving Connection Data](/docs/bank-linking/get-connection-data/retrieving-connection-data) for an example. Specifying `OWNERS` as a product in `/bank-linking/connect/start` does not guarantee account-owners data will be available for every account in the connection. Some accounts do not have owners data, and others may only have a subset of the owner fields. ## Sample response ```json theme={null} { "id": "W9ja25UmrZF25xbG22wdd", "accountId": "W9kbkRme9VT2iz6qdsaww2", "customerId": "W9ja24edcTHBks8h2e2dww", "institutionId": "Ntj3AwgdridJc2rasdssdd", "institutionName": "Chase", "status": "ACTIVE", "type": "DEPOSITORY", "subtype": "CHECKING", "name": "Checking", "truncatedAccountNumber": "1234", "identifiers": { // data }, "balances": { // data }, "owners": [ { "addresses": [ { "data": { "street": "123 Cherry Hill Lane", "city": "Palo Alto", "region": null, "postalCode": "94537-8038", "country": "USA", "full": "123 Cherry Hill Lane Palo Alto CA 94537-8038 USA" }, "primary": "UNKNOWN" } ], "emails": [ "gjones@hotmail.net" ], "names": [ "George Jones" ], "phoneNumbers": null } ], "serviceProviderDetails": [ // data ] } ``` # Processor Token Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-processor-token A processor token tokenizes sensitive information (such as account and routing numbers) between a bank linking provider and a payment service provider — for example, a Plaid–Checkout integration. Use a processor token when a downstream processor needs to act on a bank account without handling raw account details. **API Reference** * Create a processor token: `POST /bank-linking/accounts/{accountId}/processor-token` ## Overview Processor tokens are optional and are used to make calls to a payment service provider (for example, Checkout) on behalf of the bank linking provider (for example, Plaid) without needing to directly pass the sensitive data. For example, Plaid can authorize Checkout to pull money from a user's bank account by sending a processor token instead of the raw account details. * Processor tokens do not expire. * Processor tokens cannot be modified, but can be revoked at any time. * Meld currently supports processor tokens for **Plaid** and **MX**. Processor Token is treated differently than other products: it isn't requested in the initial call to `/bank-linking/connect/start`. It has its own dedicated endpoint. For Plaid-specific details, see [Plaid's processor documentation](https://plaid.com/docs/api/processors/). # Transactions Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-transactions The Transactions product returns a customer's account transaction history. Developers use it for cash-flow modeling, risk analysis, expenditure categorization, and similar use cases. **API Reference** * Search financial transactions: `GET /bank-linking/transactions` * Get a financial transaction: `GET /bank-linking/transactions/{transactionId}` ## Overview Transactional data helps you understand a customer's expenditure, timing, and frequency of purchases. Call the transactions endpoint to filter transactions. See [Retrieving Connection Data](/docs/bank-linking/get-connection-data/retrieving-connection-data) for an example. Transactions are typically only available for the following account types: * Depository accounts such as checking and savings. * Credit-type accounts, such as credit cards or student loan accounts. ## Fetching transactions See [Fetching Transaction Information](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/fetching-transaction-information) for how to fetch Meld transactions. ## Updating transactions Transactions can get updated, especially their status. See [When Is Your Data Available?](/docs/bank-linking/get-connection-data/when-is-your-data-available) for more. ## Data normalization Meld normalizes transaction data, including the type, signage, and status. See [Data Normalization](/docs/bank-linking/get-connection-data/data-normalization) for more information. ## Transaction days fetched by refresh type Meld loads a different period of transaction history depending on several factors — whether it is the first load (`INITIAL`), a subsequent refresh (`SUBSEQUENT`), or a historical aggregation (`HISTORICAL`). ### Initial loads For `INITIAL` loads (immediately following completing or reconnecting a connection), only a limited transaction history is available. Meld loads whatever the service provider has initially available: * Plaid: last 30 days * Finicity: last 180 days * MX: last 90 days * Other service providers: 30 days ### Historical loads After completing an `INITIAL` aggregation, Meld triggers a `HISTORICAL` aggregation with the service provider. Historical aggregations collect **2 years** of transaction history across all service providers. Historical transaction data typically takes longer to aggregate (and is billed separately by the service provider), which is why it is executed after the initial aggregation. Because of this, a secondary `BANK_LINKING_HISTORICAL_TRANSACTIONS_AGGREGATED` webhook is issued after the first `BANK_LINKING_TRANSACTIONS_AGGREGATED` webhook to indicate that historical transactions are now also available (as well as how many were loaded). Since each service provider varies on what is immediately available during the `INITIAL` load, the `HISTORICAL` load collects whatever remains up to 2 years (i.e., from 30 days to 2 years for Plaid, from 180 days to 2 years for Finicity, etc.). The numbers of transactions loaded are broken down by period and reflected in the aforementioned webhooks. ### Subsequent loads Following both the `INITIAL` and `HISTORICAL` loads, only the recent transaction history is aggregated daily to capture any new transactions or updates to existing transactions. These loads are considered `SUBSEQUENT` and only retrieve the last 20 days of transaction data. This is sufficient to capture recent transactions as well as updates to existing transactions (such as transactions transitioning from `PENDING` to `POSTED`, or cancellations of transactions). ## Recurring transactions Certain providers identify recurring transactions (such as a monthly subscription). For Plaid and MX specifically, recurring transactions in the transaction details object have `recurring=TRUE`, while all other transactions have `recurring=FALSE`. ## Sample response ```json theme={null} { "id": "W9ja24xnWMVFc1s7D3rewfw", "accountId": "W9kbkRme9VT2iz623efee", "customerId": "W9ja24edcTHBks8hz2cvvf", "financialAccountId": "W9ja25UmrZF25xb2E3WSQ", "amount": 2018.27, "currency": "USD", "description": "Payment to Chase card ending in 1234", "status": "POSTED", "category": "Fee", "transactionDate": "2022-02-28T11:00:00Z", "postedDate": "2022-02-28T11:00:00Z", "serviceProviderDetails": [ // data ] } ``` # Fetching Transaction Information Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/bank-linking-products/fetching-transaction-information This page explains the recommended strategies for fetching and updating transactions in your application after Meld signals new data is available. Pick the strategy that best matches how aggressive your refresh cadence is and how much extra data you are willing to fetch. ## Before you begin * You have an active bank linking connection. * You are subscribed to the `BANK_LINKING_TRANSACTIONS_AGGREGATED` webhook (see [Webhooks Quickstart](/docs/bank-linking/onboarding/step-5-setting-up-webhooks/webhook-quickstart)) or you are polling the connection's `successfullyAggregatedAt` timestamp. ## When to fetch Fetching and updating transactions follows a general process: 1. Wait for an indication that updates have occurred — either via the `BANK_LINKING_TRANSACTIONS_AGGREGATED` webhook or by noticing an updated `successfullyAggregatedAt` time for the connection. 2. Capture those updates using one of the strategies below. Transactions are ordered by date descending (newest first, oldest last) for each financial account, and each transaction has an associated key field that represents its position in this sequence. When capturing additions/updates, retrieving the full transaction history each time isn't necessary (except right after the initial connection). Transactions typically aren't added or modified beyond 2 weeks of the time of fetching them. ## Recommended strategies Use one of the following to ensure all transaction additions/updates are captured during each refresh: 1. **Date-range fetch.** Fetch transactions using the `startDate` and `endDate` params. Following a successful refresh, set them so the last 2 weeks of transaction history is fetched. This reasonably ensures all transaction updates are captured. The length of this period can be modified to be longer or shorter depending on how conservative you want to be. Paginate through these results using the `after` param set to the key of the last transaction on each page until reaching the end of the results for that time period. 2. **Oldest-updated key (forward).** Use the `oldestTransactionUpdatedSearchKey` present for each financial account on the `BANK_LINKING_TRANSACTIONS_AGGREGATED` webhook. This key can be used as a baseline so all updates are captured without also capturing unnecessary older transactions that weren't updated (which could occur with option 1). Call the search transactions endpoint with no key initially, then paginate through the results using the `after` key until you reach the transaction with key matching the `oldestTransactionUpdatedSearchKey` (newest transaction → oldest transaction updated). 3. **Oldest-updated key (reverse).** The reverse of strategy #2: call the search transactions endpoint with the `before` key set to the `oldestTransactionUpdatedSearchKey`, and paginate through the results in the opposite direction using the `before` key until there are none remaining (oldest transaction updated → newest transaction). The `after` and `before` fields refer to pagination order, not occurrence order. This is standard across all bank-linking search endpoints. In particular for transactions, this can be misleading: they do **not** mean "occurred after" or "occurred before". Since transactions are ordered with the newest first and oldest last, each call with an `after` key returns the transactions on the next page (which are actually older), and each call with a `before` key returns the results on the previous page (which are actually newer). When updating transactions, Meld recommends searching by financial account ID rather than connection ID. The ordering of transactions is done by transaction date per financial account, so searching by connection ID would yield a sequence of: *account1 transactions ordered by date* → *account2 transactions ordered by date* → etc. This can be misleading when parsing the transactions, as some recent transactions may appear missing when in reality they are later in the sequence because they belong to a different financial account for the connection. # Bank Linking Products Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/bank-linking-products/index ## Overview Once your customer has connected their account to your app, you can use Meld's endpoints to retrieve customer financial account information. This page lists the products available, shows how Meld's product names map to each service provider's terminology, and links to the deeper documentation for each. The following Meld products are normalized to return data across any bank linking service provider configured to your account: * [Balances](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-balances): The cached or realtime balances of the customer's account. * [Identifiers](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-identifiers): The institution's account and routing numbers (or related account-specific identifiers). * [Owners](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-owners): Account-owner information such as name and address. * [Transactions](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-transactions): Account transaction history. * [Investment Holdings](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-investments): Current investment holdings, including stocks and other securities. * [Investment Transactions](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-investments): Trade history. The following are not standalone products but features Meld supports: * [Processor Token](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-processor-token): Where supported by the service provider, this token can be used by an authorized third party to make API calls directly to the processor on your behalf. * [Recurring Transactions](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-transactions): Certain providers identify recurring transactions (such as a monthly subscription). For Plaid and MX specifically, recurring transactions in the transaction details object have `recurring=TRUE`, while all other transactions have `recurring=FALSE`. Processor Token is treated differently than other products: it isn't requested in the initial call to `/bank-linking/connect/start`. It has its own dedicated endpoint described on the [Processor Token](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-processor-token) page. Mapping of Meld's products to service provider's products:
Meld Product
Plaid Product
Finicity Product
MX Product
Yodlee Product
SaltEdge Product
SaltEdge Partners Product
Akoya Product
**Balances**
Balance
availBalance
Balance
Account\_details
Account\_details
Account\_details
Balances
**Identifiers**
Auth
ACH
Verification
Account\_details
Account\_details
Account\_details
Payments
**Owners**
Identity
accountOwner
Identity
Account\_details
Holder Info
Holder Info
Customers
**Transactions**
Transactions
transAgg
Transactions
Transactions
Transactions
Transactions
Transactions
**Investment Holdings**
Investment Holdings
availBalance
Investment Holdings
Holdings
Investments
**Investment Transactions**
Investment Transactions
transAgg
Transactions
Transactions
Transactions
# Account Types and Subtypes Each service provider has its own way of returning data and hierarchy/nomenclature when it comes to account types and subtypes. Meld normalizes the data across providers so accounts have a uniform way of identifying account types. You can reference the service provider mappings to the Meld account types/subtypes in the *Normalized Account Types and Subtypes* section at the bottom of each service provider's mappings page (see [Partner Details](/docs/bank-linking/partner-details/index)). If service providers add new account types/subtypes, they will be mapped to the Meld type of `UNIDENTIFIED` until a proper mapping has been determined. A list of all the Meld account types and subtypes can be found below: # Bank Linking Use Cases Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/bank-linking-use-cases This page summarizes how to build an app that uses Meld's Bank Linking stack end to end — from creating a connection through ongoing management — and lists common use cases that Bank Linking data unlocks. Use it as a roadmap that points to the deep-dive pages for each step. ## How to use the product 1. [Create a connection](/docs/bank-linking/creating-connections/index) to your customer's bank account(s). 1. Test credentials and sandbox flows are described in the [Bank Linking Sandbox Testing Guide](/docs/bank-linking/testing-and-debugging/bank-linking-sandbox-testing-guide). 2. Inform the user that the connection is complete while you fetch the data in the background. See [Informing the user of the status](/docs/bank-linking/get-connection-data/index). 2. [Get connection data](/docs/bank-linking/get-connection-data/index) to pull the financial data you need. 1. This includes [balances](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-balances), [identifiers](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-identifiers), [owners](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-owners), [transactions](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-transactions), and [investments](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-investments). 2. Configure and consume [webhooks](/docs/bank-linking/webhook-events-bank-linking) so you know when new financial data is available or a connection is broken. 3. You can also create a [processor token](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-processor-token) for a financial account. 3. [Manage connection status](/docs/bank-linking/manage-connection-status/index) to keep connections organized and up to date. 1. [Refresh](/docs/bank-linking/manage-connection-status/refreshing-connections) a connection to get the latest data. 2. [Repair](/docs/bank-linking/manage-connection-status/repairing-connections) a connection to start receiving updates again. 3. [Delete](/docs/bank-linking/manage-connection-status/deleting-connections) a connection as needed. 4. [Route](/docs/bank-linking/creating-connections/select-an-institution/routing-overview) between providers to optimize conversion. 1. Each service provider has different coverage and details. Be sure to read through them before using a particular provider. See [Plaid](/docs/bank-linking/partner-details/plaid/index), [Finicity](/docs/bank-linking/partner-details/finicity/index), [MX](/docs/bank-linking/partner-details/mx/index), [Salt Edge](/docs/bank-linking/partner-details/salt-edge/index), and [Salt Edge Partners](/docs/bank-linking/partner-details/salt-edge-partners/index). 2. Meld's Bank Linking stack lets you route customers to specific providers based on predefined defaults, a provider waterfall using institution coverage and availability, or institution-specific decisioning. Routing rules are defined in the Meld dashboard. 5. If you already have existing connections with a service provider, you can bring them over by [importing connections](/docs/bank-linking/manage-connection-status/importing-connections). ## Potential use cases Common reasons to integrate Bank Linking: 1. View users' balances and investment holdings to give them financial advice. 2. View users' balances and transactions to assess loan qualification. 3. View users' owner information to verify their identity. 4. View users' identifier information (account number and routing number) to perform an ACH transaction. See [Account Verification](/docs/bank-linking/bank-linking-quickstart/account-verification) for more. ## Building your own institution picker The first step of bank linking is knowing which institution the user wants to connect to. Most customers launch Meld's widget, which has an institution picker as the first screen. However, you can build your own picker, have the user select an institution, and pass that `institutionId` into the Meld `/bank-linking/connect/start` endpoint — this skips the institution picker screen in the Meld UI. To populate institutions along with their symbols in your own UI, fetch this data from the institutions endpoint. Institution data rarely changes, so cache the response rather than calling the institutions endpoint live every time. # Bank Linking Quickstart Source: https://docs.meld.io/docs/bank-linking/bank-linking-quickstart/index Welcome to Meld's Bank Linking product. Meld's Bank Linking stack lets developers connect their users' bank accounts through multiple service providers (Plaid, MX, Finicity, Salt Edge, Yodlee, Akoya) using a single integrated API and UX flow. This quickstart covers how connections are created and managed, the data products available, and the terminology used throughout the rest of the bank linking documentation. Looking for Meld's Crypto / Digital Assets documentation? Skip this section and head to the [Crypto Overview](/docs/stablecoins/crypto-overview_/index) instead. ## Before you begin * Complete [Onboarding](/docs/bank-linking/onboarding/index) so you have a Meld dashboard account. * Grab your Meld API key from the **Developer** tab of the Dashboard (see [Step 3: Meld API Key](/docs/bank-linking/onboarding/step-3-meld-api-key)). * Confirm which environment your key targets (sandbox or production). Reach out to Meld if you would like the OpenAPI spec for bank linking. ## How to use the product The high-level flow is summarized in [Bank Linking Use Cases](/docs/bank-linking/bank-linking-quickstart/bank-linking-use-cases). For terminology and a relationship diagram, see the [Bank Linking Glossary](/docs/bank-linking/bank-linking-quickstart/bank-linking-glossary). For details on the financial-data products you can retrieve after a connection completes, see [Bank Linking Products](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/index). **Environments** Meld offers separate sandbox and production environments. Each environment has its own API key and base URL: | Environment | API Base URL | | :---------- | :----------------------- | | Sandbox | `https://api-sb.meld.io` | | Production | `https://api.meld.io` | ## Accounts with service providers Meld has reseller agreements with Finicity, MX, and Akoya and can create an account for you with each of these providers. For the other providers, if you don't have your own account with the provider already, Meld can temporarily share their account with you for testing while your account is being spun up. Contact Meld for more details. # Step 4: Complete the Connection Source: https://docs.meld.io/docs/bank-linking/creating-connections/complete-the-connection/index The final step of the connection flow is for the customer to authenticate with their bank. After they pick an institution, the widget hands off to that institution's login screen (or to the service provider's OAuth flow). This page covers what your customer sees, how to handle failure cases, and what events to expect when the connection succeeds. Once they have chosen their institution, they will be taken to that institution's login page. Here's an example: ![](https://files.readme.io/6010dd5-image.png) For security reasons, many institutions will open their login screens in a new popup to enforce an OAuth login. In that case, the login screen will be made by the institution, rather than the provider. Here's an example: ![](https://files.readme.io/9f177ec-image.png) Once the user enters their username and password, if they have multi factor authentication set up with their bank, they may have to enter a pin or passcode. After this, provider will complete the connection to the institution, and once the user sees a success screen they can close the widget. Once you have successfully completed a connection, you can now view the user's financial data. See [Get Connection Data](/docs/bank-linking/get-connection-data) for more information. ## Next steps To see what to do next, including which webhooks to expect once a connection completes, see [Get Connection Data](/docs/bank-linking/get-connection-data). ## Connection issues There are several reasons why a connection might fail. The most prevalent reason is that the user entered the incorrect credentials, in which case they will be asked to reenter their credentials. A user may also connect to an institution where they don't have any eligible accounts. For example, Meld does not support Mortgages for Plaid right now, so if the user uses Plaid to connect to an institution where they only have a mortgage account, they may not be able to connect it, and would see a message like this: ![](https://files.readme.io/7faa787-image.png) Sometimes, a service provider may temporarily have an issue connecting to a particular institution. While Meld tries to mitigate this by noticing breakages and steering users towards providers whose connection to that institution is working, on occasion users may still see a screen like below: ![](https://files.readme.io/da7e798-image.png) In this case, the user should go back and select a different institution, or route to the same institution through a different provider. For more information on the latter option, see [Bank Linking Routing](/docs/bank-linking/creating-connections/select-an-institution/routing-overview). For a deep dive into widget errors, including how to query them from the Activity Log, see [Widget Errors During a Connection](/docs/bank-linking/creating-connections/complete-the-connection/widget-errors-during-a-connection). # Widget Errors During a Connection Source: https://docs.meld.io/docs/bank-linking/creating-connections/complete-the-connection/widget-errors-during-a-connection A customer may encounter errors inside the Meld Connect Widget while trying to make a connection. Examples include requesting a product you do not have enabled with the service provider, hitting test connection limits, or transient service provider failures. This page is for developers debugging failed connections — it explains how to query widget errors and what each error subtype means. ## Before you begin * You have a Meld API key for the environment in question (sandbox or production) * You know either the `connectionId` of the failed attempt, or a date range you want to inspect * You are comfortable querying the Meld Activity Log ## How to query widget errors Widget errors are recorded in the Activity Log. Query them by filtering on the action `INSTITUTION_CONNECTION_WIDGET_ERROR`: ```bash theme={null} curl --location 'https://api-sb.meld.io/activity-log?start=2024-05-08T00:00:00.000Z&end=2024-05-10T00:00:00.000Z&connectionId=W9343jnjn3jn3&actions=INSTITUTION_CONNECTION_WIDGET_ERROR' \ --header 'Meld-Version: 2022-11-10' \ --header 'Content-Type: application/json' \ --header 'Authorization: BASIC ***Redacted API Key***' ``` For the full Activity Log schema and supported query parameters, refer to the [API reference](/api-reference). ## Widget error subtypes There are three event types (subtypes) of widget errors. Filter by one or more using the `eventTypes` query parameter on the Activity Log endpoint. | Event type | What it means | Recommended developer action | | :---------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `SERVICE_PROVIDER_INITIALIZATION_FAILURE` | The widget could not initialize with the underlying service provider. Common causes: the requested product is not enabled on your provider account, missing provider credentials, or invalid configuration in the dashboard. | Check that the products you requested in `/connect/start` are enabled with the provider for your account. Verify provider credentials in the Meld dashboard. If the issue persists, contact Meld support with the connection id. | | `SERVICE_PROVIDER_FAILURE` | The service provider returned an error during the customer's session. Common causes: institution outage, provider rate limit, expired provider session, or customer-side issues such as repeated invalid credentials. | Inspect the `serviceProviderDetails` in the activity log entry. If the institution is unhealthy, advise the customer to retry later or use a different provider via routing. If you see repeated provider rate-limit errors, throttle test traffic. | | `INTERNAL_FAILURE` | An unexpected error inside Meld occurred during the widget flow. | Retry the connection. If the error persists, contact Meld support with the `connectionId` and the timestamp. | ## Common scenarios and fixes | Symptom | Likely cause | Developer action | | :---------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Widget fails immediately on launch | The connect token expired (older than 3 hours) or was malformed | Generate a fresh token via `/connect/start` and relaunch | | Picker is empty or has very few institutions | Too many required products or an overly restrictive region filter | Reduce the `products` array, move non-essential products to `optionalProducts`, or remove `regions` | | "Error creating user" when selecting an institution | You re-enabled a service provider with the same `customerId` / `externalCustomerId` used before disabling | Use new `customerId` / `externalCustomerId` values when reconnecting via a re-enabled provider | | Customer sees "Institution unavailable" or a routing retry screen | Provider's integration with that institution is degraded | Encourage the customer to use Routing Retry to try another provider; consider enabling Smart Routing in the dashboard | | Connection completes but no webhooks fire | Webhook profile not configured or filtered too narrowly | Verify your webhook profile per [Webhook Quickstart](/docs/bank-linking/onboarding/step-5-setting-up-webhooks/webhook-quickstart) and confirm event types include bank linking events | **Sandbox vs. production** Widget errors are logged identically in sandbox (`api-sb.meld.io`) and production (`api.meld.io`). Sandbox traffic may produce additional `SERVICE_PROVIDER_FAILURE` entries that mirror the test behavior of each provider's sandbox; these are expected and do not indicate a real outage. # Step 1: Create a Connect Token Source: https://docs.meld.io/docs/bank-linking/creating-connections/create-a-connect-token/index The first step in linking a customer's bank account is creating a **connect token**. A connect token is a short-lived JWT that authorizes the Meld Connect Widget to start a connection session for a specific customer with a specific set of products and regions. This page walks developers through generating a token, configuring it correctly, and using the response to launch the widget in Step 2. ## Before you begin * You have a Meld sandbox or production API key * You have decided which [products](/docs/bank-linking/creating-connections/create-a-connect-token/supported-bank-linking-products) you need (you can also use [optional products](/docs/bank-linking/creating-connections/create-a-connect-token/optional-products)) * You know the [regions](/docs/bank-linking/creating-connections/create-a-connect-token/supported-bank-linking-products) your customers bank in, or are comfortable leaving regions unset * Your server is calling the endpoint — **never call this endpoint from the browser or mobile client**, the API key is a secret ## Create the token Send a `POST` request to `{api_base_url}/bank-linking/connect/start` using your API key. The response contains the `connectToken` you will use in [Step 2: Launch the widget](/docs/bank-linking/creating-connections/launch-the-widget). For the full request and response schema, refer to the [Bank Linking — Create Connect Token API reference](/api-reference/bank-linking). The connect token is used only while the user is making the connection to their bank and **expires after 3 hours**. You do not have to persist it — once the connection completes it cannot be reused. To track a connection over time, use the `customerId` returned in the response, or the `externalCustomerId` you optionally pass in. Be deliberate about the regions and products you request. In Step 3, when the user selects their institution, only institutions that support **all** requested products and are in one of the requested regions appear in the widget. Choosing too many products or too few regions can dramatically shrink the institution list. If you don't know the region in which your customer banks, omit the `regions` parameter entirely. **Disabling / re-enabling service providers** If you disable a service provider in the Meld dashboard and later re-enable it (with the same or different credentials), use new `customerId` / `externalCustomerId` values for subsequent connections. Reusing prior identifiers can result in an `Error creating user` when the customer selects an institution. ## Skipping or prepopulating the Meld picker If you already know which institution the user wants to link (for example, you collect that in your own UI), you have two options to make the flow more seamless: 1. **Prepopulate the search box.** Pass the institution name as a string when creating the connect token. The Meld picker autofills the search input so matching institutions appear by default. The user can still edit the search string. 2. **Skip the picker entirely.** Pass the `institutionId` corresponding to the user's bank. This skips the Meld picker screen and sends the user straight to the institution login. To resolve an institution name to a Meld `institutionId`, use the [/institutions endpoint](/api-reference/bank-linking). Note that for MX, not all institutions that support transactions also support historical transactions, so `HISTORICAL_TRANSACTIONS` appears as a separate product in the institution search response. However, when listing products in `/connect/start`, pass `TRANSACTIONS` only — `HISTORICAL_TRANSACTIONS` is not a valid value for the products array on `/connect/start`. A key part of building the request body is the list of products. For more information on the available products and which to request, see [Supported Bank Linking Products](/docs/bank-linking/creating-connections/create-a-connect-token/supported-bank-linking-products). ## Sample request ```bash theme={null} curl --location 'https://api-sb.meld.io/bank-linking/connect/start' \ --header 'Meld-Version: 2022-11-10' \ --header 'Content-Type: application/json' \ --header 'Authorization: BASIC ***Redacted API Key***' \ --data '{ "externalCustomerId": "testCustomer", "products": [ "BALANCES", "OWNERS", "IDENTIFIERS", "TRANSACTIONS", "INVESTMENT_HOLDINGS", "INVESTMENT_TRANSACTIONS" ], "regions": ["US", "CA"], "institutionSearchString": "Fidelity" }' ``` ## Sample response ```json theme={null} { "id": "WQ46XEVzT14vrrDq6LSJVg", "connectToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyZWRpcmVjdCI6dHJ1ZSwicmVnaW9ucyI6WyJVUyIsIkNBIl0sImlzcyI6Im1lbGQuaW8iLCJjb25uZWN0aW9uIjoiV1E0NlhFVnpUMTR2cnJEcTZMU0pFZCIsImV4cCI6MTY4Njc5ODkzMywiaWF0IjoxNjg2Nzg0NTMzLCJhY2NvdW50IjoiVzlrYmtSbWU5VlQyaXo2cUFTZkFmMiIsImN1c3RvbWVyIjoiV1E0NlhEcllZTVlyZTlSQmt3YXl5USIsInByb2R1Y3RzIjpbIlRSQU5TQUNUSU9OUyIsIkJBTEFOQ0VTIiwiSURFTlRJRklFUlMiLCJPV05FUlMiXSwicm91dGluZ1Byb2ZpbGUiOiJXR3ZGWHRYYkRvQ2VXc3hVdHVpeFB5In0.jAwpUokwkSKi7BTwKJOY_Y6XAPv_O8yOi6mVaUKxnMI", "customerId": "WQ46XDrYYMYre9RBkwayyZ", "externalCustomerId": "testCustomer", "widgetUrl": "https://institution-connect-sb.meld.io?connectToken=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyZWRpcmVjdCI6dHJ1ZSwicmVnaW9ucyI6WyJVUyIsIkNBIl0sImlzcyI6Im1lbGQuaW8iLCJjb25uZWN0aW9uIjoiV1E0NlhFVnpUMTR2cnJEcTZMU0pFZCIsImV4cCI6MTY4Njc5ODkzMywiaWF0IjoxNjg2Nzg0NTMzLCJhY2NvdW50IjoiVzlrYmtSbWU5VlQyaXo2cUFTZkFmMiIsImN1c3RvbWVyIjoiV1E0NlhEcllZTVlyZTlSQmt3YXl5USIsInByb2R1Y3RzIjpbIlRSQU5TQUNUSU9OUyIsIkJBTEFOQ0VTIiwiSURFTlRJRklFUlMiLCJPV05FUlMiXSwicm91dGluZ1Byb2ZpbGUiOiJXR3ZGWHRYYkRvQ2VXc3hVdHVpeFB5In0.jAwpUokwkSKi7BTwKJOY_Y6XAPv_O8yOi6mVaUKxnMI" } ``` ### Key response fields | Field | Description | | :------------------- | :-------------------------------------------------------------------------------------------------- | | `id` | The Meld connection id. Use this to look up the connection later. | | `connectToken` | The JWT used to launch the widget. Expires after 3 hours. | | `customerId` | Meld's id for the customer. Persist this to track the customer across connections. | | `externalCustomerId` | The id you passed in (if any), echoed back. | | `widgetUrl` | The full URL with the token attached. You can load this directly in an iframe to launch the widget. | ## Common errors | HTTP status | Likely cause | Developer action | | :----------------- | :-------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------ | | `401 Unauthorized` | Missing or invalid `Authorization` header | Verify the API key value and the `BASIC ` prefix; confirm you are hitting the correct environment | | `400 Bad Request` | Invalid product name, both required and optional product overlap, or unsupported region | Check `products` / `optionalProducts` / `regions` values against the API reference | | `400 Bad Request` | No required product specified when using `optionalProducts` | Move at least one product into the `products` array | | `404 Not Found` | The `institutionId` you passed does not exist | Re-resolve the institution via the `/institutions` endpoint | | `5xx` | Transient Meld or provider issue | Retry with backoff; if it persists, contact Meld support | Now that you have your connect token, you are ready to move on to [launching the widget](/docs/bank-linking/creating-connections/launch-the-widget). # Optional Products Source: https://docs.meld.io/docs/bank-linking/creating-connections/create-a-connect-token/optional-products By default, Meld filters the institutions shown in the picker to those that support **every** product in the `/connect/start` request. Sometimes you want users to see institutions that may not support all products, and then fetch data only for the products that are supported. **Optional products** solve this use case: they are aggregated on a best-effort basis and do not restrict which institutions appear. ## When to use optional products * You want maximum institution coverage but still want bonus data (such as investments) where it exists. * A product like `INVESTMENT_HOLDINGS` is nice to have for some customers but should not gate the rest of the flow. * You are retroactively adding products via the [update connection endpoint](/docs/bank-linking/manage-connection-status/updating-connections) — those products always land in `optionalProducts`. ## Rules * You must specify **at least one** product in the required `products` array. * A product cannot appear in both `products` and `optionalProducts`. * Optional products are aggregated on a best-effort basis — Meld does not guarantee data will be returned for them. Build your UI accordingly. ## Sample request ```bash theme={null} curl --location 'https://api-sb.meld.io/bank-linking/connect/start' \ --header 'Meld-Version: 2022-11-10' \ --header 'Content-Type: application/json' \ --header 'Authorization: BASIC ***Redacted API Key***' \ --data '{ "externalCustomerId": "testCustomer", "products": [ "BALANCES", "TRANSACTIONS" ], "optionalProducts": [ "INVESTMENT_HOLDINGS", "INVESTMENT_TRANSACTIONS" ] }' ``` In the example above, the picker filters institutions to those that support `BALANCES` and `TRANSACTIONS`. Institutions are **not** filtered out for missing investment support. If the connected accounts do carry investment data, Meld retrieves it and emits the corresponding webhooks. Optional products are powerful for maximizing coverage, but do **not** guarantee data will be returned for those products. Always handle the case where investment or other optional data is empty. # Supported Bank Linking Products Source: https://docs.meld.io/docs/bank-linking/creating-connections/create-a-connect-token/supported-bank-linking-products A **product** in Meld's Bank Linking stack is a category of financial data you can request for a connection — for example `BALANCES`, `TRANSACTIONS`, or `INVESTMENT_HOLDINGS`. This page is for developers deciding which products to request in [`/connect/start`](/docs/bank-linking/creating-connections/create-a-connect-token) and which service provider to route to. Choosing the right product set is critical because it determines which institutions appear in the picker and what data Meld returns. ## Explanation of the products * **Identifiers** — account and routing numbers used for ACH and payment instruments. * **Owners** — name and contact details for the account holder. * **Balances** — current and available balance on each account. * **Transactions** — posted and pending transactions; up to 2 years of history is loaded once available. * **Investment Holdings** — securities held in an investment account. * **Investment Transactions** — buys, sells, transfers, and other movement within investment accounts. * **Processor Token** — a token usable with downstream processors (e.g. Stripe, Dwolla); availability depends on the provider. The complete catalog of products in this table is the canonical list. The [Optional Products](/docs/bank-linking/creating-connections/create-a-connect-token/optional-products) feature uses the same product names — keep that page and this one in sync when adding new products. ## Supported products Each connection is established via a service provider. The service provider handles the customer's credentials, multi-factor authentication, and deals directly with the customer's bank. The list of currently supported service providers and their product availability is below. Specific details for each provider live on the Provider Details pages. | | | | | | | | | | :-------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------- | | |
[Identifiers](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-identifiers)
|
[Owners](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-owners)
|
[Balances](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-balances)
|
[Transactions](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-transactions)
|
[Investment Holdings](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-investments)
|
[Investment Transactions](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-investments)
|
[Processor Token](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-processor-token)
| | [Finicity](/docs/bank-linking/partner-details/finicity) |
X
|
X
|
X
|
X
|
X
|
X
| | | [Plaid](/docs/bank-linking/partner-details/plaid) |
X
|
X
|
X
|
X
|
X
|
X
|
X
| | Akoya |
X
|
X
|
X
|
X
|
X
|
X
| | | Yodlee |
X
|
X
|
X
|
X
|
X
|
X
| | | [MX](/docs/bank-linking/partner-details/mx) |
X
|
X
|
X
|
X
|
X
|
X
|
X
| | [Salt Edge Open Banking](/docs/bank-linking/partner-details/salt-edge) |
X
|
X
|
X
|
X
| | | | | [Salt Edge Partners](/docs/bank-linking/partner-details/salt-edge-partners) |
X
|
X
|
X
|
X
| | | | | Mesh | | |
X
| |
X
|
X
| | ## Product initialization Products are initialized in the [`/connect/start`](/docs/bank-linking/creating-connections/create-a-connect-token) request when creating a connection. Which products to initialize depends on your application's requirements. The products specified restrict the institutions shown in the selection pane to those that support **all** of them. If maximizing institution coverage is a high priority, only request products in `/connect/start` that are critical to your use case. To fetch more data where available without filtering institutions, use [Optional Products](/docs/bank-linking/creating-connections/create-a-connect-token/optional-products). This filtering is done at the **institution** level only — not at the account level. When the customer selects which accounts to share, all of their accounts are displayed even if individual accounts do not support all requested products. There is no guarantee that each selected account will support a given product; the filter only guarantees that the institution supports the product for at least some account types. Some products are priced differently and vary in availability by service provider. Confirm pricing with Meld before adding products in production. # Creating Connections Source: https://docs.meld.io/docs/bank-linking/creating-connections/index Creating institution connections is the core of Meld's Bank Linking service: it is how your application gains permissioned access to a customer's financial data. This guide is for developers integrating bank account linking into their product, and walks you through every step required to establish a working connection. Each connection has its own set of [products](/docs/bank-linking/creating-connections/create-a-connect-token/supported-bank-linking-products) associated with it, and is established via an underlying [service provider](/docs/bank-linking/creating-connections/create-a-connect-token/supported-bank-linking-products). ## Before you begin Before creating your first connection, make sure you have: * A Meld account with at least one Bank Linking service provider enabled in the dashboard * A sandbox API key (treat it as a secret — never expose it in front-end code) * A webhook profile configured if you plan to react to connection lifecycle events * Reviewed the list of [supported products](/docs/bank-linking/creating-connections/create-a-connect-token/supported-bank-linking-products) so you know which to request ## Steps to make a connection 1. [Create a connect token.](/docs/bank-linking/creating-connections/create-a-connect-token) Choose the [products](/docs/bank-linking/creating-connections/create-a-connect-token/supported-bank-linking-products) for which you would like data. 2. [Launch the widget.](/docs/bank-linking/creating-connections/launch-the-widget) 3. [The user selects an institution.](/docs/bank-linking/creating-connections/select-an-institution) 4. [The user completes the connection.](/docs/bank-linking/creating-connections/complete-the-connection) ## Routing Routing sends the user to the best available provider to complete their connection. More information on bank linking routing can be found in the [Routing overview](/docs/bank-linking/creating-connections/select-an-institution/routing-overview). **Routing across service providers** Meld's Bank Linking stack lets you route customers to specific service providers based on predefined defaults, a service provider waterfall driven by institution coverage and availability, or institution-specific decisioning. Routing rules are defined within the Meld dashboard. # Step 2: Launch the Widget Source: https://docs.meld.io/docs/bank-linking/creating-connections/launch-the-widget/index The Meld Connect Widget is the embedded UI your customer uses to pick a financial institution and authenticate with their bank. This page is for developers who already have a connect token and need to render the widget on web, mobile web, or in a native mobile app. Once your customer completes the flow, you can call Meld's APIs to retrieve their financial data. ## Before you begin * You have a valid `connectToken` from [Step 1: Create a Connect Token](/docs/bank-linking/creating-connections/create-a-connect-token) (tokens expire after 3 hours) * You have decided how to render the widget: web iframe, mobile WebView, or new tab * Your front-end is set up to listen for `window.message` events (or the platform equivalent) so it can react to widget events ## Overview The Meld Connect Widget contains two components: * **Front-end component** — used by the customer to select the financial institution. * **Back-end component** — used by your server to access the customer's financial data via the Meld API. Meld recommends launching the widget inside an iframe for optimal UI, and closing it when the `connect_complete` event is emitted. You can also load the URL in a new tab or webview but certain UI elements will not look as polished. How you display the widget does not affect the customer's ability to connect. ## Launch the Meld Connect Widget Use the connect token to build the URL and render the widget. Below is sample code: ```html theme={null} ``` ### Iframe Best Practices: * **Minimum width:** 450px for optimal display * **Minimum height:** 790px to prevent UI overlap * **Allow permissions:** Include `camera`, `microphone`, `payment` for KYC and transactions * **Responsive design:** Consider dynamic sizing for mobile ## Step 2: Customization with URL Parameters Add optional parameters to your Meld Widget URL for customization: ```javascript theme={null} // Your complete Meld Widget URL with optional parameters const dashboardUrl = 'meldcrypto.com/?publicKey=your_public_key'; // From Meld Dashboard Developer tab const customizedUrl = `${dashboardUrl}&sourceAmount=100&destinationCurrencyCode=BTC&countryCode=US`; window.location.href = customizedUrl; ``` ### Key Parameters: * **sourceAmount** - Pre-fill purchase amount (e.g., 100) * **destinationCurrencyCode** - Pre-select cryptocurrency (e.g., BTC, ETH\_ETHEREUM) * **walletAddress** - Pre-fill destination wallet if known * **countryCode** - Override auto-detected country (rarely needed) ➡️ **[Complete parameter reference](/docs/stablecoins/meld-checkout-integration/meld-checkout-guide/url-parameters)** ## Step 3: Testing Your Integration ### Sandbox Testing Always test in sandbox environment first: ```javascript theme={null} // Use your sandbox Meld Widget URL (from Developer tab in sandbox environment) const sandboxUrl = 'meldcrypto.com/?publicKey=your_public_key'; ``` ### Test Workflow: 1. **Redirect to sandbox widget** with your parameters 2. **Complete the test transaction** using the [sandbox testing credentials](/docs/stablecoins/sandbox-guide/test) 3. **Verify webhook reception** (if implemented) 4. **Test all user flows** (buy/sell as applicable) ## Step 6: Mobile Implementation ### React Native Example: ```javascript theme={null} import { Linking } from 'react-native'; const openMeldWidget = () => { const widgetUrl = `meldcrypto.com/?publicKey=your_public_key&redirectUrl=myapp://crypto-complete`; Linking.openURL(widgetUrl); }; ``` ### Deep Link Return: ```javascript theme={null} // Handle return from widget const handleDeepLink = (url) => { if (url.includes('crypto-complete')) { // User completed transaction, update UI navigateToCryptoSuccess(); } }; ``` ### Mobile Considerations: * **Use redirectUrl** with deep links to return users to your app * **Consider theme parameter** to match your app's appearance * **Test on both iOS and Android** for compatibility ## Step 4: Go Live ### Production Checklist: * ✅ **Use production Meld Widget URL** (from Developer tab in production environment) * ✅ **Test with small real transaction** * ✅ **Verify webhook endpoints** are working * ✅ **Monitor first transactions** for issues ### Production URL: ```javascript theme={null} const productionUrl = 'meldcrypto.com/?publicKey=your_public_key'; ``` ## Troubleshooting Common Issues ### Widget Won't Load * ✅ Verify Meld Widget URL is correct and from the right environment * ✅ Check for browser popup blockers * ✅ Ensure iframe permissions are set correctly ### Parameters Not Working * ✅ URL encode parameter values * ✅ Check parameter spelling and case sensitivity * ✅ Verify parameter combinations are valid ### Mobile Issues * ✅ Test deep link return flow * ✅ Verify redirectUrl format is correct * ✅ Check mobile browser compatibility ## Error Handling ### JavaScript Error Handling: ```javascript theme={null} try { window.location.href = widgetUrl; } catch (error) { console.error('Failed to open widget:', error); // Fallback: show error message to user alert('Unable to open crypto purchase. Please try again.'); } ``` ### Iframe Error Handling: ```javascript theme={null} const iframe = document.getElementById('meld-widget'); iframe.onerror = () => { console.error('Widget failed to load'); // Show error message or fallback content }; ``` ## Next Steps ### Optimize your integration * **[URL parameters](/docs/stablecoins/meld-checkout-integration/meld-checkout-guide/url-parameters)** — complete parameter reference * **[Customization](/docs/stablecoins/meld-checkout-integration/meld-checkout-guide/meld-checkout-customization)** — styling and advanced options ### Monitor and improve * **[Webhook events](/docs/stablecoins/for-all-products/webhook-events)** — real-time notifications * **[Transaction statuses](/docs/stablecoins/for-all-products/transaction-statuses)** — understanding transaction states * **[Dashboard data](/docs/stablecoins/additional-information/dashboard-data)** — analytics and reporting *** *Your Widget Integration is now live! Users can purchase crypto directly through your application with Meld's secure, compliant widget.* # Customization Source: https://docs.meld.io/docs/stablecoins/meld-checkout-integration/meld-checkout-guide/meld-checkout-customization This discusses the customization options available to you for the Meld Checkout flow # Meld Checkout Customization & Advanced Configuration Customize your Meld Checkout Integration appearance, behavior, and user experience through dashboard settings and advanced configuration options. ## Meld Support Required Contact Meld for these advanced customizations: ### Account Logo Update your account logo in the top-left corner of the UI. **Process:** Send your logo file (PNG/SVG preferred) to Meld support with specifications: * **Size:** 200x60px recommended * **Format:** PNG with transparent background or SVG * **Colors:** Optimized for both light and dark themes ### Flow Management Control which transaction types are available: * **Disable Buy Flow** - Show only sell/offramp functionality * **Disable Sell Flow** - Show only buy/onramp functionality * **Custom Flow Logic** - Advanced routing based on user profile ### Token Restrictions Limit available cryptocurrencies: * **Chain-specific tokens** - Only show tokens from specific blockchains * **Curated token list** - Custom selection of supported cryptocurrencies * **Partner tokens** - Highlight specific tokens for partnerships ### Quote Management Customize provider quote display: * **Provider prioritization** - Promote specific providers to top of list * **Quote limits** - Control number of quotes shown to users * **Custom provider branding** - Special provider highlighting ### Default Settings Set account-wide defaults: * **Default redirect URL** - Fallback URL for transaction completion * **Theme defaults** - Account-wide light/dark mode preference ## Dashboard Customization Access these options in the **Meld Dashboard → Developer Tab → Preferences**: ### Visual Branding #### Button Color Customize the primary **Buy** or **Sell** CTA button color at the bottom of the UI. ```css theme={null} /* Example color values */ Button Color: #3498db /* Blue */ Button Color: #e74c3c /* Red */ Button Color: #2ecc71 /* Green */ Button Color: #9b59b6 /* Purple */ ``` #### Button Text Color Set the text color inside the CTA button for optimal contrast. ```css theme={null} /* Common combinations */ Button Color: #3498db, Text Color: #ffffff /* Blue background, white text */ Button Color: #ffffff, Text Color: #333333 /* White background, dark text */ Button Color: #2c3e50, Text Color: #ecf0f1 /* Dark background, light text */ ``` ![](https://files.readme.io/ba42d402322fcd88e1fd9fa5c3f3c3070dc7cc8c24e68d67b03a877e0e3762b3-image.png) **Reset Option:** Use the **Reset** button to restore default values at any time. ## Theme Configuration ### Light/Dark Mode Implementation Control the UI appearance with the theme parameter: ```javascript theme={null} // Light theme (default) const lightThemeUrl = `https://meldcrypto.com/?publicKey=${publicKey}&theme=lightMode`; // Dark theme const darkThemeUrl = `https://meldcrypto.com/?publicKey=${publicKey}&theme=darkMode`; // Dynamic theme based on user preference const userTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'darkMode' : 'lightMode'; const dynamicUrl = `https://meldcrypto.com/?publicKey=${publicKey}&theme=${userTheme}`; ``` ### Theme Locking Prevent users from changing the theme: ```javascript theme={null} const lockedThemeUrl = `https://meldcrypto.com/?publicKey=${publicKey}&theme=darkMode&themeLocked=true`; ``` ## Mobile Optimization ### Deep Link Configuration Seamlessly return users to your mobile app after transactions: ```javascript theme={null} // iOS Deep Link const iosUrl = `https://meldcrypto.com/?publicKey=${publicKey}&redirectUrl=myapp://crypto-success`; // Android Deep Link const androidUrl = `https://meldcrypto.com/?publicKey=${publicKey}&redirectUrl=myapp://crypto-success`; // Universal Link const universalUrl = `https://meldcrypto.com/?publicKey=${publicKey}&redirectUrl=https://myapp.com/crypto-success`; ``` ### Responsive Implementation Optimize the UI display for different screen sizes: ```javascript theme={null} // Mobile-first approach function getMobileOptimizedUrl() { const isMobile = window.innerWidth <= 768; const baseUrl = `https://meldcrypto.com/?publicKey=${publicKey}`; if (isMobile) { // Full screen redirect for mobile return `${baseUrl}&redirectUrl=myapp://success`; } else { // Iframe embedding for desktop return baseUrl; } } ``` ### Mobile iframe Considerations ```html theme={null} ``` ## Analytics & Tracking ### Customer Identification Strategy Track users across sessions for analytics: ```javascript theme={null} // Use external customer ID for consistent tracking const analyticsUrl = `https://meldcrypto.com/?publicKey=${publicKey}` + `&externalCustomerId=${userId}` + `&externalSessionId=${sessionId}`; // Example: E-commerce integration function trackCryptoTransaction(userId, orderId) { const trackingUrl = `https://meldcrypto.com/?publicKey=${publicKey}` + `&externalCustomerId=${userId}` + `&externalSessionId=order_${orderId}` + `&redirectUrl=https://shop.com/order-complete?id=${orderId}`; window.location.href = trackingUrl; } ``` ### Conversion Optimization Pre-fill known information to improve conversion rates: ```javascript theme={null} // Optimize for returning customers function getOptimizedCheckoutUrl(userProfile) { let url = `https://meldcrypto.com/?publicKey=${publicKey}`; // Only override country if specifically needed (auto-detection usually works) if (userProfile.Country) { url += `&countryCode=${userProfile.Country}`; } if (userProfile.preferredCrypto) { url += `&destinationCurrencyCode=${userProfile.preferredCrypto}`; } if (userProfile.wallet) { url += `&walletAddress=${userProfile.wallet}&walletAddressLocked=true`; } return url; } ``` ## Advanced Use Cases ### Marketplace Integration Configure the Meld Checkout for NFT marketplace or token sales: ```javascript theme={null} // Token sale configuration function createTokenSaleCheckout(tokenInfo) { return `https://meldcrypto.com/?publicKey=${publicKey}` + `&destinationCurrencyCode=${tokenInfo.currency}` + `&destinationCurrencyCodeLocked=true` + `&sourceAmount=${tokenInfo.price}` + `&sourceAmountLocked=true` + `&theme=darkMode` + `&redirectUrl=https://marketplace.com/purchase-complete`; } // Usage const nftSaleUrl = createTokenSaleCheckout({ currency: 'ETH', price: 150 }); ``` ### Multi-Currency Support Offer multiple cryptocurrency options: ```javascript theme={null} // Dynamic currency selection function createCurrencyCheckout(selectedCrypto) { const cryptoOptions = { 'bitcoin': 'BTC', 'ethereum': 'ETH', 'usdc': 'USDC', 'solana': 'SOL' }; return `https://meldcrypto.com/?publicKey=${publicKey}` + `&destinationCurrencyCode=${cryptoOptions[selectedCrypto]}` + `&theme=lightMode`; } ``` ### Geographic Customization Optimize currency and amounts for specific regions: > **Note:** Country is automatically detected in the Meld Checkout Integration. Use countryCode parameter only when you need to override the detection for special cases. ```javascript theme={null} // Region-specific currency and amount configuration function getRegionalCheckout(overrideCountry = null) { const regionalConfig = { 'US': { currency: 'USD', amount: 100 }, 'GB': { currency: 'GBP', amount: 75 }, 'EU': { currency: 'EUR', amount: 85 }, 'CA': { currency: 'CAD', amount: 130 } }; const config = regionalConfig[overrideCountry] || regionalConfig['US']; let url = `https://meldcrypto.com/?publicKey=${publicKey}` + `&sourceCurrencyCode=${config.currency}` + `&sourceAmount=${config.amount}`; // Only add countryCode if overriding auto-detection if (overrideCountry) { url += `&countryCode=${overrideCountry}`; } return url; } ``` ## Contact Meld For advanced customizations requiring Meld support: **Email/Slack/Telegram:** Contact your integration specialist or support team\ **Documentation:** Reference this guide when making requests ### Request Template: ``` Subject: Meld Checkout Customization Request - [Your Account Name] Customization Type: [Logo Update/Flow Management/Token Restrictions/etc.] Current Setup: [Description of current checkout configuration] Desired Changes: [Specific requirements and use case] Timeline: [When you need changes implemented] ``` *** **Implementation Complete!** Your Meld Checkout Integration is now optimized for your specific use case and user experience requirements. # URL Parameters Source: https://docs.meld.io/docs/stablecoins/meld-checkout-integration/meld-checkout-guide/url-parameters Review the URL params you can use to prefill the Meld Checkout UI # Meld Checkout URL Parameters Reference Complete technical reference for all Meld Checkout Integration URL parameters. Use these parameters to prefill information and customize the user experience. ## Transaction Parameters ### transactionType Default transaction type to display first. If not passed in, the Meld Checkout will default to the Buy flow. ``` &transactionType=BUY # Default to buy flow (default) &transactionType=SELL # Default to sell flow ``` ### sourceAmount Amount of fiat currency to exchange. ``` &sourceAmount=50 # $50 &sourceAmount=100 # $100 (default) &sourceAmount=1000 # $1000 ``` ### sourceCurrencyCode Fiat currency for the transaction. Defaults to country's primary currency if not provided. ``` &sourceCurrencyCode=USD # US Dollars &sourceCurrencyCode=EUR # Euros &sourceCurrencyCode=GBP # British Pounds ``` ### destinationCurrencyCode Cryptocurrency to purchase or sell. ``` &destinationCurrencyCode=BTC # Bitcoin &destinationCurrencyCode=ETH # Ethereum &destinationCurrencyCode=USDC_BASE # USDC on Base &destinationCurrencyCode=SOL # Solana ``` ## Wallet Parameters ### walletAddress User's wallet address for the destination cryptocurrency. ``` &walletAddress=bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh # Bitcoin &walletAddress=0x742c8f2e0ce07Dd3f7E78A31E5A97D45c50fF2c8 # Ethereum &walletAddress=So11111111111111111111111111111111111111112 # Solana ``` ### walletTag Commonly known as wallet tag, destination tag, or memo, this field is only used for certain cryptocurrencies (EOS, STX, XLM, XMR, XRP, BNB, XEM, and HBAR) and must be passed in on top of the wallet address for these cryptocurrencies for the transaction to succeed. ``` &walletAddress=rDsbeomae4FXwgQjRq9bCVFeVbU8c65pNF&walletTag=123456789 ``` ## Location Parameters ### countryCode ISO 2-letter country code to override auto-detected country. > **Note:** Country is automatically detected based on user location in the Meld Checkout Integration. Use this parameter only when you need to override the automatic detection. ``` &countryCode=US # United States &countryCode=GB # United Kingdom &countryCode=CA # Canada &countryCode=DE # Germany &countryCode=AU # Australia ``` ## Payment Parameters ### paymentMethodType How the user plans to pay for cryptocurrency. ``` &paymentMethodType=CREDIT_DEBIT_CARD # Credit/Debit Card &paymentMethodType=BANK_TRANSFER # Bank Transfer &paymentMethodType=APPLE_PAY # Apple Pay &paymentMethodType=GOOGLE_PAY # Google Pay ``` ## User Identification ### customerId Meld's internal customer ID. Do not use with externalCustomerId. ``` &customerId=cust_abc123def456 ``` ### externalCustomerId Your internal customer ID. Do not use with customerId. ``` &externalCustomerId=user_789 &externalCustomerId=customer_12345 ``` ### externalSessionId Your internal session ID for tracking and analytics. ``` &externalSessionId=session_abc123 &externalSessionId=checkout_456 ``` ## UI Parameters ### theme Meld Checkout color theme. ``` &theme=lightMode # Light theme (default) &theme=darkMode # Dark theme ``` ### redirectUrl URL to redirect users after transaction completion. Supports deep links for mobile apps.
> **Note:** Meld does not pass transaction completion data or status through redirect parameters. The redirectUrl is used only to navigate users back to your application. Use webhooks to track transaction status and completion. ``` &redirectUrl=https://yourapp.com/success # Website &redirectUrl=myapp://transaction-complete # Mobile deep link &redirectUrl=https://yourapp.com/crypto/complete # Custom success page ``` ## Field Locking Lock any parameter to prevent user editing by passing the parameter with the word "Locked" after it. Locking a field will make it so that the user cannot edit the values passed into those fields. Field locking only applies to the Meld Checkout interface. However, if you lock the `sourceCurrencyCode`/`destinationCurrencyCode` or `walletAddress` field in the Meld Checkout widget, that field will also be locked in the partner's widget for the providers that support it. See [White-Label customization → Locking fields](/docs/stablecoins/white-label-api-integration/whitelabel-api-guide/white-label-customization) for the list of providers. ### Lockable Parameters: The locked params are as follows: * `countryCodeLocked` - Pass the country code value (e.g., `countryCodeLocked=US`) * `paymentMethodTypeLocked` - Pass the payment method value (e.g., `paymentMethodTypeLocked=CREDIT_DEBIT_CARD`) * `sourceAmountLocked` - Pass the amount value (e.g., `sourceAmountLocked=100`) * `sourceCurrencyCodeLocked` - Pass the currency code value (e.g., `sourceCurrencyCodeLocked=USD`) * `destinationCurrencyCodeLocked` - Pass the crypto code value (e.g., `destinationCurrencyCodeLocked=BTC`) * `walletAddressLocked` - Pass the wallet address value (e.g., `walletAddressLocked=bc1qxy2k...`) * `themeLocked` - Pass the theme value (e.g., `themeLocked=darkMode`) ### Important Rules: * If you pass in both the unlocked and locked version of a param (for example `countryCode` and `countryCodeLocked`), the locked param will be used * Locked parameters take the actual value, not `=true` ### Field Locking Example: ``` meldcrypto.com/?publicKey=your_public_key&destinationCurrencyCodeLocked=BTC&walletAddressLocked=bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh ``` ## Complete Examples ### Basic Bitcoin Purchase ``` meldcrypto.com/?publicKey=your_public_key&sourceAmount=100&destinationCurrencyCode=BTC&countryCode=US ``` ### Locked Ethereum Purchase ``` meldcrypto.com/?publicKey=your_public_key&destinationCurrencyCodeLocked=ETH&walletAddressLocked=0x742c8f2e0ce07Dd3f7E78A31E5A97D45c50fF2c8&themeLocked=darkMode ``` ### Mobile App Integration ``` meldcrypto.com/?publicKey=your_public_key&externalCustomerId=user_12345&externalSessionId=session_abc789&redirectUrl=myapp://crypto-success&theme=lightMode ``` ### Locked BONK Purchase on Solana ``` meldcrypto.com/?publicKey=your_public_key&destinationCurrencyCodeLocked=BONK_SOLANA&walletAddressLocked=6chn9n4CLhNdyBpiLLj9ouUUUmEQYBLWfpUMMuHB9K3k&themeLocked=darkMode ``` ### Marketplace Token Sale ``` meldcrypto.com/?publicKey=your_public_key&destinationCurrencyCodeLocked=USDC&sourceAmountLocked=50&countryCodeLocked=US ``` ## Parameter Validation Rules ### Required Combinations: * **walletTag** requires **walletAddress** for supported cryptocurrencies (EOS, STX, XLM, XMR, XRP, BNB, XEM, and HBAR) * **customerId** and **externalCustomerId** are mutually exclusive - do not pass in both ### Important Parameter Rules: * **Locked parameters override unlocked versions**: If you pass in both the unlocked and locked version of a param (for example `countryCode` and `countryCodeLocked`), the locked param will be used * **Provider UI locking**: If you lock the sourceCurrencyCode/destinationCurrencyCode or walletAddress field in the Meld Checkout, then that field will also be locked in the partner's widget for the providers that support it * **Locked parameters take actual values**: Pass the actual value, not `=true` (e.g., `walletAddressLocked=6chn9n...` not `walletAddressLocked=true`) ### Invalid Parameter Handling: * **Unrecognized parameters** are ignored silently * **Invalid values** fall back to defaults * **Any param with unrecognized or unsupported values** will be ignored ## URL Encoding Always URL encode parameter values that contain special characters: ```javascript theme={null} const walletAddress = 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh'; const redirectUrl = 'https://myapp.com/success?transaction=complete'; const meldCheckoutUrl = 'meldcrypto.com/?publicKey=your_public_key'; const url = `${meldCheckoutUrl}&walletAddress=${encodeURIComponent(walletAddress)}&redirectUrl=${encodeURIComponent(redirectUrl)}`; ``` ## Testing Parameters Focus on successful transaction flows in sandbox environment: ``` meldcrypto.com/?publicKey=your_public_key&sourceAmount=10&destinationCurrencyCode=BTC&countryCode=US ``` Use small amounts and focus on green path flows to validate your integration. *** **Next:** [Customization Options](/docs/stablecoins/meld-checkout-integration/meld-checkout-guide/meld-checkout-customization) for advanced styling and configuration. # Quickstart Source: https://docs.meld.io/docs/stablecoins/meld-checkout-integration/meld-checkout-quickstart This is the 5 minute quickstart guide for executing the Meld checkout flow for the first time. This quickstart is for product teams and developers who want to launch Meld Checkout for the first time. You will get the hosted Meld Checkout URL from the dashboard and embed or redirect to it from your app so users can buy or sell crypto without any UI work on your side. # Meld Checkout Integration Quick Start **Time required:** 5 minutes **Difficulty:** No coding required ## Before you begin * Access to the Meld dashboard (sandbox and production are separate) * A page in your app where you can render a link or trigger a redirect **Sandbox vs production:** Each environment has its own Meld Checkout URL. Sandbox URLs only work with test data; production URLs handle real money. Always finish testing in sandbox first. ## Step 1: Get Your Meld Checkout URL The Meld Dashboard provides a complete, ready-to-use Meld Checkout URL. ### Process: 1. **Check your email** for the Meld dashboard invitation 2. **Click the invitation link** and log in 3. In the dashboard, **navigate to Developer tab** 4. **Find "Meld Checkout URL"** - this is your complete URL 1. Note that you will need Meld to enable this key for you. If you don't see it in the Meld dashboard, please reach out to your Meld account manager to request this being added. 5. **Copy and save** your Meld Checkout URL securely ### **Note:** You get a complete URL from the dashboard, not just a key. This URL is ready to use immediately! ## Step 2: Use Your Meld Checkout URL ### Your Dashboard URL (Ready to Use): ``` [Copy the complete URL from your Meld Dashboard Developer tab] ``` ### Add Optional Parameters for Customization: **See the [URL parameters guide](/docs/stablecoins/meld-checkout-integration/meld-checkout-guide/url-parameters) for all available parameters and options.** **See the [Customization guide](/docs/stablecoins/meld-checkout-integration/meld-checkout-guide/meld-checkout-customization) for all available styling options.** ``` meldcrypto.com/?publicKey=your_meld_checkout_key&sourceAmount=100&destinationCurrencyCode=BTC&countryCode=US ``` ### Integration Examples: **HTML Link:** ```html theme={null} Buy Crypto ``` **JavaScript Redirect:** ```javascript theme={null} function buyCrypto() { window.location.href = 'meldcrypto.com/?publicKey=your_meld_checkout_key'; } ``` ## Step 3: Test Your Integration ### Sandbox Testing: 1. **Use your Meld Checkout URL** (get from Developer tab in sandbox environment) 2. **Open the URL** in your browser 3. **Complete the test transaction** using [sandbox test credentials](/docs/stablecoins/sandbox-guide/test) 4. **Verify in dashboard** - check Transactions tab ## Step 4: Go Live ### Production Setup: 1. **Use your Meld Checkout URL** (get from Developer tab in production environment) 2. **Test with small real transaction** 3. **Start directing users** to the Meld Checkout widget! ✅ **Meld Checkout Integration Complete!** Users can now buy digital assets directly through your application. ➡️ **[Meld Checkout Customization Guide](/docs/stablecoins/meld-checkout-integration/meld-checkout-guide/meld-checkout-customization)** — styling and advanced options ## Next steps If you're ready to customize and complete your integration, see the end-to-end [Meld Checkout Guide](/docs/stablecoins/meld-checkout-integration/meld-checkout-guide).
# Sandbox Testing Guide Source: https://docs.meld.io/docs/stablecoins/sandbox-guide/index # Sandbox Environment Guide This guide is for developers and QA testing their Meld crypto integration before going live. It covers what the sandbox is for, what it cannot do, which providers have usable sandboxes, and the supported test combinations. Note that the countries, tokens, fiat currencies, and payment methods supported in sandbox are a small subset of the ones supported in production. We recommend you only test in sandbox with the below combinations for the best results: **Tokens** * BTC * ETH * USDC **Countries** * US * Europe **Payment Methods** * CREDIT\_DEBIT\_CARD * APPLE\_PAY * GOOGLE\_PAY **Fiat Currencies** * USD * EUR In production you will be able to use thousands of other combinations — see [Digital asset coverage](/docs/stablecoins/digital-asset-coverage).
## ⚠️ Important: Sandbox Purpose and Limitations ### What Sandbox is For The sandbox environment is designed for: * **Exploring transaction flows** and understanding how the process works * **Testing UI integration** and user experience * **Familiarizing yourself** with provider interfaces * **Initial development** and basic functionality testing * **Happy path combinations** of the most widely used tokens ### What Sandbox is NOT For **🚨 Critical Warning:** The sandbox environment should **NOT** be relied upon for: * **Coding solutions related to calculations** * **Production data accuracy validation** * **Finalizing business logic** based on API responses * **Performance testing** with accurate response times * **Coverage testing** because onramp sandboxes are very limited in the tokens they support **API responses in the sandbox environment may lack complete data accuracy** and should only be used for understanding flow structure, not for building calculation logic. ## 🌍 Environment Access | Environment | Widget URL | API Base URL | | -------------- | --------------------------- | ------------------------ | | **Sandbox** | `https://sb.meldcrypto.com` | `https://api-sb.meld.io` | | **Production** | `https://meldcrypto.com` | `https://api.meld.io` | ## 🏦 Sandbox-Friendly Providers ### ✅ Recommended for Testing (Full Sandbox Support) These providers offer reliable sandbox environments ideal for testing: * **Unlimit** - Complete sandbox functionality with test cards * **Simplex** - Reliable test environment with test wallet support * **TransFi** - Good sandbox with specific test credentials * **Transak** - Full-featured sandbox with test SSN/PAN numbers * **Sardine** - Phone-based testing with predictable OTP system ### ⚠️ Limited Sandbox Support These providers have sandbox limitations: * **Banxa** - Sandbox often rejects KYC/transactions; production testing recommended * **Stripe** - Functions like production; requires real payment for full testing * **Paybis** - Basic sandbox functionality * **BTC Direct** - Limited sandbox features ### ❌ Production Only These providers do not offer sandbox environments: * **Robinhood** - Production environment only * **Coinbase Pay** - Production environment only * **Blockchain.com** - Production environment only * **Alchemy Pay** - Production-like sandbox requiring real payments ## 🔄 Flow Testing Capabilities ### Buy Flow (Onramp) * ✅ **Fully testable** end-to-end in sandbox * Complete transaction simulation available * Test cards and credentials work for most providers * **Recommended starting point** for all integrations ### Sell Flow (Offramp) * ⚠️ **Limited testing** - quote generation only * Cannot complete full transactions in sandbox * Real cryptocurrency/fiat required for end-to-end testing * Use for UI flow testing only ### Transfer Flow * ⚠️ **Limited testing** - configuration and quotes only * Cannot execute actual transfers in sandbox * Real wallet addresses required for testing * Currently available in **White-Label API only** ## 🚀 Testing Best Practices ### 1. Start with Buy Flow * Begin all integrations with buy flow testing * Use recommended providers (Unlimit, Simplex, TransFi) * Complete full transaction flow before moving to other flows ### 2. Provider Selection for Testing * Choose providers from "Recommended for Testing" list * Avoid production-only providers during initial development * Test with multiple providers to understand variations ### 3. Sandbox Data Handling * **Never build calculation logic** based on sandbox responses * Use sandbox only for flow understanding and UI testing * Validate all calculations and business logic in production ### 4. Progressive Testing Approach ``` Sandbox (Flow Testing) → Staging (Integration Testing) → Production (Final Validation) ``` ## 🔧 Integration Testing Workflow 1. **Sandbox Flow Testing** * Test UI integration * Understand provider interfaces * Validate basic functionality 2. **Production Validation** * Test with real (small) transactions * Validate calculation accuracy * Confirm webhook functionality 3. **Go Live** * Deploy with confidence * Monitor real transaction data * Iterate based on production insights ## 📞 Support For sandbox-related questions or issues: * Check provider-specific testing credentials in the detailed testing guide * Review flow limitations before troubleshooting * Contact Meld support for integration guidance Remember: **Sandbox is for learning the flow, production is for validating the logic.** # White-Label API and Meld Checkout Testing Credentials Source: https://docs.meld.io/docs/stablecoins/sandbox-guide/test # Testing Overview This page is for developers and QA running sandbox transactions through Meld's White-Label API or Meld Checkout flows. It lists test cards, wallet addresses, KYC triggers, and quirks for each onramp's sandbox. ## Quick start **New to testing?** Start with the [Sandbox Environment Guide](/docs/stablecoins/sandbox-guide) to understand sandbox limitations and best practices. **Ready to test?** This page contains specific testing credentials for each provider. # Provider testing credentials Before testing, review the [Sandbox Environment Guide](/docs/stablecoins/sandbox-guide) to understand sandbox limitations. ## Testing Notes * Most providers have relaxed KYC requirements in sandbox * Some providers validate wallet addresses match the cryptocurrency type * Use test credentials provided below for each provider * Start with **Buy Flow testing** as it's fully supported in sandbox ## Unlimit Use the following test cards when testing in [Unlimit's](https://docs.gatefi.com/) sandbox. | Card Type | Card Number | Expiration | CVV | | :--------- | :------------------ | :--------- | --- | | Visa | 4000 0000 0000 0085 | 10/30 | 123 | | Mastercard | 5100 0000 0000 0065 | 10/30 | 123 | Use a real wallet address when testing, for example when trying to buy `ETH_GOERLI` (ETH on TestNet) use a real `ETH` wallet address. ## Banxa **While Banxa's sandbox does sometimes allow users to complete transactions, often KYC or transactions are rejected. Therefore Meld recommends testing in Banxa's production rather than its sandbox.** In Banxa's sandbox, the OTP is **7203**. Use the following test cards when testing in [Banxa's](https://docs.banxa.com/docs/testing-information) sandbox. | Card Type | Info | Card Number | Expiration | CVV | | :-------- | :--- | :------------------ | :--------- | --- | | Visa | US | 4111 1111 1111 1111 | 01/30 | 555 | Banxa allows real wallet addresses while testing in their sandbox. ## BTC Direct Use the following test cards when testing in [BTC Direct's](https://docs.adyen.com/development-resources/testing/) sandbox. | Card Type | Card Number | Expiration | CVV | | :--------- | :------------------ | :--------- | --- | | Visa | 4111 1111 4555 1142 | 03/30 | 737 | | Mastercard | 2222 4000 7000 0005 | 03/30 | 737 | BTC Direct allows real wallet addresses while testing in their sandbox. ## TransFi Use the following test cards when testing in [TransFi's](https://docs.transfi.com/docs) sandbox. | Card Name | Card Number | Expiration | CVV | | :-------- | :------------------ | :--------- | --- | | FL-BRW1 | 4000 0278 9138 0961 | 11/30 | 123 | Test in TransFi's sandbox using `BTC` and the wallet address `2N3oefVeg6stiTb5Kh3ozCSkaqmx91FDbsm`. ## Transak Use the following test cards when testing in [Transak's](https://docs.transak.com/docs/test-credentials) sandbox. | Card Type | Card Number | Expiration | CVV | | :-------- | :------------------ | :--------- | --- | | Visa | 4111 1111 1111 1111 | 10/33 | 123 | | Visa | 4485 1415 2054 4212 | 10/33 | 100 | For US: Use SSN number `000000001` to create a test account.\ For India: Use PAN number `ABCDE1234A` to place an INR order. Transak requires a wallet address that matches the token passed in, for example if you are testing with `BTC_TESTNET` then use wallet address `2N3oefVeg6stiTb5Kh3ozCSkaqmx91FDbsm`. ## Onramp Money In Onramp.Money's Sandbox, to get past the phone number screen, you can use the phone number `+91 8660031402` and the passcode `112233`. Use any real wallet address when testing, and Meld recommends testing with the token ETH. ## Fonbnk Fonbnk requires a real wallet address for whatever token is selected in their sandbox. Also, there is no test card available, since the payment methods Fonbnk supports are Bank Transfer and Airtime. Use Bank Transfer for testing purposes. ## Paybis Use the following test cards when testing in Paybis' sandbox. | Card Type | Card Number | Expiration | CVV | | :-------- | :------------------ | :--------- | --- | | Visa | 4111 1111 1111 1111 | 10/30 | 123 | Paybis requires a wallet address that matches the token passed in, for example if you are testing with `BTC_TESTNET` then use wallet address `2N3oefVeg6stiTb5Kh3ozCSkaqmx91FDbsm`. ## Koywe When testing in Koywe's sandbox, a single user will only be able to make transactions in 1 particular country. Koywe also requires real ID for it's sandbox, but it doesn't have to match the country of the transaction (for example a US ID will work even when making a Chile transaction). We recommend testing with Chile and the payment method Khipu in order for transactions to be completed successfully. Use the following values to complete a transaction. | Amount | Payment Method | RUT | Khipu Password | Khipu Secondary Codes | Bank Account | | :--------------------------------------------- | :------------- | :----------- | -------------- | :-------------------- | :----------- | | 20000 CLP (Khipu won't work if its over 25000) | Khipu | 28.666.939-5 | 1234 | 11, 22, 33 | Any | ## Robinhood Robinhood does not have a sandbox, only a production environment. Therefore you will have to make a real payment to fully test Robinhood's onramp. ## Coinbase Pay Coinbase Pay does not have a sandbox, only a production environment. Therefore you will have to make a real payment to fully test Coinbase Pay's onramp. ## Blockchain.com Blockchain.com does not have a sandbox, only a production environment. Therefore you will have to make a real payment to fully test Blockchain.com's onramp. ## Stripe Use the following test values when testing in [Stripe's](https://docs.stripe.com/crypto/integrate-the-onramp#test-mode-values) sandbox. | OTP | SSN | Address Line 1 | Card Number | Expiration | CVV | | :----- | :-------- | :------------------- | ------------------- | :--------- | :-- | | 000000 | 000000000 | address\_full\_match | 4242 4242 4242 4242 | 10/30 | 123 | If you are bringing over your own Stripe account: 1. please reach out to Stripe to enable `preferred_payment_method` and `source_total_amount` for your account, or else you will see errors. 2. When setting up Stripe webhooks, do the following steps in order: 1. Grab your Stripe API keys from the Stripe dashboard and put them in the Meld dashboard, putting in any value for the Webhook Secret. 2. Take the Meld webhook url from the Meld dashboard and go to the Stripe dashboard and set up webhooks to Meld. Select all events. 3. Take the generated Stripe webhook secret and go back to the Meld dashboard and edit that value to add that in so that webhooks work properly. ## Alchemy Pay Alchemy Pay's sandbox functions like a real production environment. Therefore you will have to make a real payment to fully test Alchemy Pay's onramp. ## Topper Use the following test information when testing [Topper](https://docs.topperpay.com/environments/) in sandbox. You can use rw2ciyaNshpHe7bCHo4bRWq6pqqynnWKQg for the token XRP\_XRPLEDGER and if prompted for a wallet tag use 224333255 | Card Type | Card Number | Expiration | CVV | | :-------- | :--------------- | :-------------- | ----------- | | Visa | 4921817844445119 | Any future date | Any 3 digit | ## Mercuryo Use the following test values when testing in Mercuryo's sandbox. | Card Type | Cardholder Name | Card Number | Expiration | CVV | | :-------- | :-------------- | :------------------ | :-------------- | --------------------------------- | | Visa | Cardholder Name | 4444 4444 4444 3333 | any future date | 123 | | Visa | Cardholder Name | 5555 4444 3333 1111 | any future date | CVV 123 for success, 555 for fail | Your IP address must be whitelisted by Mercuryo in order to launch their sandbox widget. ## Kryptonim Use the following test values when testing in Kryptonim's sandbox. | Card Type | Cardholder Name | Card Number | Expiration | CVV | | :-------- | :-------------- | :--------------- | :--------- | --- | | Visa | any name | 4929111260419572 | 05/2028 | 790 | ## Swapped Use the following test values when testing in Swapped's sandbox. | Card Type | Cardholder Name | Card Number | Expiration | CVV | | :-------- | :-------------- | :------------------ | :--------- | --- | | Visa | any name | 4929 4205 7359 5709 | 10/31 | 123 |
# Product 3: Virtual Account and Payouts Source: https://docs.meld.io/docs/stablecoins/virtual-account-integration/index Overview of the Virtual Account Flow This page is for product and engineering teams evaluating Meld's Virtual Account product. It explains what virtual accounts are, how onramp vs offramp flows work, and when this product is the right choice. For a step-by-step integration, jump to the [Virtual Account Quickstart](/docs/stablecoins/virtual-account-integration/virtual-account-quickstart). ## What are virtual accounts? A virtual account is a dedicated set of receiving details — a bank account number, IBAN, or crypto address — assigned to a specific customer. When funds arrive at those details, the provider automatically converts and settles them to the destination you configured (e.g. a crypto wallet for onramp, or a bank account for offramp). **Onramp (fiat to crypto):** The customer receives unique bank details (ACH routing + account number, SEPA IBAN, etc.). They deposit fiat from their bank; the provider converts it and delivers stablecoin to the specified wallet address. **Offramp (crypto to fiat):** The customer receives a crypto deposit address. They send stablecoin from their wallet; the provider converts and pays out fiat to the bank details on file. In this flow, typically a virtual account is not created per business / user unless necessary. Instead the onramp sends money directly from the provider's funding account to the business' / user's end bank account. Because each virtual account is unique to a customer, deposits are automatically attributed — no manual reference matching required. Meld orchestrates the provider integration, KYC, quoting, and settlement tracking so you work with a single API surface regardless of which provider (Noah, Due, etc.) handles the underlying rails. Please reach out to Meld for more information about virtual accounts and how to enable them.
# Full Integration Guide Source: https://docs.meld.io/docs/stablecoins/virtual-account-integration/virtual-account-guide This is the full end to end integration guide for integrating the virtual account flow. This is the end-to-end Virtual Account integration guide for developers. It walks through the complete API sequence — customer creation, KYC, quoting, order creation, and webhook tracking — that you'll need to build a production-grade onramp or offramp. ## Before you begin * You have completed the [Virtual Account Quickstart](/docs/stablecoins/virtual-account-integration/virtual-account-quickstart) end to end at least once in sandbox * You have a Meld API key with virtual-account access (sandbox first, production for go-live) * You have a webhook endpoint configured in **Developer → Webhooks** in the dashboard * If you are KYCing end users, you have at least one virtual-account provider (Noah, Due, or Brale) enabled on your account # Notes * This flow is supported for both businesses and individuals. Businesses will have to KYB with Meld, which is manual. Users will have to KYC via Meld with the onramp, which can be done via API, and is described below. * While both Noah and Due support this flow for both businesses and users, Brale only supports this flow for businesses at this time. Brale also requires an additional *redemption* step in the onramp flow in order for the business to receive their crypto. # Full Integration Guide This is the end-to-end guide for executing virtual account transactions (onramp and offramp) through the Meld API. > **API compatibility:** New fields may appear in responses without a version bump. Use flexible JSON parsing and do not reject unknown properties. Breaking changes ship under a dated `Meld-Version`. *** ## How it works 1. Create a **customer** in the Account API for the individual or business that will transact. 2. **KYB** your business with Meld. 3. If your end users will be making transactions, then **KYC** each customer with the provider (e.g. Noah, Due). Meld asks the provider to verify the customer; the provider responds asynchronously. 4. Get a **quote** from the Payment API for the currency pair and amount. 5. Create an **onramp order** (fiat to crypto) or **offramp order** (crypto to fiat). 6. The business or individual completes the fiat or crypto leg (bank transfer or wallet send). 7. Track progress via **webhooks** and the Transactions API. *** ## 1. Create a Customer API Endpoint: `POST /accounts/customers` **Request:** ```json theme={null} { "externalId": "your-internal-user-id-123", "name": { "firstName": "John", "lastName": "Doe" }, "email": "john@example.com", "phone": "+14155551234", "dateOfBirth": "1990-03-15", "type": "INDIVIDUAL" // or BUSINESS if you are a business } ``` **Response (abbreviated):** ```json theme={null} { "id": "WmYYgvN8ukpV62N3m4u3ee", "accountId": "W2aRZnYGPwhBWB94iFsZus", "externalId": "your-internal-user-id-123", "name": { "firstName": "John", "lastName": "Doe" }, "email": "john@example.com", "phone": "+14155551234", "dateOfBirth": "1990-03-15", "status": "ACTIVE", "type": "INDIVIDUAL", "serviceProviderCustomers": [], "addresses": [] } ``` Save the returned `id` — you will pass it as `customerId` on every subsequent call. If you plan on using `DUENETWORK` later for executing transactions, you must also specify the customer's address by calling `POST /accounts/customers/{customerId}/addresses`. > **Note:** Order and transaction requests also accept optional `subaccountCustomerId` and `externalSubaccountCustomerId` fields. Use these to tag which sub-account or business grouping a transaction belongs to for reporting and filtering purposes. *** ## 2. KYC / KYB If you are transacting as a business, you will KYB with Meld. If your end users are transacting, each will have to KYC once with each onramp they use. KYC happens via Sumsub. If you have already KYCed your users with Sumsub, you can pass in the KYC token — see [You KYC the user](/docs/stablecoins/unified-kyc/you-kyc-the-user). If you'd prefer Meld to KYC the user, that process is described in [Meld KYCes the user](/docs/stablecoins/unified-kyc/meld-kyces-the-user). To test KYC in the sandbox, follow the steps in [KYC testing](/docs/stablecoins/sandbox-guide/kyc-testing). **Initiate KYC:** API endpoints: * `POST /accounts/customers/{customerId}/kyc/initiate` * `PATCH /accounts/customers/{customerId}/kyc/initiate` **Request:** ```json theme={null} { "serviceProvider": "SUMSUB", "mode": "HOSTED_URL" } ``` Alternatively, if you already have a user who has passed [KYC on your Sumsub account](/docs/stablecoins/unified-kyc/you-kyc-the-user), you can import it into Meld by sharing the Sumsub applicant token (`TOKEN_IMPORT` mode). To import the KYC of the user, call `POST /accounts/customers/{customerId}/kyc/initiate`. The `customerId` is in the path, and your request body looks like this: ```json theme={null} { "serviceProvider": "SUMSUB", "mode": "TOKEN_IMPORT", "serviceProviderDetails": { "kycToken": "_act-sbx-jwt-eyJhbGciOiJub25lIn0.eyJqdGkiOiJfYWN0LXNieC1mNGFlNWYwYy1iYjFmLTQwZmUtYTk1YS1jZWM0MmY3OTBjYmIiLCJ1cmwiOiJodHRwczovL2FwaS5zdW1zdWIuY29tIn0.", "applicantId": "Sumsub applicant id" } } ``` **Response:** ```json theme={null} { "customerId": "WmYYgvN8ukpV62N3m4u3ee", "serviceProvider": "SUMSUB", "status": "PENDING", "url": "https://kyc-provider.example.com/verify/abc123" } ``` Direct the user to the `url` to complete verification. If you are not passing in a token, Sumsub will ask the user for all required fields, and if the token is passed in, Sumsub will only prompt the user for any missing fields. KYC completes **asynchronously** — the provider notifies Meld via webhook, and Meld publishes a `CUSTOMER_KYC_STATUS_CHANGE` webhook to your endpoint. **Sample KYC webhook payload:** ```json theme={null} { "eventType": "CUSTOMER_KYC_STATUS_CHANGE", "eventId": "BBxyzABC123456defGHIjk", "timestamp": "2025-02-24T14:20:00.000000Z", "accountId": "WQ5RyhdFzE45qjsomdzQ1u", "version": "2025-03-01", "payload": { "customerId": "WmYYgvN8ukpV62N3m4u3ee", "status": "APPROVED" } } ``` **Poll for KYC Status:** API Endpoint: `GET /accounts/customers/{customerId}` Check `serviceProviderCustomers[].kyc.status`. Values: `PENDING`, `APPROVED`, `REJECTED`, `EXPIRED`, `UNKNOWN`. Wait for the kyc.status to be `APPROVED` before proceeding. The customer must be KYC-approved before any transactions can be created. In both KYC initiation modes (**HOSTED\_URL** and **TOKEN\_IMPORT**), you can optionally specify Virtual Account providers you want to share the user's KYC with after Sumsub approves submission, via the `kycShareProviders` field. Alternatively, you can add share providers later by calling `PATCH /accounts/customers/{customerId}/kyc/initiate`. ```json theme={null} { "serviceProvider": "SUMSUB", "mode": "HOSTED_URL", "kycShareProviders": ["NOAH", "DUENETWORK"] } ``` ```json theme={null} { "serviceProvider": "SUMSUB", "mode": "TOKEN_IMPORT", "kycShareProviders": ["NOAH", "DUENETWORK"], "serviceProviderDetails": { "kycToken": "_act-sbx-jwt-eyJhbGciOiJub25lIn0.eyJqdGkiOiJfYWN0LXNieC1mNGFlNWYwYy1iYjFmLTQwZmUtYTk1YS1jZWM0MmY3OTBjYmIiLCJ1cmwiOiJodHRwczovL2FwaS5zdW1zdWIuY29tIn0.", "applicantId": "Sumsub applicant id" } } ``` After Sumsub verifies the user's submission, the KYC token will be automatically shared with all specified share Virtual Account providers, effectively onboarding the user on Virtual Account's platforms. The Virtual Account provider may require additional KYC for users in case the data shared by Sumsub is insufficient. To track this, you can either listen for `CUSTOMER_KYC_STATUS_CHANGE` webhooks. **Supported share providers and their requirements** * Noah * Due Network. Requires customer country to be specified — call `POST /accounts/customers/{customerId}/addresses` to add an address. **Expected Webhook Response** ```json theme={null} { "eventType": "CUSTOMER_KYC_STATUS_CHANGE", "eventId": "customerId", "timestamp": "2022-02-24T16:36:41.717262Z", "payload": { "accountId": "WeP9eoFziQX4yXE5abcfec", "customerId": "customerId", "serviceProvider": "SUMSUB", "status": "APPROVED", "kycRecepient": { "serviceProvider": "Virtual account provider", "status": "PENDING" // PENDING, REJECTED, APPROVED }, "statusUpdatedAt": "2022-02-24T16:36:41.717262Z" } } ``` After receiving a webhook event, you can call `GET /accounts/customers` for more details. For example, when the Virtual Account provider requires additional KYC on their platform, you will receive a webhook event with `kycRecipient.status=PENDING`. Then, when calling `GET /accounts/customers`, you will receive the following response: ```json theme={null} { "id": "customerId", "accountId": "accountId", "serviceProviderCustomers": [ { "serviceProvider": "Virtual Account Provider", "id": "provider customer id", "kyc": { "status": "PENDING", "updatedAt": "2026-04-08T20:04:20.457281Z", "additionalInfo": { "HostedURL": "https://virtual-provider-paltform.com/additionalKycHostedURL" // addtional provider specific kyc fields, like addtional requirements (documents, questionnaire, etc.) }, "onboardingMethod": { "kycProvider": "SUMSUB", "onboardingType": "KYC_TOKEN_SHARE" } } } ], "status": "ACTIVE", "type": "INDIVIDUAL" } ``` You will need to direct your user to `HostedURL` so they can pass additional KYC on the Virtual Account provider's platform. After users complete additional KYC and the Virtual Account provider verifies it, you will receive another `CUSTOMER_KYC_STATUS_CHANGE` event with `kycRecipient.status=APPROVED`. The new status will also be reflected in `GET /accounts/customers` responses. **Errors:** * Calling `initiate` again for the same customer + provider returns **409**. *** ## 3. Configure webhooks In the Meld dashboard (**Developer > Webhooks**), add your endpoint URL and subscribe to at minimum: * `TRANSACTION_CRYPTO_PENDING` * `TRANSACTION_CRYPTO_TRANSFERRING` * `TRANSACTION_CRYPTO_COMPLETE` * `TRANSACTION_CRYPTO_FAILED` * `CUSTOMER_KYC_STATUS_CHANGE` You can find all webhook events in [Webhook events](/docs/stablecoins/for-all-products/webhook-events). Verify webhook signatures per [Webhook authentication](/docs/stablecoins/for-all-products/webhooks-authentication). *** ## 4. Get a Quote API Endpoint: `POST /payments/virtual-account/crypto/quote` Note that the provider Brale doesn't support quotes. All Brale transactions are 1:1, aka for \$100 you will receive 100 USDC. **Request:** ```json theme={null} { "countryCode": "US", "sourceAmount": 100, "sourceCurrencyCode": "USD", "destinationCurrencyCode": "USDC", "paymentMethodType": "ACH", "customerId": "WmYYgvN8ukpV62N3m4u3ee" } ``` For a **sell** quote, swap the currency codes: `sourceCurrencyCode` is the crypto, `destinationCurrencyCode` is the fiat. **Response (abbreviated):** ```json theme={null} { "quotes": [ { "transactionType": "CRYPTO_PURCHASE", "sourceAmount": 100, "sourceCurrencyCode": "USD", "destinationAmount": 99.10, "destinationCurrencyCode": "USDC", "totalFee": 0.90, "networkFee": 0.00, "transactionFee": 0.90, "exchangeRate": 1.0, "paymentMethodType": "ACH", "serviceProvider": "NOAH" } ] } ``` **Key fields per quote:** | Field | Meaning | | ------------------- | ---------------------------------------------------- | | `sourceAmount` | What the user spends (fiat for buy, crypto for sell) | | `destinationAmount` | What the user receives | | `totalFee` | Total fees in the fiat currency | | `serviceProvider` | Provider backing this quote | | `transactionType` | `CRYPTO_PURCHASE` (buy) or `CRYPTO_SELL` (sell) | Present quotes to the user. Capture the selected `serviceProvider` for the next step. *** ## 5. Create an Order ### Buy (onramp) — fiat to crypto API Endpoint: `POST /payments/virtual-account/ramp/onramp/order` Use the `customerId` from Step 1. The `serviceProvider` comes from the quote the user selected. **Request:** ```json theme={null} { "customerId": "WmYYgvN8ukpV62N3m4u3ee", "sourceAmount": 100, "sourceCurrencyCode": "USD", "destinationCurrencyCode": "USDC", "destinationWalletAddress": "0x51FB80013111111111111112121111111", "paymentMethodType": "ACH", "serviceProvider": "NOAH" } ``` **Response:** ```json theme={null} { "orderId": "WeP9eoFziQX4yXE5abcfec", "paymentMethodType": "ACH", "receivingBankInformation": { "accountNumber": "9876543210", "routingNumber": "021000021" }, "serviceProviderDetails": {} } ``` The user sends fiat to the returned bank details from their banking app. The provider settles crypto to `destinationWalletAddress` after receiving the funds. ### Sell (offramp) — crypto to fiat API Endpoint: `POST /payments/virtual-account/ramp/offramp/order` **Request:** ```json theme={null} { "customerId": "WmYYgvN8ukpV62N3m4u3ee", "sourceAmount": 100, "sourceCurrencyCode": "USDC", "destinationCurrencyCode": "USD", "sourceWalletAddress": "0x51FB80013111111111111112121111111", "paymentMethod": { "type": "ACH", "owner": "John Doe", "details": { "accountType": "CHECKING", "routingNumber": "1111111111", "accountNumber": "22222222", "bankName": "Chase Bank", "beneficiaryAddress": { "street_line_1": "123 Market St.", "street_line_2": null, "city": "San Francisco", "state": "CA", "postalCode": "94115", "country": "US" }, "bankAddress": { "street_line_1": "270 Park Avenue", "street_line_2": null, "city": "New York", "state": "NY", "postalCode": "10017", "country": "US" } } }, "serviceProvider": "NOAH" } ``` **Offramp `paymentMethod.type` options:** | Type | Details schema | | ----------------------------- | ------------------------------------------------------------------------------------------------ | | `ACH` / `LOCAL_BANK_TRANSFER` | `accountType`, `routingNumber`, `accountNumber`, `bankName`, `beneficiaryAddress`, `bankAddress` | | `SEPA` | `iban` | **Response:** ```json theme={null} { "orderId": "WfR7abcDEF12345xyz9876", "paymentMethodType": "ACH", "walletAddress": "0xABCDEF1234567890ABCDEF1234567890ABCDEF12" } ``` The user sends crypto to `walletAddress` from their wallet. The provider pays out fiat to the bank details after receiving the crypto. Make sure the user sends the correct amount of crypto as in the Create Sell Order API call. *** ## 6. Track the Transaction ### Webhooks Meld sends webhooks as the transaction progresses. Correlate to your order using `virtualAccountRampOrderId` in the payload (matches the `orderId` from the order response). Find more information on the various webhook event types in [Webhook events](/docs/stablecoins/for-all-products/webhook-events). ### Sample webhook payload (buy) ```json theme={null} { "eventType": "TRANSACTION_CRYPTO_PENDING", "eventId": "AAsuLXHXD3mS1cjNBuHHzv", "timestamp": "2025-02-24T16:36:41.717262Z", "accountId": "WQ5RyhdFzE45qjsomdzQ1u", "profileId": "W9ka8vLE4ufBkSg3BEciZb", "version": "2025-03-01", "payload": { "virtualAccountRampOrderId": "WeP9eoFziQX4yXE5abcfec", "paymentTransactionId": "WePZCYJW7cdXR7SxUMp8mE", "customerId": "WmYYgvN8ukpV62N3m4u3ee", "externalCustomerId": "your-internal-user-id-123", "paymentTransactionStatus": "PENDING", "transactionType": "CRYPTO_PURCHASE", "sessionId": "WePjVaT4iBHPpqW49F419x" } } ``` For sell transactions, `transactionType` is `CRYPTO_SELL`. The `paymentTransactionStatus` field shows the current status of the transaction. Find the list of transaction statuses and their meanings in [Transaction statuses](/docs/stablecoins/for-all-products/transaction-statuses). ### Fetch the full transaction Extract `paymentTransactionId` from the webhook and call: API Endpoint: `GET /payments/transactions/{paymentTransactionId}` **Sample response:** ```json theme={null} { "id": "WePZCYJW7cdXR7SxUMp8mE", "accountId": "W2aRZnYGPwhBWB94iFsZus", "transactionType": "CRYPTO_PURCHASE", "status": "SETTLED", "sourceAmount": 100.00, "sourceCurrencyCode": "USD", "destinationAmount": 99.10, "destinationCurrencyCode": "USDC", "paymentMethodType": "ACH", "serviceProvider": "NOAH", "serviceTransactionId": "noah-tx-abc123", "orderId": "WeP9eoFziQX4yXE5abcfec", "customer": { "id": "WmYYgvN8ukpV62N3m4u3ee", "externalId": "your-internal-user-id-123" }, "countryCode": "US", "sessionId": "WePjVaT4iBHPpqW49F419x", "externalSessionId": null, "createdAt": "2025-02-24T16:36:41.000000Z", "updatedAt": "2025-02-24T17:05:12.000000Z" } ``` The `orderId` on the transaction matches `virtualAccountRampOrderId` in the webhook and the `orderId` from your order response, tying all three together. *** ## Sequence diagram ![Virtual Account Integration Flow](https://www.plantuml.com/plantuml/png/tLJHZjf657ttLrnjNn8fb03B2aNQAiRO17KNN61JhPeA3VO5HfWPPsOCfQkL-jGFgFg5-PBE3AQsPQ9kNcjBFCmvzzvpxXdpNYeYLBPB7BcbB2M2x42WJ3cJU8zIaZNsCU47LmX-02KoB16N9Dgk1SzOxF642_WkyCrROiWDYVc1iZMiI2BHAKFuEKCM8Jmv0BPztgHZd_DXm9cQqTyHMgtoKSwzjey6xkWAxoZ3FXSnYXprx5D6QuAxvdKq9IH2qOYcXAbAZ_RWndFRLKAjZTyBMa6lIYgfRBcTGTUhmpDrJ12MF8chM4ZYsEoirNQCvqXnKT7KzSnxXkgctfht39j1rSrgPAZvwIMVEVG1IOpbqi0-PxY-W7xG2TnwjKakjp6WUwfFBm_DmLhVmOr_4tGBGdsf1LHTQ7vUbV5Sle3855L7U_qgedhsCQUrnVQ2jxwvcIKPNU_QRRkcSre425UFF1FW9LGFkG8YWJA5Qq4K5mungS2Lps9ua2Wid4aKrGPk5Ed92jnmP1aaYbILyUEZ1w7WrgIbR8zHbH6IumvBFL9oT7BNLtt2jj04RqCeiw0WDBdWuMTSRZZVVc9QRyffRkUmSPpRMvWfJA45N_xu6po1H25ucLHyXm9kRnsdC0sD7wm3U0VXP3Q79ZdPayqeBv2EnHkQM9Hf-XFG-1sSMQCOdel9RJlPxE4Z2hgY4L6Ki_yONKniFj7ukQctrgICAaeKVbL39f8t81LHcTHj_F5WD1uQ9IOtCq0tqJoFxz845SA-B6TFYQbt3ATH4fD71n594lDF2g-8FBBeTI7Yi35D7sRpoRq_NMYVYzdSdJ_C5ju7TppqBviS-ZMFdvYU_Vr9ljuEk64udNpq1yKcEtQJGrJmKyOLrbTexiw_6znJeJAIDF8GcvzDZMCOjTLPIFxoQp7gcjXP4oR8BeLNAqeKVFdpBuZ4CLNyjHrjlhhK_glq_3xqiwC3y78vtBPks6_Q0KEz5Ac5H_4R41WXtLEsDf_4YAsusDdWJBu0USBsyz1rCsGnMCsAGpuObheGykaEdAYDNAGfKIf1yo6MnDXevrDtF7Ez-MWoNdZJnt0-MUHtwmLCUnJuq-dBwDxaFhppv_x5JUQtHWd298cCUNrnr6-Z-DdHValPBK_PhLQdy4wPcVZiJYhX1K7GyTzgzqdoRUcPvjyGtu4a2IetZWLA-VNCSgcALml-Zt6zrp_PBla7) ## Testing Provider sandboxes often cannot complete full settlement — you may only reach virtual bank account creation. See [Virtual account flow testing credentials](/docs/stablecoins/sandbox-guide/virtual-account-flow-testing-credentials) for provider-specific sandbox values (Noah, Due, Brale). ## Next steps ### Testing your implementation * [**Virtual account testing credentials**](/docs/stablecoins/sandbox-guide/virtual-account-flow-testing-credentials) — provider-specific sandbox values ### Advanced features * [**Webhook events**](/docs/stablecoins/for-all-products/webhook-events) — real-time updates * [**Dashboard data**](/docs/stablecoins/additional-information/dashboard-data) — track performance ### Production deployment * [**Service-provider setup**](/docs/stablecoins/for-all-products/service-provider-setup) — configure additional onramps *** ## Support & resources * [**Postman collection**](https://www.postman.com/meldeng/workspace/meld-io-public-api-collection) — test APIs directly * [**FAQ**](/docs/stablecoins/faq) — common questions answered
# Product 1: White-Label API Source: https://docs.meld.io/docs/stablecoins/white-label-api-integration/index Overview of the White-Label API Integration Flow The White-Label API integration is for developers and product teams who want to build their own UI on top of Meld's APIs to let users buy, sell, or transfer crypto. The transaction itself — including KYC and payment — happens in the onramp's UI; you control everything before and after. This is the most popular choice due to its UI flexibility. 1. For the 30-minute quickstart guide that gets you through the flow for the first time, see the [White-Label Quickstart](/docs/stablecoins/white-label-api-integration/whitelabel-quickstart). 2. Once you have completed the quickstart, see the [end-to-end White-Label API Guide](/docs/stablecoins/white-label-api-integration/whitelabel-api-guide) for the full integration walkthrough. If you're still deciding between products, the [Overview](/docs/stablecoins/crypto-overview_) compares White-Label API, Meld Checkout, and Virtual Account side by side. # Full Integration Guide Source: https://docs.meld.io/docs/stablecoins/white-label-api-integration/whitelabel-api-guide/index This is the full end to end integration guide for integrating the white-label API flow. # Build Your Own UI with Meld APIs This guide is for developers building a custom cryptocurrency interface on top of Meld's White-Label API. You'll learn the exact sequence of API calls — from detecting the user's country to launching the provider widget and tracking the transaction — needed to ship a production-ready buy/sell/transfer flow with your own UI. ## Before you begin * ✅ **API key** from the Meld dashboard (see [White-Label Quickstart](/docs/stablecoins/white-label-api-integration/whitelabel-quickstart) Step 1) * ✅ **Webhook endpoint** configured (recommended; you can also poll the API) * ✅ **Basic understanding** of REST APIs and JSON * ✅ **Sandbox environment** — base URL `https://api-sb.meld.io` Sandbox and production credentials, base URLs, and widget URLs are separate. Develop against sandbox until your flow is ready, then switch credentials to go live. **Important:** This is White-Label API Integration, NOT Meld Checkout 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 ## Technical Architecture ``` ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Your UI │◄──►│ Meld APIs │◄──►│ Provider UI │───►│ Your UI │ │ │ │ │ │ (Widget) │ │ │ ├─────────────┤ ├─────────────┤ ├─────────────┤ ├─────────────┤ │ • Quotes │ │ • Pricing │ │ • Payment │ │ • Status │ │ • Selection │ │ • Sessions │ │ • KYC │ │ • Results │ │ • Launch │ │ • Webhooks │ │ • Compliance│ │ • Tracking │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ ``` ## 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): ```javascript theme={null} // 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: ```javascript theme={null} // Sample Code: Get supported countries list with flags GET /service-providers/properties/countries?accountFilter=true ``` ### For US Users - State Requirements: ```javascript theme={null} // 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 ``` ![](https://files.readme.io/f8d4f0f7f888b118578747b0f9b21352d1478ef730a2eba53e1476a492c0cdb7-ezgif-38c25fa2d98f23.gif) *Screenshot: Meld Widget showing automatic country detection with manual override option* ### API Response Example: ```json theme={null} { "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: ```javascript theme={null} // Sample Code: Get default currency and payment method for country GET /service-providers/properties/defaults/by-country?countries={countryCode} ``` **Response:** ```json theme={null} [ { "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: ```javascript theme={null} // Sample Code: Get all available fiat currencies for the country GET /service-providers/properties/fiat-currencies?countries={countryCode}&accountFilter=true ``` ### Get Payment Methods: ```javascript theme={null} // Sample Code: Get payment methods available for selected fiat currency GET /service-providers/properties/payment-methods?fiatCurrencies={fiatCurrency}&accountFilter=true ``` ### UI Implementation: ```javascript theme={null} // 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); ``` ![](https://files.readme.io/79ce5b8f16fe7ced3dafc545083e92b7cf374c3e1d3c643d83e409c6e36245e9-Screenshot_2025-09-02_at_2.08.10_PM.png) *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: ```javascript theme={null} // Sample Code: Get available cryptocurrencies for the country GET /service-providers/properties/crypto-currencies?countries={countryCode}&accountFilter=true ``` ### Response Example: ```json theme={null} { "cryptoCurrencies": [ { "currencyCode": "BTC", "currencyName": "Bitcoin", "networkCode": "BTC", "networkName": "Bitcoin" }, { "currencyCode": "ETH_ETHEREUM", "currencyName": "Ethereum", "networkCode": "ETHEREUM", "networkName": "Ethereum" } ] } ``` ### UI Implementation: ```javascript theme={null} // 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) })); } ``` ![](https://files.readme.io/d8047750901e8d2f687baaac9e44fac18736c63dc14d64583e7fbb8a3b799db3-ezgif-34fa46603b5728.gif) *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: ```javascript theme={null} // Sample Code: Get purchase limits for validation GET /service-providers/limits/fiat-currency-purchases?accountFilter=true ``` ### Response Example: ```json theme={null} { "limits": { "USD": { "minAmount": 20, "maxAmount": 20000, "dailyLimit": 5000, "monthlyLimit": 20000 } } } ``` ### User Input Validation: ```javascript theme={null} // 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 }; } ``` ![](https://files.readme.io/d13ccd4e7660f6c04d85a78f18419d945245c65ceff5e2ed4b2c7e39cf7c3896-Screenshot_2025-09-02_at_2.12.41_PM.png) *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. It is important that you tie each user to a Meld customerId or externalCustomerId (they map 1:1). This will ensure that the rampIntelligence suggests the best quote for the user. ### API Call: ```javascript theme={null} // Sample Code: Get real-time quotes from multiple providers POST /payments/crypto/quote ``` ### Request Body: ```json theme={null} { "externalCustomerId": "user_123", "sourceAmount": "200", "sourceCurrencyCode": "USD", "destinationCurrencyCode": "ETH", "countryCode": "US", "walletAddress": "0xfCFAa8059080D01b27ccA2B1fA086df0853397E6" } ``` ### Response Example: ```json theme={null} { "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, "partnerFee": 2, "destinationAmount": 0.04308219, "destinationCurrencyCode": "ETH", "exchangeRate": 4642.290, "paymentMethodType": "CREDIT_DEBIT_CARD", "serviceProvider": "UNLIMIT", "rampIntelligence": { "rampScore": 20.00, "lowKyc": false } }, { "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, "partnerFee": 1.96, "destinationAmount": 0.042577, "destinationCurrencyCode": "ETH", "exchangeRate": 4697.4, "paymentMethodType": "CREDIT_DEBIT_CARD", "serviceProvider": "ROBINHOOD", "rampIntelligence": { "rampScore": 19.76, "lowKyc": false } } ], "message": null, "error": null, "timestamp": null } ``` ### Key Response Fields Explained: * **`rampScore`**: Meld's recommendation score (higher = better conversion likelihood) * **`lowKyc`**: Whether provider requires minimal identity verification ![](https://files.readme.io/5e8d9532c381286e3ea1edeff72364e228c23ea704a8f6ce985fce53394d4966-ezgif-352c80746562fc.gif) *Screenshot: Meld Widget showing multiple provider quotes with ranking* ## 🏆 **MELD RECOMMENDED QUOTE RANKING** **Use Meld's`rampScore` for optimal conversion rates!** Meld provides a `rampScore` 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: ```javascript theme={null} // Sample Code: Use Meld's recommended ranking for best results function rankQuotesByMeldScore(quotes) { return quotes.sort((a, b) => { // Primary: Sort by rampScore (higher = better) if (a.rampScore !== b.rampScore) { return b.rampScore - a.rampScore; } // Secondary: Sort by destinationAmount (more crypto = better) return b.destinationAmount - a.destinationAmount; }); } ``` ### **Best Practice:** Always display the highest `rampScore` quote first to maximize transaction success rates and user satisfaction. 📊 **Learn More:** See [Ramp Intelligence](/docs/stablecoins/white-label-api-integration/whitelabel-api-guide/ramp-intelligence) 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: ```javascript theme={null} // Sample Code: Create widget session to get provider URL POST /crypto/session/widget ``` ### Request Body: ```json theme={null} { "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: ```json theme={null} { "id": "WePLapZetkn1hfeKFScf3T", "externalSessionId": "session_456", "externalCustomerId": "user_123", "customerId": "WXEX4DsAX7cp6Ch78oq2w3", "widgetUrl": "https://meldcrypto.com?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "serviceProviderWidgetUrl": "https://transak.com/?sessionId=asjknakjnjknwejksdnjkdsjkfnsd", "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." } ```
Note that the `widgetUrl` returns a Meld url, which iframes the onramp's url (if allowed) or does a full redirect to the onramp's url for the onramps that block iframing. The `serviceProviderWidgetUrl` directly returns the url of the onramp. You can choose to launch either one, based on your preference. We recommend using the `serviceProviderWidgetUrl` instead of the `widgetUrl` **Recommendation:** ### Provider UI Launch Implementation: #### Desktop Implementation: ```javascript theme={null} // Sample Code: Launch provider UI in popup window function launchProviderUI(sessionResponse, options = {}) { const popup = window.open( sessionResponse.serviceProviderWidgetUrl, '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: ```javascript theme={null} // Sample Code: Launch provider UI on mobile function launchMobileProviderUI(sessionResponse) { // For webviews if (isMobileApp()) { window.ReactNativeWebView?.postMessage( JSON.stringify({ type: 'OPEN_WEBVIEW', url: sessionResponse.serviceProviderWidgetUrl }) ); } else { // Mobile browser - full redirect window.location.href = sessionResponse.widgetUrl; } } ``` ![](https://files.readme.io/c36806bc626f1b6cdbc3d19d59c327e5857af4e9afefb645d7cbd00d871e2302-ezgif-358045d7123d01.gif) *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. You will receive webhooks from Meld every time a transaction is created or updated. To learn more about the various Meld webhook events and see payload examples, see [Webhook events](/docs/stablecoins/for-all-products/webhook-events). ### Webhook Integration: ```javascript theme={null} // 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: ```javascript theme={null} // 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.'; } ``` ### **Status Reference:** See [Transaction Statuses](/docs/stablecoins/for-all-products/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): ```javascript theme={null} 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): ```javascript theme={null} // Always fetch fresh: - Crypto quotes - Widget session creation - Transaction details - Transaction status ``` ### Implementation Example: ```javascript theme={null} 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: ```javascript theme={null} 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 * **[Sandbox testing credentials](/docs/stablecoins/sandbox-guide/test)** — test cards, wallets, and KYC triggers ### Advanced features * **[Webhook events](/docs/stablecoins/for-all-products/webhook-events)** — real-time updates * **[Dashboard data](/docs/stablecoins/additional-information/dashboard-data)** — track performance * **[Ramp Intelligence](/docs/stablecoins/white-label-api-integration/whitelabel-api-guide/ramp-intelligence)** — improve success rates ### Production deployment * **[Service-provider setup](/docs/stablecoins/for-all-products/service-provider-setup)** — configure additional onramps *** ## Support & resources * **[FAQ](/docs/stablecoins/faq)** — common questions answered *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.* # Ramp Intelligence Source: https://docs.meld.io/docs/stablecoins/white-label-api-integration/whitelabel-api-guide/ramp-intelligence # Ramp Intelligence Meld's intelligent quote ranking system maximizes conversion rates by guiding users toward the most suitable payment providers. Through advanced algorithms analyzing user history, provider performance, and KYC requirements, Meld helps developers achieve significantly higher success rates. ### **Proven Results** Meld's rampScore and lowKyc optimization features have enabled developers to achieve **69-94% end-to-end conversion rates** - a dramatic improvement over industry averages. ## Overview Every quote response includes intelligent ranking data to help you: * **Prioritize familiar providers** where users have succeeded before * **Identify low-friction options** requiring minimal verification * **Optimize for user success** based on historical performance * **Reduce abandonment rates** through smart provider selection ### **Scope**: These optimization features apply to buy transactions only and are included automatically in all quote responses. ## Understanding KYC Impact **Know Your Customer (KYC)** verification significantly impacts conversion rates. Each provider has different requirements: * **Minimal KYC**: Email, phone, basic details (high conversion) * **Standard KYC**: Document uploads, identity verification (medium conversion) * **Enhanced KYC**: Bank statements, proof of funds (low conversion) Meld's optimization ensures users are directed to providers where they've already completed verification, dramatically reducing friction. ## Key Optimization Parameters Every quote includes these conversion-critical fields: ### `rampScore` (0-100) Meld's proprietary recommendation engine that predicts conversion likelihood for each user-provider pair. Higher scores indicate greater success probability. Meld recognizes users by their wallet address, so this score works across the entire Meld ecosystem, even if your user used the same wallet address via a different wallet before! ### `lowKyc` (boolean) Indicates whether the transaction can complete without document uploads for new users. ### Calculation Factors Meld's algorithm processes multiple data points, updated several times daily: **User History (Primary Factor)** * Previous successful transactions with each provider * Transaction recency and frequency * Failure patterns and recovery behavior * Cross-wallet transaction history within Meld network **Provider Performance** * Historical success rates by geography and payment method * Average processing times and reliability metrics * Fee competitiveness and spread analysis * Payment method diversity and regional support **Market Dynamics** * Real-time provider availability and capacity * Regional regulatory compliance status * Seasonal performance variations ### Score Interpretation | Score Range | Meaning | Recommendation | | ----------- | ------------------- | ------------------------------------------------- | | 70-100 | **Excellent Match** | Multiple recent successes, prioritize prominently | | 30-69 | **Good Match** | Some success history, feature positively | | 0-29 | **New/Unknown** | No significant history, standard positioning | **Implementation Note**: Always include the user's `walletAddress` in quote requests to enable personalized scoring. ### Implementation Strategy **Primary Sorting**: Always rank quotes by `rampScore` (highest first) **Visual Emphasis**: Highlight providers with scores > 30 using: * "⭐ Recommended" badges * Primary button styling * "You've used this before" messaging **Code Example**: ```typescript theme={null} interface Quote { rampScore: number; serviceProvider: string; lowKyc: boolean | null; partnerFee: number | null; sourceAmount: number; destinationAmount: number; totalFee: number; // ... other fields } function sortQuotesByRecommendation(quotes: Quote[]): Quote[] { return quotes.sort((a, b) => b.rampScore - a.rampScore); } function getRecommendationBadge(score: number): string { if (score >= 70) return "🏆 Your Best Choice"; if (score >= 30) return "⭐ Recommended"; return ""; } ``` ### **Best Practice**: Never hide or de-emphasize high-scoring options, even if they have higher fees. User familiarity typically outweighs small price differences in conversion impact. ## Low KYC Optimization The `lowKyc` flag identifies providers where new users can complete transactions without document uploads, significantly reducing friction and improving conversion rates. ### What Low KYC Means **✅ Low KYC Requirements:** * Basic information: email, phone, name * Simple verification: SMS codes, email confirmation * Optional: SSN or tax ID (varies by country) * **No document uploads required** **❌ Standard KYC Requirements:** * Government ID photos (license, passport) * Proof of address documents * Bank statements or financial verification * Selfie verification ### Response Values | Value | Meaning | User Experience | | ------- | ---------------------------------------------------- | ----------------------------------- | | `true` | No documents needed for this amount | ⚡ Fast completion (2-5 minutes) | | `false` | Documents required | 📋 Standard process (10-30 minutes) | | `null` | Provider doesn't specify or amount exceeds threshold | 🤷 Unknown requirements | ### Implementation Guidelines **Badge Messaging**: * `lowKyc: true` → "⚡ No Documents Required" * `lowKyc: false` → Standard presentation * `lowKyc: null` → No special treatment **Code Example**: ```typescript theme={null} function getLowKycBadge(lowKyc: boolean | null): string { return lowKyc === true ? "⚡ No Documents Required" : ""; } function shouldHighlightForSpeed(quote: Quote): boolean { return quote.lowKyc === true; } ``` ### **Conversion Impact**: Low KYC options can improve completion rates by 40-60% for new users, making them valuable for customer acquisition. ## Implementation Example Here's a realistic quote response showing how optimization parameters work together: ```json theme={null} { "quotes": [ { "transactionType": "CRYPTO_PURCHASE", "sourceAmount": 200.00, "sourceAmountWithoutFees": 195.83, "destinationAmount": 0.04308219, "destinationCurrencyCode": "ETH", "exchangeRate": 4642.29, "paymentMethodType": "CREDIT_DEBIT_CARD", "serviceProvider": "UNLIMIT", "partnerFee": 2, "rampIntelligence": { "rampScore": 78.5, "lowKyc": null } }, { "transactionType": "CRYPTO_PURCHASE", "sourceAmount": 200.00, "sourceAmountWithoutFees": 193.09, "destinationAmount": 0.04176125, "destinationCurrencyCode": "ETH", "exchangeRate": 4789.13, "paymentMethodType": "CREDIT_DEBIT_CARD", "serviceProvider": "KRYPTONIM", "partnerFee": 2, "rampIntelligence": { "rampScore": 45.2, "lowKyc": true } }, { "transactionType": "CRYPTO_PURCHASE", "sourceAmount": 200.00, "sourceAmountWithoutFees": 190.07, "destinationAmount": 0.041154, "destinationCurrencyCode": "ETH", "exchangeRate": 4859.80, "paymentMethodType": "CREDIT_DEBIT_CARD", "serviceProvider": "STRIPE", "partnerFee": 2, "rampIntelligence": { "rampScore": 12.1, "lowKyc": false } } ] } ``` ### Analysis & UI Recommendations **1. UNLIMIT (Score: 78.5)** * 🏆 **Primary Recommendation**: Highest customer score indicates strong success history * **UI Treatment**: Featured position, "Your Best Choice" badge * **Conversion Expectation**: Very high (user familiarity) **2. KRYPTONIM (Score: 45.2)** * ⚡ **Speed Advantage**: `lowKyc: true` means fast completion for new users * **UI Treatment**: "No Documents Required" badge * **Conversion Expectation**: Good for new users, moderate for returning **3. STRIPE (Score: 12.1)** * **Standard Treatment**: Lowest score, no special advantages * **UI Treatment**: Regular presentation * **Conversion Expectation**: Lower (unknown user history) ### Optimal Implementation Strategy ```typescript theme={null} // Sort quotes optimally function optimizeQuoteDisplay(quotes: Quote[]): Quote[] { return quotes .sort((a, b) => b.rampScore - a.rampScore) .map(quote => ({ ...quote, recommendationBadge: getRecommendationBadge(quote.rampScore), speedBadge: getLowKycBadge(quote.lowKyc), isPrimary: quote.rampScore >= 70, isRecommended: quote.rampScore >= 30 })); } ``` ### **Key Principle**: Rank by `rampScore` first, then enhance with `lowKyc` badges. Never let low KYC override strong user history - familiarity typically wins over convenience. ## Summary Meld's conversion optimization combines user history intelligence with friction analysis to maximize transaction success rates. By implementing proper quote ranking and visual emphasis, developers can achieve industry-leading conversion performance. **Implementation Checklist:** * ✅ Always include `walletAddress` in quote requests * ✅ Sort quotes by `rampScore` (highest first) * ✅ Add visual badges for scores > 30 * ✅ Highlight `lowKyc: true` options with speed messaging * ✅ Never override high rampScore with other factors * ✅ Monitor conversion metrics and iterate # Supporting Multiple Downstream Applications Source: https://docs.meld.io/docs/stablecoins/white-label-api-integration/whitelabel-api-guide/supporting-multiple-downstream-applications This page is for platforms and aggregators whose Meld integration serves multiple downstream partners, applications, or developers. It explains the two architectural options Meld supports and how each affects fees, onramp coverage, and reporting. If your use case involves supporting multiple downstream partners / applications / developers, there are 2 ways to handle that. 1. Having 1 Meld account 2. Having multiple Meld accounts In both approaches, you should pass in an `externalCustomerId` that corresponds to the end user making the transaction. This is just a reference id you provide, and can have any `string` value. ## Option 1: A single Meld account Can your downstream applications use the same set of onramps with the same fee set by you? If so, then the easiest way to support this is to use a single Meld account. * Meld (and you) will still need to know which application is responsible for each transaction. When you call `POST /crypto/session/widget`, use the `externalSubaccountCustomerId` field to pass in an identifier corresponding to the application that made the call. The `externalSubaccountCustomerId` you pass in will be tied to the transaction the user ends up making. This is useful for compliance, metrics, and revenue calculation. * Several providers require whitelisting the domains that users will get redirected to after completing a transaction. If you will have many applications, it may not make sense to whitelist each new domain with the provider. In this case, it would make sense for you to have an intermediary redirect url with a domain that you whitelist, that then automatically redirects to the actual target url. An example would be [https://yourcompanyurl.com/?redirectUrl=https://yourapplicationurl.com](https://yourcompanyurl.com/?redirectUrl=https://yourapplicationurl.com). Here you would only have to whitelist [https://yourcompanyurl.com](https://yourcompanyurl.com), but you would build an automatic redirect so that the above redirect sends the user to [https://yourapplicationurl.com](https://yourapplicationurl.com). ## Option 2: Multiple Meld accounts If your downstream applications need different sets of service provider and want to charge different partner fees, you may need multiple Meld accounts. Meld will need to create each account for you. Contact Meld about this. If you use 1 Meld account per downstream application, you do not need to pass in an `externalSubaccountCustomerId`. The `externalSubaccountCustomerId` will be returned in the transaction webhooks and you can also query `GET /payments/transactions` with this value. This allows you to see all transactions made by a particular downstream application. ## Implementation Instructions Steps to using `externalSubaccountCustomerId`: First, create a customer with `POST /accounts/customers`, using the string value as the `externalId`. A sub-account customer must be created before it can be referenced on a widget session. Sample request: ```json theme={null} { "externalId": "business1", "type": "BUSINESS" } ``` When you call `POST /crypto/session/widget`, pass in the `externalSubaccountCustomerId` corresponding to the downstream app in the body of the call. Sample request: ```json theme={null} { "sessionData": { "walletAddress": "2N3oefVeg6stiTb5Kh3ozCSkaqmx91FDbsm", "countryCode": "US", "sourceCurrencyCode": "USD", "sourceAmount": "100", "destinationCurrencyCode": "USDC", "serviceProvider": "TRANSAK" }, "externalCustomerId": "user1", "externalSubaccountCustomerId": "business1" } ``` Track which transaction was made by which `externalSubaccountCustomerId` by reading that value in the webhooks, the `GET /payments/transactions` response, and the dashboard CSV export. # Customization Source: https://docs.meld.io/docs/stablecoins/white-label-api-integration/whitelabel-api-guide/white-label-customization # Locking fields This page is for developers who want their users to land in the onramp's widget with specific fields pre-filled and non-editable. Some service providers support locking the token and wallet address fields within their widgets. To do this, pass the `lockFields` parameter to the `POST /crypto/session/widget` endpoint with one or both of `destinationCurrencyCode`/`sourceCurrencyCode` (depending on buy vs sell flow) and `walletAddress`. The table below shows which service providers support locking which fields.
Locking cryptocurrency
Locking wallet address
**Banxa**
Not supported
Not supported
**BTC Direct**
Supported
Supported
**Onramp Money**
Not supported
Always locked when passed in
**Transak**
Supported
Supported
**Binance Connect**
Always locked
Not supported
**TransFi**
Always locked when passed in
Always locked when passed in
**Unlimit**
Supported
Supported
**Paybis**
Not supported
Not supported
**Fonbnk**
Not supported
Supported
**Koywe**
Always locked when passed in
Always locked when passed in
**Robinhood**
Always locked when passed in
Always locked when passed in
**Coinbase**
Not supported
Not supported
**Blockchain**
Always locked when passed in
Always locked when passed in
**Kryptonim**
Supported
Supported
**Topper**
Supported
Supported
**Mercuryo**
Supported
Not Supported
**Swapped**
Not Supported
Not Supported
**Guardarian**
Not Supported
Supported
Here's a sample buy request with both the token and wallet address fields locked: ```json theme={null} { "sessionData": { "walletAddress": "2N3oefVeg6stiTb5Kh3ozCSkaqmx91FDbsm", "countryCode": "US", "sourceCurrencyCode": "USD", "sourceAmount": "100", "destinationCurrencyCode": "USDC", "serviceProvider": "TRANSAK", "lockFields": ["destinationCurrencyCode","walletAddress"] }, "externalCustomerId": "test1", "externalSessionId": "test1" } ```
# Quickstart Source: https://docs.meld.io/docs/stablecoins/white-label-api-integration/whitelabel-quickstart This is the 30 minute quickstart guide for executing the white-label API flow for the first time. # Integration Quickstart This quickstart is for developers integrating Meld's White-Label API for the first time. You'll go from zero credentials to a successfully completed sandbox transaction in about 30 minutes. **Time required:** 30 minutes **Difficulty:** API knowledge required ## Before you begin * A Meld dashboard invitation (check your email) * Ability to make authenticated REST calls (curl, Postman, or your language of choice) * A publicly reachable webhook URL (optional; you can poll the API instead) **Sandbox base URL:** `https://api-sb.meld.io`. Production credentials and URLs are separate — sandbox credentials will not work in production and vice versa. ## Step 1: Get Your API Key Your API key is required for all Meld API calls. ### Process: 1. **Check your email** for the Meld dashboard invitation 2. **Click the invitation link** and log in 3. In the dashboard [https://dashboard.meld.io](https://dashboard.meld.io) , **navigate to Developer > API Keys** 4. **Click "Reveal Key"** 5. **Copy and save** your API key securely **Important:** Always add `BASIC` before your API key in all requests **Example:** `BASIC W9kZTT7332okCEc1A9aqAq:3sYKoXQv6oHVHSts7G2agw9vTCXz` *** ## Step 2: Set Up Webhooks Webhooks notify your server when transactions are created and updated. This step can be completed later. ### Requirements: * **A publicly accessible URL** (localhost will not work) * For testing, consider using [ngrok](https://ngrok.com/), [webhook.site](https://webhook.site/) or similar services ### Setup Process: 1. Navigate to **Developer > Webhooks** in the dashboard 2. Click **"Add Endpoint"** 3. Enter your **webhook URL** (must start with http/https) 4. Give your webhook a **descriptive name** 5. Select **"Subscribe to all events"** 6. Click **"Add endpoint"** ### **Note:** You will receive notifications to this URL for all transaction creations and updates. *** ## Step 3: Test Your API Connection Verify your API key by requesting a price quote. ### API Call Details: * **Endpoint:** `POST /payments/crypto/quote` * **Authorization:** `BASIC [your-api-key]` ### Request Body: ```json theme={null} { "countryCode": "US", "sourceCurrencyCode": "USD", "destinationCurrencyCode": "BTC", "paymentMethodType": "CREDIT_DEBIT_CARD", "sourceAmount": 100 } ``` ### Testing Notes: * **Use the interactive docs** at the endpoint URL ### Expected Response: ✅ **200 status code** with quote details Here is an example of a single quote. You can expect multiple quotes, 1 per onramp, in the response. ```json theme={null} { "quotes": [ { "transactionType": "CRYPTO_PURCHASE", "sourceAmount": 100.00, "sourceAmountWithoutFees": 95.82, "fiatAmountWithoutFees": 95.82, "destinationAmountWithoutFees": null, "sourceCurrencyCode": "USD", "countryCode": "US", "totalFee": 4.18, "networkFee": 0.28, "transactionFee": 2.9, "partnerFee": 1, "destinationAmount": 0.00105423, "destinationCurrencyCode": "BTC", "exchangeRate": 94856.0, "paymentMethodType": "CREDIT_DEBIT_CARD", "serviceProvider": "TOPPER", "rampIntelligence": { "rampScore": 21.66, "lowKyc": false } } ] } ``` *** ## Step 4: Create a Test Transaction Create a test transaction without using real money. ### API Call Details: * **Endpoint:** `POST /crypto/session/widget` * **Authorization:** `BASIC [your-api-key]` ### Request Body: ```json theme={null} { "countryCode": "US", "sourceCurrencyCode": "USD", "destinationCurrencyCode": "BTC", "paymentMethodType": "CREDIT_DEBIT_CARD", "sourceAmount": 100, "serviceProvider": "TRANSAK", "walletAddress": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh" } ``` ### **Note:** Replace `walletAddress` with your own or use the test address provided. ### Expected Response: ✅ **200 status code** with onramp url details ```json theme={null} { "id": "WePjVaT4iBHPpqW49F419x", "externalSessionId": "testSession", "externalCustomerId": "testCustomer", "customerId": "WePZCYZjAK97cJWokfH3Jc", "widgetUrl": "https://meldcrypto.com?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtZWxkLmlvIiwiaWF0IjoxNzY1MjM2MjExLCJzdWIiOiJjcnlwdG8iLCJleHAiOjE3NjUyMzgwMTEsImFjY291bnRJZCI6IldRNVJ5aGRGekU0NXFqc29tZHpRMXUiLCJzZXNzaW9uSWQiOiJXZVBqVmFUNGlCSFBwcVc0OUY0MTlhIn0.TH6P9KVKN4GNu4CNsDAN9uicjMBashgA9QY7jiMiDEF", "serviceProviderWidgetUrl": "https://topper.com?apiKey=1234&sessionId=eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJvdHQiOiIxZDFjOTM1ODY0YTQ0NGM0YTJiMmQ5ODJkYjc0Njg1NCIsImlhdCI6MTc2NTIzNjIxMiwiZXhwIjoxNzY1MjM2NTEyfQ.6ZnmR5FAxzr9bG3n_I54L1EmHYljfhPJNeqp97WZPI7GUm9VCksbKv_rWK4KiB6YkAAJR6_C1Xsnn-fliUrABC", "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtZWxkLmlvIiwiaWF0IjoxNzY1MjM2MjExLCJzdWIiOiJjcnlwdG8iLCJleHAiOjE3NjUyMzgwMTEsImFjY291bnRJZCI6IldRNVJ5aGRGekU0NXFqc29tZHpRMXUiLCJzZXNzaW9uSWQiOiJXZVBqVmFUNGlCSFBwcVc0OUY0MTlhIn0.TH6P9KVKN4GNu4CNsDAN9uicjMBashgA9QY7jiMiDEF" } ``` ### Complete the Test Flow: 1. **Copy the`widgetUrl`** from the API response 2. **Open the URL** in your browser 3. **For KYC testing:** * **SSN:** Use any fake number * **ID Upload:** Any image file works 4. **For payment testing:** * **Card Number:** `4111 1111 1111 1111` * **Expiry:** Any future date * **CVV:** Any 3 digits 📚 **Full test data reference:** [Sandbox testing credentials](/docs/stablecoins/sandbox-guide/test) *** ## Step 5: Verify Your Transaction ### Fetch Transaction Details after Receiving Webhooks 1. **Check your webhook endpoint** for a webhook that the transaction has been created. ### Expected Webhook Response ```json theme={null} { "eventType": "TRANSACTION_CRYPTO_PENDING", "eventId": "AAsuLXHXD3mS1cjNBuHHzv", "timestamp": "2022-02-24T16:36:41.717262Z", "accountId": "WQ5RyhdFzE45qjsomdzQ1u", "profileId": "W9ka8vLE4ufBkSg3BEciZb", "version": "2025-03-01", "payload": { "requestId": "f07f1accb7404aec9bd9a5d64975eed1", "accountId": "W2aRZnYGPwhBWB94iFsZus", "paymentTransactionId": "WePZCYJW7cdXR7SxUMp8mE", "customerId": "WePZCYZjAK97cJWokfH3Jc", "externalCustomerId": "testCustomer", "externalSessionId": "testSession", "paymentTransactionStatus": "PENDING", "transactionType": "CRYPTO_PURCHASE", "sessionId": "WePjVaT4iBHPpqW49F419x" } } ``` 2. **Extract the`paymentTransactionId`** from the webhook payload 3. **Call** `GET /payments/transactions/{transactionId}` ### Expected Response: ✅ **200 status code** with transaction details ```json theme={null} { "transaction": { "id": "WePZCYJW7cdXR7SxUMp8mE", "parentPaymentTransactionId": null, "accountId": "WQ5RyhdFzE45qjsomdzQ1u", "isPassthrough": false, "passthroughReference": null, "isImported": false, "customer": { "id": "WePZCYZjAK97cJWokfH3Cc", "accountId": "WQ5RyhdFzE45qjsomdzQ1u", "externalId": "YC100" }, "transactionType": "CRYPTO_PURCHASE", "status": "SETTLED", "sourceAmount": 100.00, "sourceCurrencyCode": "USD", "destinationAmount": 0.00105423, "destinationCurrencyCode": "BTC", "paymentMethodType": "CREDIT_DEBIT_CARD", "serviceProvider": "TOPPER", "serviceTransactionId": "1f0d470c-f5c2-6fc2-a54d-b6cdd6b6f014", "orderId": null, "description": null, "externalReferenceId": "testSession", "serviceProviderDetails": { **raw details from the onramp** }, "multiFactorAuthorizationStatus": null, "createdAt": "2025-12-08T20:03:07.173223Z", "updatedAt": "2025-12-08T20:08:08.877106Z", "countryCode": "US", "sessionId": "WePZCbr96gAUxgu7Auvm4q", "externalSessionId": "testSession", "paymentDetails": null, "externalCustomerId": "testCustomer", "fiatAmountInUsd": 100.00, "sessionClientTags": null, "serviceProviderTransactionUrl": null, "serviceProviderCreatedAt": "2025-12-08T20:02:22Z", "cryptoDetails": { "sourceWalletAddress": null, "destinationWalletAddress": "0xd72cc3468979360e31bc83b84f0887deccfd81d5", "sessionWalletAddress": "0xd72cc3468979360e31bc83b84f0887deccfd81d5", "totalFee": 4.18, "networkFee": 0.28, "transactionFee": 2.9, "partnerFee": 1, "totalFeeInUsd": 4.18, "networkFeeInUsd": 0.28, "transactionFeeInUsd": 2.9, "partnerFeeInUsd": 1, "blockchainTransactionId": "0x553d295955a978ed3e9fc1717b5bcb903c69577e49c8ad255abece945ffa9ba0", "institution": null, "chainId": "1" } } } ``` ### Dashboard Verification: 1. Navigate to the **Transactions tab** 2. If your transaction isn't visible: * Click the **Status dropdown** * Select **"Select All"** * Your transaction should appear ✅ **White-Label API Integration Complete!** You can now build custom crypto experiences. ➡️ **[Build Custom UI Guide](/docs/stablecoins/white-label-api-integration/whitelabel-api-guide)** — complete implementation guide ## Next steps If you're ready to begin your integration, follow the end-to-end [White-Label API Guide](/docs/stablecoins/white-label-api-integration/whitelabel-api-guide). *** ## Troubleshooting ### Common Issues and Solutions: #### 🚫 401 Unauthorized * Ensure `BASIC` is added before your API key * Check for extra spaces or incorrect formatting #### 🚫 Webhook Not Received * Verify URL is publicly accessible * Check firewall settings * Ensure webhook endpoint returns 200 status #### 🚫 Transaction Not Visible * Change status filter to "Select All" in dashboard * Wait 30 seconds and refresh * Check if using correct environment (sandbox vs production) # Build with AI Source: https://docs.meld.io/docs/welcome/build-with-ai Meld's Stablecoins & Digital Assets docs are AI-friendly. Plug them into Claude, Cursor, ChatGPT, or any LLM tool to ship your crypto integration faster. The Meld Stablecoins documentation is published in an agent-ready format so coding assistants and AI agents can read, search, and reason over it directly — without copy-pasting pages or relying on stale training data. This page covers four ways to bring Meld docs into your AI workflow: Connect Claude, Cursor, and other tools to a live, searchable index of the docs. Drop-in context files that summarize or inline the entire docs site. Get any page as clean Markdown by appending `.md` to its URL. Machine-readable capability file for agents that act on Meld's APIs. All endpoints below serve the full Meld documentation, including the Stablecoins & Digital Assets, Meld API, and API reference sections. ## MCP server The Meld docs are exposed as a hosted [Model Context Protocol](https://modelcontextprotocol.io) server. Once connected, your AI tool can search the docs and read full pages on demand instead of guessing. **Server URL** ``` https://docs.meld.io/mcp ``` **Tools the server exposes** * `search_meld` — semantic search across every page; returns titles, snippets, and links. * `query_docs_filesystem_meld` — shell-style read access (`ls`, `tree`, `cat`, `head`, `rg`) over the docs as a virtual filesystem, including the OpenAPI specs. ### Add it to your tool Run from your project: ```bash theme={null} claude mcp add --transport http meld-docs https://docs.meld.io/mcp ``` Then in a session, ask: *"Use the meld-docs MCP to find the White-Label API quickstart and summarize the request payload."* Add to `claude_desktop_config.json`: ```json theme={null} { "mcpServers": { "meld-docs": { "type": "http", "url": "https://docs.meld.io/mcp" } } } ``` Restart Claude Desktop. The `meld-docs` tools will appear in the tool picker. Add to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` in your project: ```json theme={null} { "mcpServers": { "meld-docs": { "url": "https://docs.meld.io/mcp" } } } ``` Add to your workspace `.vscode/mcp.json`: ```json theme={null} { "servers": { "meld-docs": { "type": "http", "url": "https://docs.meld.io/mcp" } } } ``` Open a chat and attach the Meld docs MCP under **Connectors → Add connector** (available on Plus, Pro, Business, and Enterprise). Use: ``` https://docs.meld.io/mcp ``` No auth required. A discovery endpoint is published at `https://docs.meld.io/.well-known/mcp` so MCP-aware tools can find the server automatically. ## llms.txt The docs are also published as flat text files designed for direct LLM consumption — useful when you want to drop the whole site into a context window, a RAG pipeline, or a fine-tuning corpus. | File | Use it when | | -------------------------------------------------------------------------- | ------------------------------------------------------------ | | [`https://docs.meld.io/llms.txt`](https://docs.meld.io/llms.txt) | You want a structured index of every page with descriptions. | | [`https://docs.meld.io/llms-full.txt`](https://docs.meld.io/llms-full.txt) | You want the entire docs site inlined as one Markdown file. | **Example — load the full docs into a Claude API request:** ```python theme={null} import anthropic, urllib.request docs = urllib.request.urlopen("https://docs.meld.io/llms-full.txt").read().decode() client = anthropic.Anthropic() msg = client.messages.create( model="claude-opus-4-8", max_tokens=1024, system=[ {"type": "text", "text": "You are a Meld integration assistant."}, {"type": "text", "text": docs, "cache_control": {"type": "ephemeral"}}, ], messages=[{"role": "user", "content": "How do I create a crypto quote for a USD → USDC buy?"}], ) print(next((b.text for b in msg.content if b.type == "text"), "")) ``` Use prompt caching (as shown above) when you call repeatedly — `llms-full.txt` is large, and caching cuts cost and latency dramatically. ## Markdown export Any page on `docs.meld.io` can be fetched as raw Markdown — no HTML, no nav chrome — by appending `.md` to its URL. **Examples** ``` https://docs.meld.io/docs/stablecoins/crypto-overview_/quickstart.md https://docs.meld.io/docs/stablecoins/white-label-api-integration/whitelabel-quickstart.md https://docs.meld.io/api-reference/crypto/retail-ramp/crypto-quote-get.md ``` You can also: * Send `Accept: text/markdown` on any docs request to get the Markdown variant. * Press Cmd+C (Ctrl+C on Windows) on any page to copy it as Markdown. * Use the **Copy as Markdown** / **View as Markdown** options in the page's contextual menu. API reference pages include the full OpenAPI operation in the Markdown export, which is what most coding agents need to generate a correct request. ## skill.md For agents that *act* on Meld (not just read about it), the docs publish an [agentskills.io](https://agentskills.io/specification)–compatible capability file: ``` https://docs.meld.io/skill.md ``` It describes what an agent can accomplish with the Meld API, the inputs each capability needs, and the constraints that apply. MCP-connected agents discover it automatically as a resource on the server above; you can also add it manually: ```bash theme={null} npx skills add https://docs.meld.io ``` ## Recommended setup for crypto integrations Add `https://docs.meld.io/mcp` to your editor or chat tool. This is the single best upgrade — your assistant can now look up exact request shapes, status codes, and webhook payloads on demand. Bookmark these Markdown URLs and feed them to your agent at the start of a session: * `https://docs.meld.io/docs/stablecoins/crypto-overview_/index.md` — product overview and which integration to pick * `https://docs.meld.io/docs/stablecoins/crypto-overview_/quickstart.md` — first API call * `https://docs.meld.io/docs/stablecoins/for-all-products/webhook-events.md` — webhook event catalog When you want a clean-slate model (no MCP) to do something self-contained — a one-off script, a code review, a migration — paste `llms-full.txt` into context with caching enabled. The MCP filesystem includes the OpenAPI JSON for every product. Ask your agent to validate generated requests against `/openapi/crypto-20260203.json` before you ship. ## Questions or feedback Found a docs gap your agent stumbled on, or want a new endpoint covered? Reach out to your Meld contact — agent-readability is a first-class goal for this section of the docs. # Overview Source: https://docs.meld.io/docs/welcome/overview Welcome to Meld This page introduces what Meld does and the use cases it supports. It is for developers and product teams evaluating Meld, and by the end you should know whether Meld's Digital Assets or Bank Linking products fit your needs and where to go next. # Meld empowers you to 1. Buy, sell, or transfer digital assets, including stablecoins globally. 2. Have your users connect to their banks and pull their banking information.
## Common Digital Assets Use Cases 1. You are a wallet or fintech app that wants your users to be able to buy or sell crypto anywhere. 2. You are an employer who needs to pay out employees / contractors across the globe. 3. You are a marketplace that needs to collect payment globally using digital assets. 4. You currently support one or a few onramps but need many more for global coverage and better conversion.
## Common Bank Linking Use Cases 1. You offer your customers financial planning and need their transaction history and current balance. 2. You need your customers to verify their identity by connecting to their bank. 3. You need your customer's iban information (or account number / routing number) to facilitate global bank transfers.
## Terminology | Term | Meaning | | :------------- | :--------------- | | Onramps | Network Partners | | Digital Assets | Crypto | | Onramping | Buying crypto | | Offramp | Selling crypto | These terms are used consistently across all Meld documentation. If you see "onramp" or "network partner" in a guide, they refer to the same thing.
Next: View [Meld's products](/docs/welcome/see-all-products) to see which ones are right for you.
# Reference Source: https://docs.meld.io/docs/welcome/reference This page is a quick reference for environments, authentication, status codes, errors, security, and date formats. It is for developers who have already started integrating Meld and need to look up exact values while coding. If you have not started the integration process, you should skip this page and start with the [Overview](/docs/welcome/overview). # Meld Environments Meld offers both production and sandbox environments. The below table contains the URL for each environment: | Environment | API Base URL | | :---------- | :----------------------------------------------- | | Sandbox | [https://api-sb.meld.io](https://api-sb.meld.io) | | Production | [https://api.meld.io](https://api.meld.io) | Sandbox and Production use separate API keys and separate data. A key issued for one environment will not work against the other. Test transactions in Sandbox do not move real funds. To obtain your API key for either Production or Sandbox environments, work with your Meld contact. Your API Key is a secret, treat it as such. Do not share it or send it through a front end call. # Authentication Meld uses API keys to authenticate requests. These keys carry many privileges such as authorizing payments and accessing financial accounts data. It is important to keep them private and secure during both storage and transmission. Authentication is handled via HTTP headers, using the `Authorization` header. Example request: ```bash theme={null} curl --location --request \ GET 'https://api.meld.io/' \ --header 'Authorization: BASIC {{Your API Key}}' ``` Example successful response: ```json theme={null} { "id": "abc123", "status": "OK" } ``` "BASIC" Authorization When submitting your API key for authentication, you must specify "BASIC " before the key value pair. Note the trailing space between `BASIC` and your key. To help keep your API keys secure, follow these best practices: * Do not embed API keys directly in code, because they can be accidentally exposed to the public. Instead, store them in environment variables or in files outside of your application's source tree. * Do not store your API keys in files inside your application's source tree. If you must store API keys in files, keep the files outside your source tree to ensure your keys do not end up in your source code control system, especially if you use a public one such as GitHub. * Delete unneeded API keys to minimize exposure to attacks. * Review your code before publicly releasing it. Ensure that it does not contain API keys or any other private information before you make it publicly available. # API Status Codes The following table lists the status code you will receive from our APIs.
Status code Description
200 Successful, with response data as defined by the `Content-Type` header
201 Successful, with response data as defined by the `Content-Type` header
400 Bad Request
401 Unauthorized
403 Forbidden
404 No Resource Found
422 Input Validation Failed
425 Failure. TOO\_EARLY You might see this error when the same idempotent key is used twice and the first transaction is still being processed.
429 Too Many Requests
500 Unexpected Issue

# API Error Schema Any status code of `400` or higher returns an error payload. Inspect the `code` and `errors` fields to determine how to handle the failure, and surface `requestId` when contacting Meld support so we can trace the exact call. All errors are returned in the form of JSON and contain the following data: | Key | Description | | :---------- | :-------------------------------------------------------------------------------------------------------------------- | | `code` | A categorization of the error | | `message` | A developer-friendly representation of the error code. This may change over time and is not safe for programmatic use | | `errors` | A user-friendly representation of the error code. This may change over time and is not safe for programmatic use. | | `requestId` | The request Id | | `timestamp` | The date and time when the request was made | Below is a sample error response: ```json theme={null} { "code": "BAD_REQUEST", "message": "Bad request", "errors": [ "[amount] Must be a decimal value greater than zero" ], "requestId": "eb6aaa76bd7103cf6c5b090610c31913", "timestamp": "2022-01-19T20:32:30.784928Z" } ``` # Security 1. CORS -- Meld does not need to whitelist any of our customer's URL or IPs for them to call our public Production & Sandbox APIs. You can use whichever URL you desire, as we authenticate via your Meld API Key. 2. For security reasons, Meld recommends using your backend server to make the calls to Meld's API. If you make these calls from your frontend instead, it may not work and you may get back a CORS error. This is because making calls to Meld APIs requires that you pass in an `Authorization` header with the API Key we issued you. It is insecure to keep this API Key hardcoded in your mobile app or web app. 3. All our customers need to treat the Meld API Key they've been issued like any other password. It is an extremely sensitive credential that needs to be protected at all cost. The security measures you need to ensure are: a) strict controls to the backend server (as it has access to your Meld API Key), b) a way to authenticate your FE/app to your backend server, c) reject/ignore all other calls to your backend server. Never call Meld APIs directly from a browser, mobile app, or any other untrusted client. Calls must originate from your backend server so your API key is never exposed. # Dates All Meld dates and timestamps returned via Meld's API are in UTC time and formatted using ISO 8601 (for example, `2022-01-19T20:32:30.784928Z`). # See All Products Source: https://docs.meld.io/docs/welcome/see-all-products List of Meld Products This page lists every product Meld offers so you can pick the right integration path before you start building. It is for developers comparing flows, and by the end you should know which product (or combination) matches your use case and where its quickstart lives. You may choose to integrate more than one product. It is important to understand the distinction between the products before proceeding. 1. [Digital Assets: White Label API Flow](/docs/stablecoins/white-label-api-integration/index) 1. Enables your users to buy, sell, and transfer crypto using onramps. Users KYC and complete the transaction within the onramp's UI. Meld powers the flow and offers recommendation of which onramp is best for the user. You can initiate this flow either from Meld's UI (which you can embed in your app / site) or build your own UI on top of Meld's APIs. 2. Use this flow if you are a wallet or fintech app that wants your users to be able to buy or sell any digital asset, and **you want to build your own UI**. Examples of customers using this flow are Phantom Wallet, Uniswap Wallet, and Metamask Wallet. 3. Start building with the [White Label API flow](/docs/stablecoins/white-label-api-integration/whitelabel-quickstart). 2. [Digital Assets: Meld Checkout Flow](/docs/stablecoins/meld-checkout-integration/index) 1. Enables your users to buy, sell, and transfer crypto using onramps. 2. Receive your unique Meld url that you can embed / redirect to in your app. The entire flow will happen in the Meld UI. Setup happens in minutes. 3. Use this flow if you are a wallet or fintech app that wants your users to be able to buy or sell any digital asset, and **you want to use Meld's UI**. 4. Start building with the [Meld Checkout Ramps flow](/docs/stablecoins/meld-checkout-integration/meld-checkout-quickstart). 3. [Digital Assets: Virtual Account Flow](/docs/stablecoins/virtual-account-integration/index) 1. Enables you to have a white-label API driven flow to complete buying and selling crypto. The onramp will create a virtual bank account for you to send crypto to (for buying) and receive payment from (for selling). 2. Use this flow if **you want onramps to create a virtual bank account for you and each of your users**. This unlocks being able to purchase or sell large amounts of digital assets, especially stablecoin, globally. 3. Start building with the [Virtual Account flow](/docs/stablecoins/virtual-account-integration/virtual-account-quickstart). 4. [Bank Linking](/docs/bank-linking/bank-linking-quickstart/index) 1. Allows you to have your users connect their bank account and then pull any relevant information you need, such as transaction history, balance, and more. 2. Use this flow if **you need information about your customers' bank accounts**. 3. Start building with the [Bank Linking Quickstart](/docs/bank-linking/bank-linking-quickstart/index). Do not use the Bank Linking product if your business is digital asset related. Use one of the Digital Assets flows above instead. Not sure which flow to pick? Start with the [Overview](/docs/welcome/overview) to map your use case to a product, then come back here for the quickstart link.
# Create processor token Source: https://docs.meld.io/api-reference/bank-linking/accounts/bank-linking-accounts-create-processor-token /openapi/banklinking-20231219.json post /bank-linking/accounts/{financialAccountId}/processors/create-token The token is used by the processor to access the information on this account via a service provider. Depending on the service provider and/or processor, this token represents whatever token/code/key is needed to provide access. # Get a financial account Source: https://docs.meld.io/api-reference/bank-linking/accounts/bank-linking-accounts-get /openapi/banklinking-20231219.json get /bank-linking/accounts/{financialAccountId} Use this endpoint to retrieve account information of a specific financial account. # Search financial accounts Source: https://docs.meld.io/api-reference/bank-linking/accounts/bank-linking-accounts-search /openapi/banklinking-20231219.json get /bank-linking/accounts Use this endpoint to filter financial accounts by various parameters. # Create a connection Source: https://docs.meld.io/api-reference/bank-linking/connect/bank-linking-connect-create /openapi/banklinking-20231219.json post /bank-linking/connect/start Use this endpoint to obtain a connect token, which can be used to initialize the Meld widget and start a bank linking connection. # Repair a connection Source: https://docs.meld.io/api-reference/bank-linking/connect/bank-linking-connect-repair /openapi/banklinking-20231219.json post /bank-linking/connect/repair A Service Provider's connection to the institution may sometimes degrade or new financial account(s) may be discovered for the connection. In order to fix the connection or add these newly discovered account(s), the customer must reconnect. This endpoint generates a new connect token for the connection to enable this reconnection process. This connect token can be used to invoke a new bank linking flow where the user can enter the service provider's UI to fix the connection # Update a connection Source: https://docs.meld.io/api-reference/bank-linking/connections/bank-linking-connection-update /openapi/banklinking-20231219.json post /bank-linking/connections/{connectionId}/update Update a connection and its products. Adds new products retroactively to an existing connection and triggers a refresh with this updated set of products. The products provided must not already be present on the connection and be supported by the underlying service provider and institution in order to be successful. # Delete an institution connection Source: https://docs.meld.io/api-reference/bank-linking/connections/bank-linking-connections-delete /openapi/banklinking-20231219.json delete /bank-linking/connections/{connectionId} Use this endpoint to delete a connection and all of its financial accounts. This will also delete the connection with the service provider used to make the connection, thus stopping any updates for the connection and its financial accounts. Listen for the associated [BANK_LINKING_CONNECTION_DELETED](https://docs.meld.io/docs/webhook-events-bank-linking#bank_linking_connection_deleted) webhook for acknowledgement that the connection was deleted. *Note:* Financial accounts may belong to multiple connections. This can occur if a customer connects to their institution multiple times in separate sessions, either through the same or a different service provider. If a connection is deleted but its financial accounts still belong to another active connection, then they won't be deleted yet. Not until all the connections that a financial account is associated with are deleted, will the financial account then be deleted. *Note:* In some cases, connections will be deleted before ever calling this endpoint. The aforementioned webhook will notify when this occurs, and calling this endpoint for the connection will no longer be necessary. This can occur if a customer stops granting access to the service provider directly through their institution. *Note:* Duplicate provider connections occur when the same customer connects to the same institution using the same login credentials through the *same* service provider. In such cases, only the most recently refreshed of these duplicates is actively maintained and the rest are considered inactive. When deleting duplicate connections, all of the inactive duplicates must be deleted prior to deleting the main duplicate. If an attempt is made to delete the active duplicate prior to the inactive duplicates being deleted, then the next most recently aggregated duplicate will become the active one. Only once all duplicates are deleted will this sever the connection with the service provider and cease billing. Duplicate connections made through *different* service providers do not have this restriction, however. # Get an institution connection Source: https://docs.meld.io/api-reference/bank-linking/connections/bank-linking-connections-get /openapi/banklinking-20231219.json get /bank-linking/connections/{connectionId} Retrieve details of a specific institution connection. # Import existing connections with a service provider Source: https://docs.meld.io/api-reference/bank-linking/connections/bank-linking-connections-import /openapi/banklinking-20231219.json post /bank-linking/connections/import Importing is done asynchronously. Webhooks will be sent for each connection as they are imported, the same as if the connection was added via the connect widget. # Refresh accounts belonging to a connection Source: https://docs.meld.io/api-reference/bank-linking/connections/bank-linking-connections-refresh /openapi/banklinking-20231219.json post /bank-linking/connections/{connectionId}/refresh Trigger a refresh of all financial accounts for a given institution connection with the latest information from the service provider. This is a premium service for most service providers and should be used sparingly for requesting up-to-date data as of the time of the call to this endpoint. Transaction and balance data is updated daily by service providers, and Meld's webhooks will notify of these updates so that product data does not become stale. *Note:* By default, historical transactions are loaded upon initial connection for all providers. This is a premium service for Finicity and MX, so if this is not desired please reach out to Meld support to disable this feature. After doing so, then historical transactions will only be loaded after the first time a refresh is requested for a Finicity/MX connection that has transactions as an available product. *Note:* Duplicate provider connections occur when the same customer connects to the same institution using the same login credentials through the *same* service provider. In such cases, only the most recently refreshed of these duplicates is actively maintained and the rest are considered inactive. Choosing to refresh an inactive duplicate will cause it to become the new active duplicate and it will assume the same connection status as the previous active duplicate. The previous active duplicate will now be considered inactive. # Search institution connections Source: https://docs.meld.io/api-reference/bank-linking/connections/bank-linking-connections-search /openapi/banklinking-20231219.json get /bank-linking/connections Retrieve institution connections filtered by various parameters. # Get institution Source: https://docs.meld.io/api-reference/bank-linking/institutions/bank-linking-institutions-get /openapi/banklinking-20231219.json get /institutions/{institutionId} This endpoint allows you to get an institution by id, as well as the service provider metadata such as name, URL, and logos for each service provider configured to your account that support this institution # Search institutions Source: https://docs.meld.io/api-reference/bank-linking/institutions/bank-linking-institutions-search /openapi/banklinking-20231219.json get /institutions Allows you to search across the list of Institutions supported by your service providers. This endpoint is only available in production. # Get a financial account investment holding Source: https://docs.meld.io/api-reference/bank-linking/investments/bank-linking-investment-holdings-get /openapi/banklinking-20231219.json get /bank-linking/investments/holdings/{investmentHoldingId} Use this endpoint to retrieve investment holding information of a specific financial account investment holding. # Search financial account investment holdings Source: https://docs.meld.io/api-reference/bank-linking/investments/bank-linking-investment-holdings-search /openapi/banklinking-20231219.json get /bank-linking/investments/holdings Use this endpoint to filter financial account investment holdings by various parameters. # Get a financial account investment transaction Source: https://docs.meld.io/api-reference/bank-linking/investments/bank-linking-investment-transactions-get /openapi/banklinking-20231219.json get /bank-linking/investments/transactions/{investmentTransactionId} Use this endpoint to retrieve investment transaction information of a specific financial account investment transaction. # Search financial account investment transactions Source: https://docs.meld.io/api-reference/bank-linking/investments/bank-linking-investment-transactions-search /openapi/banklinking-20231219.json get /bank-linking/investments/transactions Use this endpoint to filter financial account investment transactions by various parameters. # Get a financial account transaction Source: https://docs.meld.io/api-reference/bank-linking/transactions/bank-linking-transactions-get /openapi/banklinking-20231219.json get /bank-linking/transactions/{transactionId} Use this endpoint to retrieve transaction information of a specific financial account transaction. # Search financial account transactions Source: https://docs.meld.io/api-reference/bank-linking/transactions/bank-linking-transactions-search /openapi/banklinking-20231219.json get /bank-linking/transactions Use this endpoint to filter financial account transactions by various parameters. # Create a crypto widget Source: https://docs.meld.io/api-reference/crypto/retail-ramp/crypto-session-widget-create /openapi/crypto-20231219.json post /crypto/session/widget Use this endpoint to create a crypto widget for a session to buy or sell crypto. # Get transaction by id Source: https://docs.meld.io/api-reference/crypto/retail-ramp/payments-transactions-get /openapi/crypto-20231219.json get /payments/transactions/{id} Search transaction by its Meld identifier # Search transactions Source: https://docs.meld.io/api-reference/crypto/retail-ramp/payments-transactions-search /openapi/crypto-20231219.json get /payments/transactions Search a list of transactions you've previously created. The transactions are ordered by the date of creation. # Fetch a transaction from the provider (for offramp use) Source: https://docs.meld.io/api-reference/crypto/retail-ramp/payments-transactions-sessions-get /openapi/crypto-20231219.json get /payments/transactions/sessions/{sessionId} Fetch a transaction from the service provider using the session ID. This endpoint is primarily used for offramp scenarios where the transaction needs to be retrieved from the provider after the user completes their action in the widget. # Create a crypto virtual account ramp quote Source: https://docs.meld.io/api-reference/crypto/virtual-account/crypto-virtual-account-quote-get /openapi/crypto-20231219.json post /payments/virtual-account/quote Use this endpoint to request the current exchange rate of the selected fiat currency-cryptocurrency pair, and the required fees. Enter a fiat currency as the sourceCurrencyCode to buy crypto and enter a crypto currency in that field to sell crypto. # Create a virtual account offramp order Source: https://docs.meld.io/api-reference/crypto/virtual-account/payments-virtual-account-ramp-offramp-order-create /openapi/crypto-20231219.json post /payments/virtual-account/offramp-order Create a virtual account offramp order to initiate crypto to fiat transfer # Create a virtual account onramp order Source: https://docs.meld.io/api-reference/crypto/virtual-account/payments-virtual-account-ramp-onramp-order-create /openapi/crypto-20231219.json post /payments/virtual-account/onramp-order Create a virtual account onramp order to receive bank details for fiat transfer # Add an address to a customer Source: https://docs.meld.io/api-reference/customer/customers/accounts-customers-addresses-create /openapi/customer-20231219.json post /accounts/customers/{customerId}/addresses # Create a new customer or retrieve a customer by its external id Source: https://docs.meld.io/api-reference/customer/customers/accounts-customers-create /openapi/customer-20231219.json post /accounts/customers # Delete a customer Source: https://docs.meld.io/api-reference/customer/customers/accounts-customers-delete /openapi/customer-20231219.json delete /accounts/customers/{customerId} Use this endpoint to delete a customer with Meld and/or with any of the service providers it is linked to. This endpoint should only be used for customers using the Bank-Linking stack, as it is not yet compatible with the Payments or Crypto stack. If a customer is deleted that is linked to a Payments or Crypto service provider customer, it will still be deleted with Meld, but not with the provider. ***Note:*** For customers using the Bank-Linking stack, this will delete all of the customer's connections and financial accounts as well. # Initiate customer KYC Source: https://docs.meld.io/api-reference/customer/customers/accounts-customers-kyc-initiate /openapi/customer-20231219.json post /accounts/customers/{customerId}/kyc/initiate Retrieve a url that when launched displays a UI that commences KYC for your user # Search customers Source: https://docs.meld.io/api-reference/customer/customers/accounts-customers-search /openapi/customer-20231219.json get /accounts/customers Returns a list of customers that match the query parameters. The customers are sorted by external id. # Update a customer Source: https://docs.meld.io/api-reference/customer/customers/accounts-customers-update /openapi/customer-20231219.json patch /accounts/customers/{customerId} # Import an external payment transaction Source: https://docs.meld.io/api-reference/payments/external-transactions/payments-external-transactions-import /openapi/payments-20231219.json post /payments/external/import This is the endpoint to import a transaction on any of the supported providers. Use this endpoint if you have performed transactions on one or more providers outside of Meld, and want to import those transactions into Meld's system. Note that when performing an [external refund](https://docs.meld.io/docs/crypto-supported-service-providers-assets), the initial transaction is automatically imported into Meld's system. # Refund an external payment transaction Source: https://docs.meld.io/api-reference/payments/external-transactions/payments-external-transactions-refund /openapi/payments-20231219.json post /payments/external/refund This is the endpoint to refund an external transaction on any of the supported providers. Use this endpoint if you have created a transaction with any of these providers outside of Meld's system, but want to perform the refund using Meld. Note that this will also cause the original transaction to be imported into Meld's system. Both partial and full refunds are supported. # Create a risk analysis Source: https://docs.meld.io/api-reference/payments/risk/payments-transactions-risk-analysis /openapi/payments-20231219.json post /payments/risk-analysis Perform an ACH risk analysis and create associated records # Create a Meld payment token Source: https://docs.meld.io/api-reference/payments/transactions/payment-token-post /openapi/payments-20231219.json post /payments/tokens Use this endpoint to create long-lasting Meld payment tokens that can be used across service providers. These tokens can replace the payment method provided for future transactions. [For more details see this page](https://docs.meld.io/docs/payment-methods-and-tokenization). # Capture a payment transaction Source: https://docs.meld.io/api-reference/payments/transactions/payments-transactions-capture /openapi/payments-20231219.json post /payments/transactions/{id}/capture See [here](https://docs.meld.io/docs/authorization-capture) to learn more about authorization and capture. # Create a payment transaction Source: https://docs.meld.io/api-reference/payments/transactions/payments-transactions-create /openapi/payments-20231219.json post /payments/transactions Use this endpoint to create a payment transaction. You can find sample code in our public Postman collection, or build it in the adjacent API explorer. The link to our postman collection is: [https://www.postman.com/meldeng/workspace/meld-io-public-api-collection](https://www.postman.com/meldeng/workspace/meld-io-public-api-collection) **IDEMPOTENT REQUESTS**: Please refer to [Idempotency Key](https://docs.meld.io/docs/idempotency-key) **PASSTHROUGH ENABLED REQUESTS**: Please refer to [Passthrough Headers](https://docs.meld.io/docs/credential-passthrough) **ERRORS**: Please refer to [Error Responses](https://docs.meld.io/docs/fiat-error-responses) # Get transaction by id Source: https://docs.meld.io/api-reference/payments/transactions/payments-transactions-get /openapi/payments-20231219.json get /payments/transactions/{id} Search transaction by its Meld identifier # Refund a payment transaction Source: https://docs.meld.io/api-reference/payments/transactions/payments-transactions-refund /openapi/payments-20231219.json post /payments/transactions/{id}/refund This is the endpoint to refund a transaction on any of the supported providers. Full as well as partial refunds are supported, so it is not mandatory to inform the total value of the original transaction in the case of a full refund. Note: While the parameters within the body are optional, you must at least pass in a valid JSON body, e.g. { }. # Search transactions Source: https://docs.meld.io/api-reference/payments/transactions/payments-transactions-search /openapi/payments-20231219.json get /payments/transactions Search a list of transactions you've previously created. The transactions are ordered by the date of creation. # Create a payout transaction Source: https://docs.meld.io/api-reference/payments/transactions/payouts-transactions-create /openapi/payments-20231219.json post /payments/transactions/payout Use this endpoint to create a payout transaction. You can find sample code in our public Postman collection, or build it in the adjacent API explorer. The link to our postman collection is: [https://www.postman.com/meldeng/workspace/meld-io-public-api-collection](https://www.postman.com/meldeng/workspace/meld-io-public-api-collection) **IDEMPOTENT REQUESTS**: Please refer to [Idempotency Key](https://docs.meld.io/docs/idempotency-key) **ERRORS**: Please refer to [Error Responses](https://docs.meld.io/docs/fiat-error-responses) # Search countries Source: https://docs.meld.io/api-reference/service-providers/service-provider/service-providers-properties-country-search /openapi/serviceproviders-20231219.json get /service-providers/properties/countries Returns a list of properties which meet the search criteria. # Search service providers Source: https://docs.meld.io/api-reference/service-providers/service-provider/service-providers-search /openapi/serviceproviders-20231219.json get /service-providers Returns a list of service providers which meet the search criteria. # FAQ Source: https://docs.meld.io/docs/bank-linking/bank-linking-faq Answers to the most common questions developers ask while integrating Meld Bank Linking. If you don't see your question here, check the [Activity Log](/docs/bank-linking/testing-and-debugging/debugging-activity-log) for connection-level detail or reach out to Meld support. Follow the [Creating Connections](/docs/bank-linking/creating-connections) guide for end-to-end instructions on making a connection. There are several likely candidates: * The provider doesn't support the institution for all of the products specified. * Your account with that provider may not be configured for a particular product or for OAuth institutions. * If you are using Smart Routing, Meld may know that the connection between that provider and institution is currently unstable and is therefore routing you to a different provider. Assuming the institution is supported by one of the providers you have enabled, the likely issue is that the institution doesn't support all of the products you requested when calling [/connect/start](/api-reference/bank-linking/connect/bank-linking-connect-create). Meld filters the institutions returned in the picker to only the ones that support all products requested. To check if this is the issue, try making a request for only `BALANCES` and `TRANSACTIONS` and see if your institution shows up. The most likely reason is your connection is in a status other than `ACTIVE`. Check the status of your connection — it might be `RECONNECT_REQUIRED` (the user must log in again in the repair flow) or `PARTIALLY_ACTIVE` (for example, because it's a duplicate). See [Connection Statuses and Errors](/docs/bank-linking/get-connection-data/connection-statuses-and-errors) for the full list. Plaid forces the user to select their institution in the Plaid picker even if it was already selected in the Meld picker. No other provider does this. The best Meld can do is have the institution you picked in the Meld picker be the first one in the list of the Plaid picker, which it does. Yes. Pass `"accountPreferenceOverride": {"selectionStyle": "TILE"}` as part of your [/connect/start request](/docs/bank-linking/creating-connections/create-a-connect-token). No. You can skip the Meld picker by passing in the ID of the institution the user will connect to. See [Create a Connect Token](/docs/bank-linking/creating-connections/create-a-connect-token) for how to prepopulate the picker. # Connection Errors and Reasons Source: https://docs.meld.io/docs/bank-linking/get-connection-data/connection-statuses-and-errors/connection-errors-and-reasons Each connection status has an associated `statusReason` that provides additional insight into **why** the connection is in that status and **what action**, if any, you should take. This page is for developers writing lifecycle handlers — use the recommended action column to decide whether to surface a reconnect prompt, schedule a retry, or treat the connection as terminal. For the high-level status definitions, see [Connection Statuses and Errors](/docs/bank-linking/get-connection-data/connection-statuses-and-errors). ## Status and reason reference | Status | Status Reason | Description | Can be refreshed? | Can reconnect? | Recommended developer action | | :------------------- | :--------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------- | :------------- | :---------------------------------------------------------------------------------------------------------- | | `IN_PROGRESS` | `null` | Customer is in the process of connecting within the widget. | No | No | Wait for the widget flow to finish; do not call refresh or repair. | | `EXPIRED` | `null` | Customer abandoned the widget or did not complete the connection in 4 hours. | No | No | Terminal. Create a new connection if the customer returns. | | `ACTIVE` | `null` | Connection completed and is healthy. Wait for webhooks to know when aggregation finishes. | Yes | No | Normal operation. Listen for `BANK_LINKING_ACCOUNTS_UPDATED` and `BANK_LINKING_TRANSACTIONS_AGGREGATED`. | | `ACTIVE` | `ACCESS_EXPIRING_SOON` | Access will expire eventually. Customer can reconnect to prevent expiry. | Yes | Yes | Prompt the customer to reconnect in your UI; call the repair endpoint to generate a connect token. | | `ACTIVE` | `NEW_ACCOUNTS_AVAILABLE` | New accounts can be added if the customer reconnects. | Yes | Yes | Optionally surface a "link new accounts" prompt and call the repair endpoint. | | `PARTIALLY_ACTIVE` | `PARTIAL_AGGREGATION_SUCCESS` | Aggregation was partially successful; some data or products are missing. | Yes | Yes | Refresh to try again; if it persists, repair the connection. | | `PARTIALLY_ACTIVE` | `INACTIVE_DUPLICATE` | The connection is a duplicate of another connection through the same service provider. See [Duplicate Connections](/docs/bank-linking/get-connection-data/duplicate-connections). | Yes | Yes | Decide whether to keep this duplicate. Delete it if unused; otherwise refresh to promote it to `ACTIVE`. | | `DEGRADED` | `INSTITUTION_CURRENTLY_UNAVAILABLE` | Temporary institution issues. | Yes | Yes | Retry later with exponential backoff; do not prompt the customer immediately. | | `DEGRADED` | `INSTITUTION_ACTION_REQUIRED` | Customer action with their institution is required before a refresh can succeed (e.g., re-authorize access in the bank's app). | Yes | Yes | Forward the message from the `BANK_LINKING_CONNECTION_STATUS_CHANGE` webhook to the customer; then refresh. | | `DEGRADED` | `SERVICE_PROVIDER_CURRENTLY_UNAVAILABLE` | Service provider is experiencing issues. | Yes | Yes | Retry later. If persistent, contact Meld support. | | `DEGRADED` | `INTERACTIVE_REFRESH` | Customer participation is required for every refresh on this provider/institution. | Yes | Yes | Avoid scheduled refreshes; only refresh in response to a customer action that returns them to your UI. | | `RECONNECT_REQUIRED` | `LOGIN_REQUIRED` | Credentials changed or MFA is required. | Yes | Yes | Send the customer through the repair flow. | | `RECONNECT_REQUIRED` | `REPAIRING_INSTITUTION` | Institution is being repaired on the provider's side. | Yes | Yes | Continue to attempt reconnects periodically until the institution is back. | | `RECONNECT_REQUIRED` | `TOKEN_EXPIRED` | Service provider's connection with the institution expired and needs re-authentication. | Yes | Yes | Send the customer through the repair flow. | | `RECONNECT_REQUIRED` | `OTHER` | Various other reasons requiring reconnection. | Yes | Yes | Inspect `serviceProviderDetails` for specifics; send the customer through the repair flow. | | `UNRECOVERABLE` | `CONTINUED_AGGREGATION_FAILURES` | Aggregation has failed at least three times and has not succeeded for at least two weeks. | No | Yes | Treat as broken. Optionally invite the customer to start a fresh connection. | | `UNRECOVERABLE` | `INSTITUTION_DISABLED` | The institution is no longer supported by the provider. | No | Yes | Direct the customer to connect a different institution or via a different provider via routing. | | `UNRECOVERABLE` | `OTHER` | Various other reasons that have made the connection unrecoverable. | No | Yes | Inspect `serviceProviderDetails`; consider creating a new connection. | | `UNDETERMINED` | `AGGREGATION_TIMEOUT` | A timeout prevented the service provider aggregation from completing. | Yes | Yes | Retry the refresh after a short delay. | | `UNDETERMINED` | `SUBMIT_SERVICE_PROVIDER_TICKET` | Submit a ticket to the provider; Meld cannot resolve. | Yes | Yes | Open a ticket with the underlying service provider; retry the refresh after they confirm a fix. | | `UNDETERMINED` | `UNKNOWN_SERVICE_PROVIDER_ERROR_CODE` | A provider error code Meld has not seen before. | Yes | Yes | Notify Meld support with the `connectionId` and the raw provider error; retry the refresh. | | `UNDETERMINED` | `INTERNAL_ERROR` | Meld internal error. | Yes | Yes | Notify Meld support; retry the refresh. | | `DELETED` | `null` | Connection was deleted. | No | No | Terminal. Start a new connection if the customer wants to reconnect. | The `BANK_LINKING_CONNECTION_STATUS_CHANGE` webhook fires whenever a connection moves between statuses or status reasons. Always use this webhook as the source of truth — do not poll the connections endpoint on a schedule.
# Connection Statuses and Errors Source: https://docs.meld.io/docs/bank-linking/get-connection-data/connection-statuses-and-errors/index Every Meld bank-linking connection has a **status** that reflects its current health and what actions you can take on it. Understanding these statuses is essential for any developer handling the connection lifecycle — knowing when to refresh, when to ask the customer to reconnect, and when a connection is unrecoverable. This page lists every status, what it means, and which lifecycle operations it supports. For the granular `statusReason` values and the recommended action per reason, see [Connection Errors and Reasons](/docs/bank-linking/get-connection-data/connection-statuses-and-errors/connection-errors-and-reasons). ## Connection statuses A connection is started immediately upon initialization in the [Create a Connection](/docs/bank-linking/creating-connections/create-a-connect-token) endpoint and can transition through multiple statuses over its lifespan. Connection status changes to error states typically occur during daily aggregations. ***Note*: Connection statuses are only for the connection, not its financial accounts. Financial account statuses are entirely independent and will only ever be active/inactive.** ***Note*: All connection status changes occur after the widget flow is completed/abandoned (except for when it is first created and set to `IN_PROGRESS`). Therefore any errors within the service provider widget flow that are displayed for the customer will not lead to connection status changes and are usually resolvable by the customer immediately (i.e. "wrong password, try again"). For information about these widget errors, this information is recorded in the [activity log](/docs/bank-linking/testing-and-debugging).** Below are the various connection statuses and what they mean: * `IN_PROGRESS` - A connection is in progress when it is first initialized and the customer is in the widget flow. It will remain in this status until it completes and becomes `ACTIVE` or is never finished and becomes `EXPIRED`.\ ***Note*: Connections that are being actively repaired do not go back to `IN_PROGRESS`. They will stay in their previous status until completed.**\ ***Note*: A connection with this status cannot be refreshed nor repaired as it is currently in progress with the customer.** * `EXPIRED` - A connection becomes expired if it was never completed in the allowed timeframe (4 hours) since its initialization. This usually indicates the customer abandoned the widget flow or encountered issues within the service provider widget and never finished it.\ ***Note*: A connection with this status is terminal and cannot be refreshed nor repaired.** * `ACTIVE` - A connection becomes active once the user successfully completes the widget flow and access to the underlying financial accounts is authorized. The [BANK\_LINKING\_CONNECTION\_COMPLETED](/docs/bank-linking/webhook-events-bank-linking) webhook will indicate once a connection becomes `ACTIVE`. A connection will remain active indefinitely unless an error occurs during a forced or background refresh, or it is deleted. Following the initial connection, an `ACTIVE` status does not necessarily imply that the initial aggregation of desired products has completed, but rather that the connection to the underlying institution is complete and aggregation is in progress. The [BANK\_LINKING\_ACCOUNTS\_UPDATED](/docs/bank-linking/webhook-events-bank-linking) webhook will notify of aggregation completion.\ ***Note*: A connection with this status is refreshable, but not repairable (unless the `statusReason` is `ACCESS_EXPIRING_SOON` or `NEW_ACCOUNTS_AVAILABLE`.** * `PARTIALLY_ACTIVE` - This status represents connections that may have partially aggregated, but not fully. This can occur if only some accounts belonging to the connection successfully aggregated, but not all. This status is also used for duplicate connections that have the same underlying service provider id, and only the active duplicate is routinely updated. In such case, the status reason will be `INACTIVE_DUPLICATE`.\ ***Note*: A connection with this status is refreshable and repairable.** * `DEGRADED` - The connection is experiencing temporary issues that are preventing aggregation, but it is still "connected". This can occur due to temporary institution issues, or the customer may need to take action with their institution directly (such as authorizing access). In the latter case, it is recommended to forward along this message to the customer, and the specific reasons/actions for this status can be found in the service provider details of the [BANK\_LINKING\_CONNECTION\_STATUS\_CHANGE](/docs/bank-linking/webhook-events-bank-linking) webhook.\ ***Note*: A connection with this status is refreshable and repairable.** * `RECONNECT_REQUIRED` - Indicates that a once `ACTIVE` connection is now failing and needs to be repaired in order to capture the most up to date data. When a connection enters this status, you will be notified via webhooks of the status change. Depending on the cause for error, the connection should be repaired via the [repair flow](/docs/bank-linking/manage-connection-status/repairing-connections). A common situation that might cause this is if a customer changes their bank password after connecting their accounts, or the underlying service provider consent to the accounts expired. Repairing the connection will make it `ACTIVE` again.\ ***Note*: A connection with this status cannot be refreshed and will not update until it is repaired.** * `UNRECOVERABLE` - The connection is permanently broken and can no longer be aggregated. We still allow repairing the connection in the rare chance that it can be recovered.\ ***Note*: A connection with this status is repairable but not refreshable.** * `DELETED` - A connection that is marked deleted is in a final state. This indicates that it will no longer be updated nor actively billed. A customer revoking consent to an existing connection with their bank will cause a connection to be deleted. Deleting a connection can also be triggered from the client-side, i.e. via the [delete a connection](/docs/bank-linking/manage-connection-status/deleting-connections) endpoint. Deleting a customer will also delete all of its connections. A [BANK\_LINKING\_CONNECTION\_DELETED](/docs/bank-linking/webhook-events-bank-linking) webhook will be sent when a connection has been deleted. A deleted connection cannot be recovered; a new connection must be started.\ ***Note*: A connection with this status is terminal and cannot be refreshed nor repaired.** * `UNDETERMINED` - Indicates an unforeseen service provider error, or an internal Meld error. Refreshes and reconnects are allowed as the error's true cause is not yet known.\ ***Note*: A connection with this status can be both refreshed and repaired as its true status is not yet known.** To understand why a connection may be in each of these statuses and learn if the connection can be refreshed or reconnected, see [Connection Errors and Reasons](/docs/bank-linking/get-connection-data/connection-statuses-and-errors/connection-errors-and-reasons). ## Widget Flow Errors We recommend capturing the following events to close the widget iframe when an error occurs or your customer does not complete the authentication step. * `[meld-connect]error` -- Emitted when the widget encounters an error * `[meld-connect]cancel` -- Emitted when your customer cancels the widget # Data Normalization Source: https://docs.meld.io/docs/bank-linking/get-connection-data/data-normalization Each service provider returns financial data with slightly different conventions — signage, status names, and category labels. Meld normalizes these into a single consistent shape so your code does not need provider-specific branches. This page is for developers consuming Meld's APIs who want to understand the normalized values and the rules behind them. Raw provider responses are still available in the `serviceProviderDetails` field on every endpoint and in the Activity Log. ## Balance signage Balance signage is generally **positive** for all account types — including loan, mortgage, and credit accounts, where a positive balance indicates the amount the customer owes. ## Transaction signage Service providers use different signage for deposits vs. withdrawals. Meld standardizes to: * **Positive** — money has moved **out** of the account. Includes withdrawals and debits. * **Negative** — money has moved **into** the account. Includes deposits. ## Investment types For both investment holdings and investment transactions, Meld normalizes the investment `type` in the response. * **Investment holdings:** `STOCK`, `MUTUALFUND`, `CASH`, `OTHER`, `REALESTATE`, `ETF`, `DERIVATIVE`, `DIGITALASSET`. * **Investment transactions:** `SELL`, `BUY`, `CANCEL`, `CASH`, `OTHER`, `TRANSFER`. ## Transaction status Transactions have one of the following statuses: * **Pending** — All transactions start as `PENDING`. A pending transaction has been approved but not yet fully processed. * **Posted** — `PENDING` transactions move to `POSTED` within a few days, typically one to five business days, with a rare two-week maximum. * **Expired** — On rare occasions, `PENDING` transactions are cancelled and stop appearing in provider responses. When this occurs, Meld marks them `EXPIRED`. When updating transaction data, fetch the last 14 days to capture status and amount changes. After two weeks, transactions almost never change. ## Transaction categories While providers group transactions into their own categories, Meld also normalizes transaction categories across providers. For the complete provider-to-Meld category mapping, refer to the [account transactions reference](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-transactions) and the [Bank Linking API reference](/api-reference/bank-linking). # Duplicate Connections Source: https://docs.meld.io/docs/bank-linking/get-connection-data/duplicate-connections Duplicate connections occur when multiple Meld connections reference the same underlying service provider connection. This occurs when the **same customer connects to the same bank account(s) through the same service provider in separate connect sessions using the same login credentials**. In such cases, the majority of service providers recognize that the connection already exists and will return the same id for it. However, since Meld generates connection ids immediately upon creation (prior to the customer actually linking their accounts and prior to knowing whether it will be a duplicate) then this means there will now be multiple Meld connections linked to the same underlying service provider connection id. Therefore it only becomes necessary to maintain one of these Meld connections to avoid excessive refreshing and redundancy. In such cases, the most recently connected of these duplicate Meld connections is considered `ACTIVE` and routinely maintained, with the remainder moving to status `PARTIALLY_ACTIVE` and `statusReason` `INACTIVE_DUPLICATE`. These `INACTIVE_DUPLICATE` Meld connections still share the same underlying financial accounts, transactions, etc. as the primary duplicate — the difference is that this data is refreshed once per service provider update rather than for every duplicate. At any time you can promote a `PARTIALLY_ACTIVE` connection to `ACTIVE` by [manually refreshing it](/docs/bank-linking/manage-connection-status/refreshing-connections), which demotes the previously `ACTIVE` duplicate to `PARTIALLY_ACTIVE`. Whenever a connection moves to `PARTIALLY_ACTIVE`, you will receive a `BANK_LINKING_CONNECTION_STATUS_CHANGE` [webhook](/docs/bank-linking/webhook-events-bank-linking). If duplicate connections exist, the `duplicateConnectionIds` list returned by the connections endpoint contains the ids of all of them ordered from most recently aggregated to oldest. Otherwise this list is `null`. Refer to the [Bank Linking API reference](/api-reference/bank-linking) for the connections endpoint schema. When deleting connections, all of its duplicates must be deleted in order to sever the connection with the underlying service provider and stop it from being billed. **Meld does not ever automatically delete duplicates but encourages you to do so if you are not using them.** Note: Duplicate connections will all belong to the same service provider and have the same service provider connection id. **If the user connects via different providers, then the connections will NOT be considered duplicates.** For service providers (such as Plaid) that DO NOT identify duplicate connections (and thus always use a unique id for each new connection regardless of whether the customer has already connected the same financial accounts), then the associated Meld connections will not be considered duplicates either. This is because the service provider still treats them as separate connections and they are billed separately.
# Get Connection Data Source: https://docs.meld.io/docs/bank-linking/get-connection-data/index After a customer completes the Meld Connect Widget flow, you can begin fetching their financial data — balances, owners, identifiers, transactions, and investments. This section is for developers building backend integrations that listen for connection completion, wait for data to be available, and call Meld's APIs to retrieve it. ## Before you begin * A connection has been created through [Creating Connections](/docs/bank-linking/creating-connections) * A webhook profile is configured so you receive lifecycle events — see [Webhook Quickstart](/docs/bank-linking/onboarding/step-5-setting-up-webhooks/webhook-quickstart) * Your backend has access to the customer's `connectionId` or `customerId` ## Steps 1. Review the [widget emitted events](/docs/bank-linking/get-connection-data/widget-emitted-events). These tell you when a connection has been successfully completed or if there was an error during the flow. 2. Understand [when your data is available](/docs/bank-linking/get-connection-data/when-is-your-data-available). Different products are aggregated on different timelines, and webhooks indicate when each is ready. 3. Once your data is available, [retrieve your connection data](/docs/bank-linking/get-connection-data/retrieving-connection-data). This covers balances, owners, identifiers, transactions, and investments. 4. Review the [connection statuses and errors](/docs/bank-linking/get-connection-data/connection-statuses-and-errors). As connections move out of `ACTIVE`, learn how to refresh, repair, or delete them in the next section. The data Meld returns is normalized across providers — see [Data Normalization](/docs/bank-linking/get-connection-data/data-normalization) for details on signage, categories, and investment types. ## Informing the user of the status There will likely be a delay between your user completing the widget flow and you receiving webhooks indicating data is ready. This can be a few seconds for balance, identifier, and owner data, or several minutes for transactions on accounts with large histories. Show a loading state in your UI during this time so users don't think the connection failed and try again. ## Endpoint quick reference | Data | Endpoint | Reference | | :---------------------- | :------------------------------------------ | :---------------------------------------------------------------------------------------------- | | Balances / Owners / IDs | `GET /bank-linking/financial-accounts` | [Retrieving Connection Data](/docs/bank-linking/get-connection-data/retrieving-connection-data) | | Transactions | `GET /bank-linking/transactions` | [Retrieving Connection Data](/docs/bank-linking/get-connection-data/retrieving-connection-data) | | Investment Holdings | `GET /bank-linking/investment-holdings` | [Retrieving Connection Data](/docs/bank-linking/get-connection-data/retrieving-connection-data) | | Investment Transactions | `GET /bank-linking/investment-transactions` | [Retrieving Connection Data](/docs/bank-linking/get-connection-data/retrieving-connection-data) | | Connections metadata | `GET /bank-linking/connections` | [Retrieving Connection Data](/docs/bank-linking/get-connection-data/retrieving-connection-data) | Refer to the [Bank Linking API reference](/api-reference/bank-linking) for the complete request and response schema for each endpoint. # Retrieving Connection Data Source: https://docs.meld.io/docs/bank-linking/get-connection-data/retrieving-connection-data Once your customer has successfully completed a connection and Meld has signaled that data is ready (see [When Is Your Data Available](/docs/bank-linking/get-connection-data/when-is-your-data-available)), you can call the endpoints below to read their financial data. This page is for backend developers fetching connection data from Meld's REST API. ## Before you begin * You have received the `BANK_LINKING_ACCOUNTS_UPDATED` or `BANK_LINKING_TRANSACTIONS_AGGREGATED` webhook for the product you intend to query * You know the `customerId`, `externalCustomerId`, `connectionId`, or `financialAccountId` you want to scope the query to * Your API key has access to the relevant products ## Accessing customer financial data Meld's data endpoints follow two patterns: * **Get a single result by id** — pass the resource id. * **Filter through results by query parameters** — typical filters include: * `externalCustomerId` — the id you provided Meld for your customer. * `customerId` — the id Meld assigned for your customer. * `institutionId` — the Meld id for a financial institution. * `financialAccountId` — the Meld id for your customer's financial account. ### Financial data To retrieve [Balances](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-balances), [Identifiers](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-identifiers), or [Owners](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-owners), use the financial accounts endpoint. To retrieve [Transactions](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-transactions), use the transactions endpoint. Transactions are ordered newest first per financial account. To retrieve [Investment Holdings](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-investments), use the investment holdings endpoint. To retrieve **Investment Transactions**, use the investment transactions endpoint. ### Metadata To retrieve a list of connections, use the connections endpoint. For the complete request and response schema for each endpoint listed above, refer to the [Bank Linking API reference](/api-reference/bank-linking). For updating transaction data efficiently on subsequent calls, see [When Is Your Data Available — Updating Transaction Data](/docs/bank-linking/get-connection-data/when-is-your-data-available). The recommended approach is to fetch only the last 14 days when handling routine background aggregations. # When Is Your Data Available Source: https://docs.meld.io/docs/bank-linking/get-connection-data/when-is-your-data-available After a connection completes, Meld goes through an **aggregation** process to load each product's data from the underlying service provider. Aggregation does not finish instantly — it happens in stages, and different products become available at different times. This page is for developers wiring up webhook handlers and deciding when it is safe to query Meld's data endpoints for a given product. ## Before you begin * You have a webhook profile configured — see [Webhook Quickstart](/docs/bank-linking/onboarding/step-5-setting-up-webhooks/webhook-quickstart) * You can match incoming webhooks to a `connectionId` in your system * You understand the [list of bank-linking webhook events](/docs/bank-linking/webhook-events-bank-linking) ## Account aggregations Account aggregations represent the process of loading balance, transaction, and other account data for each account belonging to an institution connection. Aggregations are kicked off for a connection in each of the following scenarios: 1. **Following initial connection:** An aggregation process is initiated whenever an institution connection is successfully made in the Meld widget. Depending on the underlying service provider, this process may even begin prior to the customer exiting the widget. 2. **During routine background refreshes**: Background refreshes are ran behind the scenes to ensure that the connection and account data do not get stale. Depending on the service provider, these background aggregations typically occur from 1-3 times a day. 3. **During forced refreshes:** When a connection is refreshed via the [Meld refresh endpoint](/docs/bank-linking/manage-connection-status/refreshing-connections) it triggers the underlying service provider to perform a *live* aggregation process as of that moment. Since background aggregations already occur on a frequent basis, forced refreshes usually aren't necessary, and they incur extra charges. ## Data availability during aggregations Aggregation data becomes available in stages as an aggregation process completes, and webhooks are sent out coinciding with each step of this process. More details about each webhook can be found [in Webhook Events: Bank Linking](/docs/bank-linking/webhook-events-bank-linking). Some products load quicker than others, and this enables the access of basic account info and other fast-loading products immediately without having to wait for the entire aggregation process to complete. ### Webhook flow During an aggregation process expect webhooks as follows: 1. The [BANK\_LINKING\_CONNECTION\_COMPLETED](/docs/bank-linking/webhook-events-bank-linking) webhook is sent to indicate that the connection has successfully completed. However, this doesn't mean that data is available yet. Wait till the following webhooks arrive to know when your data can be fetched. 2. The [BANK\_LINKING\_ACCOUNTS\_UPDATING](/docs/bank-linking/webhook-events-bank-linking) webhook is sent once the financial accounts belonging to a connection are identified and in the process of aggregating. This webhook contains basic info such as id, type, and name for identifying the accounts, but does not mean the accounts nor their desired products have aggregated yet. 3. The [BANK\_LINKING\_ACCOUNTS\_UPDATED](/docs/bank-linking/webhook-events-bank-linking) webhook is sent once the aggregation of non-transaction products for the accounts belonging to the connection is complete. It also details which products were loaded for each individual account, since not all account types support all products. Depending on the institution and which products were specified in the initial call to [create a connection](/docs/bank-linking/creating-connections/create-a-connect-token), this webhook may take slightly longer to send. If the only product belonging to the connection is `TRANSACTIONS` then this webhook will never be sent. At this point, up-to-date financial account info is now ready to be fetched, and the Meld APIs can be queried for more details about each of the financial accounts belonging to the connection. 4. The [BANK\_LINKING\_TRANSACTIONS\_AGGREGATED](/docs/bank-linking/webhook-events-bank-linking) webhook is sent once transactions have finished aggregating for all the accounts belonging to the connection that have them. It also details how many transactions were added, updated, or removed for each account since the last aggregation occurred. Depending on how many transactions the financial account has, this webhook could take some time to arrive as the provider may take time to fetch all the transactions from the bank. At this point, up-to-date transaction data can now be queried for the accounts belonging to the connection 5. The [BANK\_LINKING\_HISTORICAL\_TRANSACTIONS\_AGGREGATED](/docs/bank-linking/webhook-events-bank-linking) webhook is sent once historical transactions (up to 2 years of data) have finished aggregating for all accounts belonging to the connection that have them. This aggregation process can take a while longer than the others to finish, so it usually arrives last.\ **Note:** This aggregation will only ever occur at most once for a connection. By default, historical transactions are loaded during the initial aggregation, but retry attempts will be made during subsequent aggregations if it failed originally. Aggregating historical transactions is a premium service for Finicity, so if this is not desired please reach out to Meld support to disable this feature. After doing so, you can still aggregate historical transactions if so desired for any Finicity connection that has transactions as an available product upon the first time a forced [refresh](/docs/bank-linking/manage-connection-status/refreshing-connections) is triggered. ## Updating transaction data Initially, only 30 days of transaction history is made available following the first aggregation of a connection with `TRANSACTIONS` enabled as a product. Not until the `BANK_LINKING_HISTORICAL_TRANSACTIONS_AGGREGATED` webhook is received does transaction history beyond 30 days become available. When subsequent aggregations occur, updating transactions becomes necessary to maintain transaction health and capture any changes. Examples of changes include the transition of a transaction from `PENDING` to `POSTED`, or modification to the amount of a transaction (such as from a tip being added). See [Transaction States](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-transactions) for more details. Transactions that do change typically only do so within 2 weeks of the original transaction date. Beyond 14 days, it is unlikely a transaction will ever be modified. When updating transactions in response to routine background aggregations, fetch the last **14 days** of transactions. Anything older is extremely unlikely to have changed. ## Sandbox vs. production timing **Aggregation timing differs between sandbox and production.** * In **sandbox**, providers typically aggregate test fixtures within seconds, so the entire webhook flow (from `BANK_LINKING_CONNECTION_COMPLETED` to `BANK_LINKING_HISTORICAL_TRANSACTIONS_AGGREGATED`) can complete in well under a minute. * In **production**, aggregations rely on live provider calls to real institutions. Non-transaction products usually arrive within seconds to a few minutes, but transactions — especially historical transactions on accounts with long histories — can take several minutes to load. * **Background refresh cadence** is also different: sandbox connections typically do not run scheduled background refreshes the way production connections do. To simulate a refresh in sandbox, use the [refresh endpoint](/docs/bank-linking/manage-connection-status/refreshing-connections) directly. Always rely on the webhook events to know when data is ready — do not assume a fixed delay. # Widget Emitted Events and Errors Source: https://docs.meld.io/docs/bank-linking/get-connection-data/widget-emitted-events The Meld Connect Widget emits messages to the host page throughout the connection lifecycle. This page is for front-end developers wiring up event listeners — for example, to close the widget when the connection completes or to surface errors to the customer. Events are visible in the browser console; Meld also uses them internally for debugging. **The bank picker will emit both Meld and service provider events. The Meld events normalize the service provider events (aka are consistent across providers) and therefore you should ONLY listen to the meld events, not the service provider events. The Meld events will have*domain: "meld-connect"* in them to distinguish them.** # Types of events emitted by the widget Here are a list of Meld events emitted by the widget, along with what each means and what to do with them. 1. `{ domain: "meld-connect", eventName: "init" }` 1. Emitted when the widget initializes. 2. Use this to know that the widget has been initialized so that you can track a session. 2. `{ domain: "meld-connect", eventName: "debug" }` 1. Emitted at various times with debug information. Examples include when your customer cancels an integrated service provider's embedded widget or whenever your customer navigates to another screen within an integrated service provider's embedded widget. 2. This event is used by Meld for debugging. It is not necessary for you to listen to this event. 3. `{ domain: "meld-connect", eventName: "error" }` 1. Emitted when the widget encounters an error. This event will be accompanied by query-string metadata, containing `details` and `reason` keys, as available. 2. This event tells you if a user runs into a service provider error while trying to connect to their bank. It is not necessary for you to listen to this event. 4. `{ domain: "meld-connect", eventName: "connect_complete" }` 1. Emitted once the internal API call to /connect/complete has finished. It will be accompanied by query-string metadata, containing: 1. institutionId: Meld's institution ID for the institution your customer chose to connect with 2. institutionName: The name of the same institution 3. accountCount: The number of accounts your customer has connected 4. accounts: Where available, an array of connected accounts including details such as name, type, and mask; null otherwise 2. This event tells you the connection has completed successfully, and you can close the widget. Note that the connection may still be aggregating (aka gathering data), but will still succeed with the widget closed as the user's involvement is done. 5. `{ domain: "meld-connect", eventName: "cancel" }` 1. Emitted when your customer cancels the widget. 2. This event lets you know that the customer has decided to leave the widget without successfully completing a connection. Use this event to trigger closing the widget. 6. `{ domain: "meld-connect", eventName: "handover" }` 1. Emitted when the connect has been completed. This is a legacy event that has been made redundant by "connect\_complete". 2. It is not necessary for you to listen to this event, in fact it is advised that you don't, as "connect\_complete" serves the same purpose. # Listening for Emitted Events After your customer has successfully authenticated themselves, the Meld Connect Widget will emit an event indicating that the widget can now be closed and the connection will still succeed. On most platforms, this event will have a `data` object, which will contain a `domain` (always `"meld-connect"`), an `eventName`, and a `metadata` object (where appropriate). However, for iOS and Android, these events will be serialized into a string, in the format `[]?<&-delimited-metaData>`. If you wish to close the iframe at this point, you can capture the `[meld-connect]connect_complete` event. This can be done in a few different ways, depending on how you have embedded the widget. **Desktop / Mobile browser** If you have embedded the widget into a page running in a full web browser, you can make use of that browser's `addEventListener` function: ```javascript theme={null} window.addEventListener( "message", (event) => { if (event.data === '[meld-connect]connect_complete') { // close the Meld Connect Widget } }, false, ); ``` **React Native** When instantiating your WebView, you simply need to provide the component with an `onMessage` function: ```html React theme={null} { if (event.data === '[meld-connect]connect_complete') { // close the Meld Connect Widget } }} /> ``` **Android WebView** If you have embedded the widget as a WebView inside of your Android app, you will need to create a class that implements the `@JavascriptInterface` interface, and add that to the webview. The name of the class is unimportant, but the Connect Widget knows to look for a namespace named `Android` that contains a function named `postMessage`, so those names are required. ```java theme={null} private class JsObject { @JavascriptInterface public void postMessage(String event) { if (event === "[meld-connect]connect_complete") { // close the Meld Connect Widget } } } yourWebView.addJavascriptInterface(new JsObject(), "Android"); ``` **iOS WKWebView** Embedding the widget in an iOS app requires o`WKWebView` set up to handle callback messages. Configure a `WKWebView` with `userContentController`: ```swift theme={null} class ConnectController: UIViewController { let meldMessageHandler = "meldMessageHandler" override func viewDidLoad() { let configuration = WKWebViewConfiguration() configuration.userContentController.add(self, name: meldMessageHandler) let webView = WKWebView(frame: .zero, configuration: configuration) } } extension ConnectController: WKScriptMessageHandler { func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { if message.name == meldMessageHandler, let body = message.body as? String, body == "[meld-connect]connect_complete" { // dismiss the controller } } } ``` We recommend also capturing the following events to close the iframe when an error occurs, or your customer does not complete the authentication step. * `[meld-connect]error` -- Emitted when the widget encounters an error * `[meld-connect]cancel` -- Emitted when your customer cancels the widget Once you have received the `connect_complete` event, the widget flow is complete. After [setting up a webhook configuration with Meld](/docs/bank-linking/onboarding/step-5-setting-up-webhooks/webhook-quickstart), you will receive the [BANK\_LINKING\_CONNECTION\_COMPLETED](/docs/bank-linking/webhook-events-bank-linking) webhook notifying you of official connection completion, followed by the other aggregation-related webhooks that signal when account and transaction data is ready to be fetched from the Meld API. See [Retrieving Connection Data](/docs/bank-linking/get-connection-data/retrieving-connection-data) for next steps and more detail on loading account and transaction data. # Deleting Connections Source: https://docs.meld.io/docs/bank-linking/manage-connection-status/deleting-connections When a customer no longer needs a connection — they revoked access, switched banks, or the connection is a duplicate — you can delete it. This page is for developers cleaning up connections so they no longer receive webhooks or incur billing. ## Before you begin * You have the `connectionId` you want to delete * You understand deletion is **permanent** — a deleted connection cannot be recovered * For [duplicate connections](/docs/bank-linking/get-connection-data/duplicate-connections), remember to delete all duplicates if you want to fully sever the underlying provider link ## How to delete a connection Call the [delete connections endpoint](/api-reference/bank-linking) with the `connectionId`. Once you delete a connection: * It is deleted on **both** Meld and the underlying service provider. * You will no longer receive any updates (webhooks) for it. * You will no longer be billed for it. * A `BANK_LINKING_CONNECTION_DELETED` [webhook](/docs/bank-linking/webhook-events-bank-linking) is sent to confirm. You can only delete a **connection**, not individual financial accounts under a connection. To stop tracking a subset of a customer's accounts, the customer must reconnect and choose which accounts to share. For the full request and response schema, refer to the [Bank Linking API reference](/api-reference/bank-linking). ## Common errors | HTTP status | Likely cause | Developer action | | :-------------- | :------------------------------- | :------------------------------------------------------------------------------------------------------------------- | | `404 Not Found` | Invalid `connectionId` | Verify the id and the environment (sandbox vs production). | | `409 Conflict` | Connection is already `DELETED` | No action needed — confirm with a `GET` on the connection. | | `5xx` | Transient provider or Meld issue | Retry with backoff. The Meld side may succeed even if the provider call retries; the webhook is the source of truth. | # Importing Connections Source: https://docs.meld.io/docs/bank-linking/manage-connection-status/importing-connections If you already have connections with a service provider — for example, because you previously integrated directly with Plaid or Akoya — you can bring them into Meld. Importing a connection does not involve the customer; there is no widget flow and no re-authentication required. This page is for developers migrating from a direct provider integration to Meld. ## Before you begin * You have provider-level credentials (Plaid `access_token`, Akoya equivalent) for each connection you want to import * The corresponding service provider is enabled on your Meld account * You have decided what `externalCustomerId` to associate with each imported connection **Importing is currently supported for Plaid and Akoya only.** For other providers, the customer must complete the standard widget flow. ## How to import a connection Call the [import connections endpoint](/api-reference/bank-linking). Use the `externalCustomerId` field to tie each imported connection to an id of your choice for tracking. Importing a connection does the following: 1. Adds the connection and all its financial accounts to Meld. 2. Ties the connection to your `externalCustomerId`, if provided. 3. Tells the underlying provider to send webhooks for that connection to Meld going forward (instead of to your servers). 4. Refreshes the connection at the time of import. 5. Sends you the standard aggregation webhooks (`BANK_LINKING_ACCOUNTS_UPDATING`, `BANK_LINKING_ACCOUNTS_UPDATED`, `BANK_LINKING_TRANSACTIONS_AGGREGATED`) just like a fresh connection. Once a connection is imported, the provider will stop sending webhooks to your old endpoint. Make sure your Meld webhook profile is configured before importing in production. For the full request and response schema, refer to the [Bank Linking API reference](/api-reference/bank-linking). ## Common errors | Symptom | Likely cause | Developer action | | :------------------------------------------ | :---------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------- | | `400 Bad Request` — provider not supported | Importing only works for Plaid and Akoya | Use the standard widget flow for other providers. | | `401` or `403` from the underlying provider | Provider-level credentials supplied are invalid or expired | Verify the `access_token` is valid for the environment; re-issue if needed. | | Import succeeds but no webhooks arrive | Meld webhook profile not configured for bank-linking events | Configure the webhook profile per [Webhook Quickstart](/docs/bank-linking/onboarding/step-5-setting-up-webhooks/webhook-quickstart). | # Manage Connections Source: https://docs.meld.io/docs/bank-linking/manage-connection-status/index Over its lifetime, a connection may need to be **refreshed**, **repaired**, **updated**, **deleted**, or **imported**. This section is for developers building lifecycle handling on top of Meld bank linking. The four most-confused operations — refresh, repair, update, and import — all mutate the connection in different ways. The terminology matters: using the wrong endpoint will either no-op or charge for an operation that wasn't needed. ## Refresh vs. repair vs. update vs. delete vs. import | Operation | What it does | When to use it | Customer involved? | Status before | Status after | Endpoint | | :---------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------ | :---------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------- | :----------------------------------------------------------------------------------------------- | | **Refresh** | Pulls the latest balances and transactions from the underlying provider for an existing, healthy connection. | The customer just took an action in your app and you want up-to-the-minute data. Don't use for routine polling — background refresh handles that. | No | `ACTIVE`, `PARTIALLY_ACTIVE`, `DEGRADED`, `UNDETERMINED` | Same status; new aggregation webhooks | [Refreshing Connections](/docs/bank-linking/manage-connection-status/refreshing-connections) | | **Repair** | Generates a new connect token that takes the customer straight to the institution login so they can re-authenticate. Brings a broken connection back online. | The customer changed their bank password, MFA changed, or the provider consent expired. The connection moved to `RECONNECT_REQUIRED`. | **Yes** — they go through the widget again | `RECONNECT_REQUIRED`, `DEGRADED`, `UNRECOVERABLE`, or `ACTIVE` (when `ACCESS_EXPIRING_SOON` / `NEW_ACCOUNTS_AVAILABLE`) | `ACTIVE` | [Repairing Connections](/docs/bank-linking/manage-connection-status/repairing-connections) | | **Update** | Adds new **products** (e.g., `INVESTMENT_HOLDINGS`) to an existing connection. New products are added as `optionalProducts` and aggregated on a best-effort basis. | You initially requested `BALANCES` and `TRANSACTIONS`, and now you want investment data on the same connection without a fresh widget flow. | No (provider-dependent — may require repair flow) | Any non-terminal | Same status; new product aggregation kicks off | [Updating Connection Products](/docs/bank-linking/manage-connection-status/updating-connections) | | **Delete** | Severs the connection on Meld and the underlying provider. No more webhooks, no more billing. Cannot be undone. | The customer no longer wants this account linked, or you are cleaning up duplicates. | No | Any | `DELETED` | [Deleting Connections](/docs/bank-linking/manage-connection-status/deleting-connections) | | **Import** | Brings an existing provider connection into Meld without sending the customer through the widget. | You already have Plaid or Akoya connections and you are migrating to Meld. | No | n/a (new in Meld) | `ACTIVE` | [Importing Connections](/docs/bank-linking/manage-connection-status/importing-connections) | ## Quick guide 1. [Refresh a connection.](/docs/bank-linking/manage-connection-status/refreshing-connections) Use for one-off data pulls. Background refreshes run automatically; do not refresh on a schedule from your code. 2. [Repair a connection.](/docs/bank-linking/manage-connection-status/repairing-connections) Use when the connection is in `RECONNECT_REQUIRED` (or `DEGRADED` with a customer-actionable reason). 3. [Update a connection's products.](/docs/bank-linking/manage-connection-status/updating-connections) Use to add `BALANCES`, `INVESTMENT_HOLDINGS`, etc. retroactively without re-prompting the customer. 4. [Delete a connection.](/docs/bank-linking/manage-connection-status/deleting-connections) Use when the customer revokes, when cleaning up duplicates, or when the institution is no longer supported. 5. [Import a connection.](/docs/bank-linking/manage-connection-status/importing-connections) Use to migrate existing Plaid or Akoya connections into Meld. **Terminology matters.** "Refresh" pulls new data on a working connection. "Repair" (sometimes called "reconnect") fixes a broken connection by re-authenticating the customer. "Update" adds new products. These are distinct endpoints with distinct billing implications. # Refreshing Connections Source: https://docs.meld.io/docs/bank-linking/manage-connection-status/refreshing-connections A **refresh** pulls the latest balances and transactions for an existing, healthy connection from the underlying service provider. This page is for developers deciding when and how to refresh — and when **not** to (most of the time, background refresh has you covered). ## Before you begin * You have a `connectionId` for a connection that is **not** in `RECONNECT_REQUIRED`, `EXPIRED`, `DELETED`, or `IN_PROGRESS` (those statuses cannot be refreshed — see [Connection Errors and Reasons](/docs/bank-linking/get-connection-data/connection-statuses-and-errors/connection-errors-and-reasons)) * You understand that forced refreshes can incur additional charges and may be rate-limited by the provider * You have webhooks configured so you can observe the resulting aggregation events ## Automatic refreshes Service providers typically refresh connection data (balances and transactions) once or a few times a day, and Meld pulls the latest data at that time. When Meld detects a change, you receive webhooks so you can fetch the latest data. **You do not need to schedule refreshes yourself for routine freshness.** ## Forced refreshes At any time you can call the [refresh endpoint](/api-reference/bank-linking) to force a live aggregation of all accounts on a connection. You can scope the refresh to specific products. Both forced and automatic refreshes produce webhooks. Forced refreshes can incur additional charges and providers rate-limit them. For example, MX supports forced refresh only every 3 hours. Use forced refresh only when the customer just performed an action in your app that requires up-to-the-minute data. ## How to refresh a connection Call the refresh endpoint with the `connectionId` and (optionally) the list of products you want to refresh. For the full request and response schema, refer to the [Bank Linking API reference](/api-reference/bank-linking). After the call returns, listen for the standard aggregation webhooks — `BANK_LINKING_ACCOUNTS_UPDATED`, `BANK_LINKING_TRANSACTIONS_AGGREGATED` — to know when fresh data is available. ## Common errors | HTTP status | Likely cause | Developer action | | :-------------- | :---------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `404 Not Found` | Invalid `connectionId` | Verify the id and the environment (sandbox vs production). | | `409 Conflict` | Connection is in a status that cannot be refreshed (e.g. `RECONNECT_REQUIRED`, `EXPIRED`) | Check the status; if `RECONNECT_REQUIRED`, send the customer through the [repair flow](/docs/bank-linking/manage-connection-status/repairing-connections) instead. | | `429 Too Many` | Provider rate limit hit (e.g. MX 3-hour limit) | Back off and retry after the provider window. Do not loop on this error. | | `5xx` | Transient Meld or provider issue | Retry with exponential backoff; if persistent, inspect the next `BANK_LINKING_CONNECTION_STATUS_CHANGE` webhook to see if the connection moved to `DEGRADED` or `UNDETERMINED`. | # Repairing Connections Source: https://docs.meld.io/docs/bank-linking/manage-connection-status/repairing-connections A **repair** (also called reconnect) fixes a broken connection by sending the customer back into the widget straight to the institution login page, skipping the institution picker. Repair is the only way to bring a `RECONNECT_REQUIRED` connection back to `ACTIVE`. This page is for developers wiring up the reconnect prompt in their UI. ## Before you begin * You have received a `BANK_LINKING_CONNECTION_STATUS_CHANGE` webhook moving the connection to `RECONNECT_REQUIRED` (or a `DEGRADED` reason that requires customer action, or an `ACTIVE` connection with `statusReason` `ACCESS_EXPIRING_SOON` / `NEW_ACCOUNTS_AVAILABLE`) * You know the `connectionId` of the broken connection * You have UI ready to render the widget — repair generates a connect token just like Step 1 ## When connections need repair Connections that were initially successful (`ACTIVE`) can later require reconnection and move to `RECONNECT_REQUIRED`. A connection in `RECONNECT_REQUIRED` will **no longer update**; the latest data available (still queryable via Meld's endpoints) is from before the connection degraded. A connection can also remain `ACTIVE` with a `statusReason` of `NEW_ACCOUNTS_AVAILABLE` — meaning the customer has the option to add additional accounts via the repair flow. Common causes of a broken connection: * The customer changed their bank password after the initial connection. * The institution expired the consent after a set period and requires the customer to re-authenticate. * Multi-factor authentication settings changed at the institution. * Provider-level token or session expired. ## How to repair a connection Call the [repair endpoint](/api-reference/bank-linking) with the `connectionId`. The response includes a fresh `connectToken` and `widgetUrl`. Launch the widget exactly the same way you did in [Step 2: Launch the Widget](/docs/bank-linking/creating-connections/launch-the-widget) — but the customer skips the institution picker and goes straight to the login screen. Once the customer logs in successfully, you will receive a `BANK_LINKING_CONNECTION_STATUS_CHANGE` webhook indicating the connection is `ACTIVE` again, and you can retrieve up-to-date data. For the full request and response schema, refer to the [Bank Linking API reference](/api-reference/bank-linking). ## Sample response ```json theme={null} { "id": "WQ5RitxnrnggqcT8noLoZN", "connectToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyZWRpcmVjdCI6dHJ1ZSwicmVnaW9ucyI6WyJVUyJdLCJpc3MiOiJtZWxkLmlvIiwiY29ubmVjdGlvbiI6IldRNVJpdHhucm5nZ3FjVDhub0xvWk4iLCJleHAiOjE2ODcyMzM4NTgsImlhdCI6MTY4NzIxOTQ1OCwiYWNjb3VudCI6Ilc5a2JrUm1lOVZUMml6NnFBU2ZBZjIiLCJjdXN0b21lciI6IldRNVJpd2lBTkM5ZnhTdmhZRnpXOXIiLCJwcm9kdWN0cyI6WyJCQUxBTkNFUyIsIklERU5USUZJRVJTIiwiT1dORVJTIiwiVFJBTlNBQ1RJT05TIl0sInJvdXRpbmdQcm9maWxlIjoiV0d2Rlh0WGJEb0NlV3N4VXR1aXhQeSJ9.d_D-VTwLx51bT2DwtNwxpH38oaaKdnrLQD0SM-pXFpU", "institutionId": "Ntj3AwgdridJc2rtfeheku", "widgetUrl": "https://institution-connect-sb.meld.io?connectToken=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyZWRpcmVjdCI6dHJ1ZSwicmVnaW9ucyI6WyJVUyJdLCJpc3MiOiJtZWxkLmlvIiwiY29ubmVjdGlvbiI6IldRNVJpdHhucm5nZ3FjVDhub0xvWk4iLCJleHAiOjE2ODcyMzM4NTgsImlhdCI6MTY4NzIxOTQ1OCwiYWNjb3VudCI6Ilc5a2JrUm1lOVZUMml6NnFBU2ZBZjIiLCJjdXN0b21lciI6IldRNVJpd2lBTkM5ZnhTdmhZRnpXOXIiLCJwcm9kdWN0cyI6WyJCQUxBTkNFUyIsIklERU5USUZJRVJTIiwiT1dORVJTIiwiVFJBTlNBQ1RJT05TIl0sInJvdXRpbmdQcm9maWxlIjoiV0d2Rlh0WGJEb0NlV3N4VXR1aXhQeSJ9.d_D-VTwLx51bT2DwtNwxpH38oaaKdnrLQD0SM-pXFpU" } ``` The connect token returned by repair expires after 3 hours, identical to a fresh `/connect/start` token. If the customer doesn't complete the repair in that window, request a new one. ## Common errors | Symptom | Likely cause | Developer action | | :-------------------------------------------------------------- | :------------------------------------------------------ | :----------------------------------------------------------------------------------------------------------- | | `404 Not Found` on the repair endpoint | Invalid `connectionId` | Verify the id and the environment. | | `409 Conflict` — connection cannot be repaired | Connection is in `EXPIRED`, `DELETED`, or `IN_PROGRESS` | These statuses are terminal or active. Start a new connection instead. | | Customer completes repair but status stays `RECONNECT_REQUIRED` | Provider-side failure during re-authentication | Inspect the next `BANK_LINKING_CONNECTION_STATUS_CHANGE` webhook for the new reason; consider routing retry. | # Updating Connection Products Source: https://docs.meld.io/docs/bank-linking/manage-connection-status/updating-connections Products on a connection are first specified in the `/connect/start` request, but you may want to add products **retroactively** — for example, enabling `INVESTMENT_HOLDINGS` on a connection that originally only requested `BALANCES`. This page is for developers expanding the data they retrieve from an existing connection without sending the customer back through the widget. ## Before you begin * You have an existing connection in a non-terminal status (not `EXPIRED` or `DELETED`) * The product you want to add is supported for retroactive addition on the connection's underlying service provider — see the support matrix below * You understand that the new product lands in `optionalProducts` and is aggregated on a best-effort basis ## How updating works Call the [update endpoint](/api-reference/bank-linking) with the connection id and the products to add. Depending on the service provider, there may be limitations on which products can be added retroactively — the institution may not support the new product, or the provider may require additional customer consent. The newly requested products are added to `optionalProducts` on the connection and are aggregated on a best-effort basis. Adding products via the update endpoint does **not** guarantee data will be returned for those products. If the provider requires re-authentication, you may need to send the customer through the [repair flow](/docs/bank-linking/manage-connection-status/repairing-connections) to capture the new product. For the full request and response schema, refer to the [Bank Linking API reference](/api-reference/bank-linking). ## Support matrix Below are the products that can be added post-connect for each service provider (assuming the institution already supports them): | | | | | | | | | :-------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------- | | |
[Identifiers](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-identifiers)
|
[Owners](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-owners)
|
[Balances](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-balances)
|
[Transactions](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-transactions)
|
[Investment Holdings](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-investments)
|
[Investment Transactions](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-investments)
| | [Finicity](/docs/bank-linking/partner-details/finicity) |
X
|
X
|
X
|
X
|
X
|
X
| | [Plaid](/docs/bank-linking/partner-details/plaid) |
X
|
X
|
X
|
X
|
X
|
X
| | Yodlee | | | | | | | | [MX](/docs/bank-linking/partner-details/mx) | | | | only if `INVESTMENT_TRANSACTIONS` is already a product | only if `INVESTMENT_TRANSACTIONS` is already a product | only if `TRANSACTIONS` or `INVESTMENT_HOLDINGS`is already a product | | [Salt Edge Open Banking](/docs/bank-linking/partner-details/salt-edge) | | | | | | | | [Salt Edge Partners](/docs/bank-linking/partner-details/salt-edge-partners) | | | | | | | | Mesh | | | | | | | | Akoya |
X
|
X
|
X
|
X
|
X
|
X
| For MX, `INVESTMENT_TRANSACTIONS` can only be added after the connection is made if `TRANSACTIONS` was specified as a product on the original `/connect/start` call, and vice versa.
# Finicity Balances Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/finicity/finicity-balances-mapping This data comes from Finicity's [/aggregation/v1/customers /\{customerId}/institutionLogins /\{institutionLoginId}/accounts/](https://api-reference.finicity.com/#/rest/api-endpoints/accounts/get-customer-accounts-by-institution-login) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Meld uses the cached balance [GetCustomerAccountsByInstitutionsLogin](https://developer.mastercard.com/open-banking-us/documentation/api-reference/?view=api#GetCustomerAccountsByInstitutionLogin) the vast majority of the time (for daily refreshes). If you force refresh just balances, Meld calls [GetAvailableBalanceLive](https://developer.mastercard.com/open-banking-us/documentation/api-reference/?view=api#GetAvailableBalanceLive) (because it's quicker and cheaper), but if you refresh balances and transactions Meld calls [RefreshCustomerAccountsByInstitutionLogin](https://developer.mastercard.com/open-banking-us/documentation/api-reference/?view=api#RefreshCustomerAccountsByInstitutionLogin). Example values: `currency: "USD"`, `currentAmount: 1543.22`, `availableAmount: 1487.10`, `updatedAt: "2024-08-12T14:33:00Z"`.
Meld Description Finicity Response Field
currency The currency of the account balance currency
currentAmount The current amount in the account balance
availableAmount The available amount in the account Assigned in the following order if non-null: detail.availableBalanceAmount\ detail.availableCashBalance\ balance
updatedAt The last time balances were updated balanceDate
# Finicity Identifiers Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/finicity/finicity-identifiers-mapping This data comes from Finicity's [/aggregation/v1/customers /`{customerId}`/accounts /`{accountId}`/details](https://api-reference.finicity.com/#/rest/api-endpoints/payments/get-account-ach-details) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example values: `ach.accountNumber: "1234567890"`, `ach.routingNumber: "021000021"`. Finicity returns ACH identifiers only. `eft.*`, `international.iban`, `international.bic`, and `bacs.*` fields are not populated. | Meld | Description | Finicity Response Field | | :------------------- | :--------------------------------------------------------------------------------------------------- | :---------------------- | | ach | ACH data object | | |    accountNumber | The account number | realAccountNumber | |    routingNumber | The account routing number. Reference [Routing number](https://en.wikipedia.org/wiki/Routing_number) | routingNumber | | eft | EFT data object | | |    accountNumber | The account number | *None* | |    institutionNumber | The account institution number | *None* | |    branchNumber | The institution branch number | *None* | | international | International data object | | |    iban | The International Bank Account Number (IBAN) for the financial account | *None* | |    bic | The Bank Identifier Code (BIC) for the financial account | *None* | | bacs | The BACS data object | | |    accountNumber | BACS account number | *None* | |    sortCode | BACS sort code | *None* |
# Finicity Investment Holdings Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/finicity/finicity-investment-holdings-mapping This data comes from Finicity's [/aggregation/v1/customers/\{customerId}/institutionLogins/\{institutionLoginId}/accounts](https://developer.mastercard.com/open-banking-us/documentation/api-reference/?view=api#GetCustomerAccountsByInstitutionLogin) endpoint and is returned from Meld's [/bank-linking/investments/holdings](/api-reference/bank-linking/investments/bank-linking-investment-holdings-search) endpoint. Example values: `symbol: "AAPL"`, `quantity: 10`, `currentValue: 1820.50`, `costBasis: 150.25`, `currencyCode: "USD"`, `isin: "US0378331005"`, `cusip: "037833100"`, `type: "STOCK"`. Finicity does not provide `description` or `closePrice` for investment holdings. | Meld Field | Description | Finicity Response Field | | :----------- | :------------------------------------------------------------------------- | :---------------------- | | symbol | The symbol of the security | symbol | | quantity | The number of shares of the security | units | | currentValue | The total current value of the holding | marketValue | | costBasis | The purchase price of the holding, per share | cost\_basis | | currencyCode | The ISO currency code that was used to purchase the holding | securityCurrency | | updatedAt | The last time the details of this holding were updated | currentPriceDate | | description | A description of the holding | **Not Available** | | closePrice | The price of the security at last market close | **Not Available** | | isin | The global ISO number for an individual security. | Securities.isin | | cusip | A shortened version of the isin used for North American securities. | cusipNo | | type | The type of holding (ex: stock, etf). This is normalized across providers. | securityType | # Finicity Investment Transactions Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/finicity/finicity-investment-transactions-mapping This data comes from Finicity's [/investments/transactions/get](https://api-reference.finicity.com/#/rest/models/structures/transactions) endpoint and is returned from Meld's [/bank-linking/investments/transactions](/api-reference/bank-linking/investments/bank-linking-investment-transactions-search) endpoint. Example values: `symbol: "AAPL"`, `quantity: 5`, `amount: -912.50`, `currency: "USD"`, `description: "BUY AAPL"`, `status: "POSTED"`, `transactionDate: "2024-08-09"`, `postedDate: "2024-08-10"`, `type: "BUY"`. Finicity does not provide `cashBalance` for investment transactions.
Meld Field Description Finicity Response Field
symbol The symbol of the security symbol
quantity The number of shares of the security unitQuantity
costBasis The purchase price of the holding, per share costBasis
cashBalance The cash balance of the account after the transaction **Not Available**
accountId The Id of the financial account accountId
amount The total currency involved in the transaction amount
currency The ISO currency code that was used to purchase the holding currencySymbol
description A description of the transaction description
status The status of the transaction status\
  - If returns `Active`, Meld will populate the value as `POSTED`
  - If returns `Pending`, Meld will populate the value as `PENDING`
  - If returns `Shadow`, Meld will populate the value as `PENDING`\ **Note:**\ Some institutions continue to modify or delete investment transactions long after they are first posted to the institution’s data feed. This practice can cause Finicity transactions to appear as duplicates, or to continue to appear in the Finicity data after they have disappeared from the institution’s current website.\ Finicity has added the ability to identify transactions that were found in an earlier aggregation of an account, but are not found in the institution’s current data source. These `SHADOW` transactions are identified in the `transaction record`.
transactionDate The date the transaction was initiated transactionDate
postedDate The date the transaction was finalized postedDate
type The type of investment transaction investmentTransactionType
# Finicity Owners Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/finicity/finicity-owners-mapping This data comes from Finicity's [/aggregation/v1/customers /`{customerId}`/accounts /`{accountId}`/owner](https://api-reference.finicity.com/#/rest/api-endpoints/account-owner/get-account-owner) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example values: `names: ["Jane Doe"]`, `addresses.data.full: "123 Main St, Springfield, IL 62701"`. Finicity returns owner address as a single unparsed string in `ownerAddress`, mapped to `addresses.data.full`. Parsed sub-fields (`street`, `city`, `region`, `postalCode`, `country`) are not provided. Email and phone number data are also not provided.
Meld Field Description Finicity Response Field
addresses The address(es) associated with this owner
   data The address data object
      street The street and residence number *None*
      city The city *None*
      region The region/state *None*
      postalCode The postal or zip code *None*
      country The ISO 3166-1 alpha-2 country code *None*
      full The full unparsed address ownerAddress
   primary Indicates if this is the owner's primary residence Does not provide.
Meld will\ default\ to`Primary.UNKNOWN`
emails The email(s) associated with this owner
   data The email address *None*
   primary Indicates if this is the owner's primary email *None*
names The name(s) of this owner ownerName
phoneNumbers The phone number(s) associated with this owner
   data The phone number *None*
   primary Indicates if this is the owner's primary phone number *None*
# Finicity to Meld Overall Mappings Source: https://docs.meld.io/docs/bank-linking/partner-details/finicity/finicity-to-meld-mappings You can always compare the raw responses from Finicity in the `serviceProviderDetails` section of a financial account. [Financial Account Base Fields](#financial-account-base-fields) [Balances](./finicity-balances-mapping) [Identifiers](./finicity-identifiers-mapping) [Owners](./finicity-owners-mapping) [Transactions](./finicity-transactions-mapping) [Investment Holdings](./finicity-investment-holdings-mapping) [Investment Transactions](./finicity-investment-transactions-mapping) [Normalized Account Types and Subtypes](#normalized-account-types-and-subtypes) ## Financial Account Base Fields This data comes from Finicity's [/aggregation/v1/customers /`{customerId}`/institutionLogins /`{institutionLoginId}`/accounts/](https://api-reference.finicity.com/#/rest/api-endpoints/accounts/get-customer-accounts-by-institution-login) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint.
Meld Description Finicity Response Field
name The account name name
truncated\ AccountNumber The last 4 digits of the account number realAccountNumberLast4 * If null:\_ accountNumberDisplay
status The real-time status of the account status
Possible values:
- `PENDING`
- `ACTIVE`
type The type of the account. Mapped to a Meld standardized type type
subtype The subtype of the account. Mapped to a Meld standardized subtype based on the type * None\_. Meld subtype\ determined from Finicity type.
## Normalized Account Types and Subtypes # Finicity Transactions Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/finicity/finicity-transactions-mapping This data comes from Finicity's [/aggregation/v3/customers /`{customerId}`/accounts /`{accountId}`/transactions](https://api-reference.finicity.com/#/rest/api-endpoints/transactions/get-customer-account-transactions) endpoint and is returned from Meld's [/bank-linking/transactions](/api-reference/bank-linking/transactions/bank-linking-transactions-search) endpoint. To fetch transaction data, Meld calls [GetCustomerAccountTransactions](https://developer.mastercard.com/open-banking-us/documentation/api-reference/?view=api#GetCustomerAccountTransactions) the vast majority of the time, Meld only uses the paid endpoint [LoadHistoricTransactionsForCustomerAccount](https://developer.mastercard.com/open-banking-us/documentation/api-reference/?view=api#LoadHistoricTransactionsForCustomerAccount) for historical transactions (1 time per connection on the initial load). Forced refreshes are done via [RefreshCustomerAccountsByInstitutionLogin](https://developer.mastercard.com/open-banking-us/documentation/api-reference/?view=api#RefreshCustomerAccountsByInstitutionLogin). Finicity's transaction signs (positive and negative) are inverted when mapped to make the behavior consistent with other providers. You can find more information on signage [here](/docs/bank-linking/bank-linking-quickstart/bank-linking-products/account-transactions#data-normalization). Example values: `amount: -42.50`, `currency: "USD"`, `description: "STARBUCKS #1234"`, `status: "POSTED"`, `transactionDate: "2024-08-10"`, `postedDate: "2024-08-11"`. Finicity may continue to surface transactions as `SHADOW` after the institution has removed them from its current data feed. Treat `SHADOW` transactions accordingly when reconciling.
Meld Description Finicity Response Field
amount The amount of the transaction amount
currency The currency used in the transaction currencySymbol * If null:\_ Meld will default\ to `USD`
description The transaction's description description
status The status of the transaction status
  - If returns `Active`, Meld will populate the value as `POSTED`
  - If returns `Pending`, Meld will populate the value as `PENDING`
  - If returns `Shadow`, Meld will populate the value as `SHADOW` **Note:**\ Some institutions continue to modify or delete transactions long after they are first posted to the institution’s data feed. This practice can cause Finicity transactions to appear as duplicates, or to continue to appear in the Finicity data after they have disappeared from the institution’s current website. Finicity has added the ability to identify transactions that were found in an earlier aggregation of an account, but are not found in the institution’s current data source. These `SHADOW` transactions are identified in the `transaction record`.
transactionDate The date and time the transaction was made transactionDate * If null:\_ postedDate
postedDate The date and time the transaction was posted postedDate
# Finicity Partner Details Source: https://docs.meld.io/docs/bank-linking/partner-details/finicity/index Finicity (a Mastercard company) is a US-focused open banking provider. Meld supports Finicity for Balances, Identifiers, Owners, Transactions, Investment Holdings, and Investment Transactions across United States and Canadian institutions. Do not attempt to set up webhooks from Finicity to Meld. Meld passes Finicity the correct webhook URL when initiating the Finicity widget (in any environment). ## Supported Countries Meld supports Finicity institutions in the United States (US) and Canada (CA). ## Configuration Settings If you have your own [Finicity](https://www.finicity.com/) account that you wish to integrate with Meld, you will need to provide the following credentials securely on the Meld Integrations Tab on the dashboard: * *App Key* * *Partner Id* * *Partner Secret* * *Experience Id* ![](https://files.readme.io/b593fe1-image.png) ## Special considerations Finicity refreshes existing connections once a day with the institution. They do not notify when these updates occur, so Meld polls daily to check for updates. Finicity bills extra for aggregating historical transactions. Meld loads them by default on the initial connection. Contact Meld if you would instead like to configure Finicity connections to only load historical transactions once the first forced refresh is triggered for the connection. # Partner Details Source: https://docs.meld.io/docs/bank-linking/partner-details/index Meld routes bank linking traffic through several service providers. Each provider has its own institution coverage, product support, and data shape. The pages in this section document how Meld maps each partner's raw data into Meld's normalized models so you can predict exactly what your application will receive. Use these pages when you need to: * Confirm which products a given provider supports. * Trace a Meld field back to the underlying provider field. * Diagnose differences between connections sourced from different providers. ## Supported partners Largest US institution coverage. Strong support for balances, transactions, identifiers, owners, and investments. Broad US coverage with a user-friendly widget and reliable identifier and balance data. Mastercard Open Banking provider with strong US bank coverage and detailed transaction data. Open Banking provider with strong international coverage, especially across Europe. Meld also supports Salt Edge Partners as a separate routing option for white-label and partner-managed Salt Edge integrations. See the Salt Edge Partners section in the sidebar for those mappings. # MX Partner Details Source: https://docs.meld.io/docs/bank-linking/partner-details/mx/index [MX](https://www.mx.com/) is a bank linking aggregator that Meld integrates with to power account aggregation in the United States and Canada. Through MX, Meld supports the following products: account base data, balances, identifiers (ACH/EFT), owners (identity), transactions, investment holdings, and investment transactions. This page covers supported countries, configuration settings, and MX webhooks. For field-by-field mappings, see the per-product mapping pages linked from [MX to Meld Overall Mappings](/docs/bank-linking/partner-details/mx/mx-to-meld-mappings). MX does not return international identifiers (IBAN/BIC) or BACS identifiers. If your use case requires those formats, see [Plaid](/docs/bank-linking/partner-details/plaid/plaid-identifiers-mapping) or [Salt Edge](/docs/bank-linking/partner-details/salt-edge). ## Supported Countries Meld supports MX institutions in the United States (US) and Canada (CA). ## Configuration Settings If you have your own [MX](https://www.mx.com/) account that you wish to integrate with Meld, you will need to provide the following credentials securely on the Meld Integrations Tab on the dashboard: * *Client ID* * *API Key* * *Webhook Username* * *Webhook Password* ![](https://files.readme.io/af21d64-image.png) ## MX Webhooks MX specifically requires a webhook username and password when setting up webhooks to Meld. However, Meld needs your MX API Key and Client ID before we can generate a url for you to use set up webhooks in the MX dashboard. Therefore when setting up an MX integration, the recommended order of operations is for you to do the following: 1. Add your API Key and Client Id for MX in the dashboard and click Add Integration. 2. Grab your Meld webhook url from the dashboard and use that to go and set up a webhook profile on MX's dashboard. You should set up webhooks for Aggregation, Balance, Connection Status, and History. 3. While still on MX's dashboard, for each webhook, choose the security type basic and set up a webhook username and password. You should use the same username and password for all webhook types within an environment. 4. Come back to Meld's dashboard and add the username and password to the MX integration credentials. ## Special Considerations * In MX production, they require whitelisting the IP addresses that will be hitting their API. This means you will need to request that Meld's IP's be whitelisted on their dashboard. Depending on the Meld environment your MX production account is pointing to, will require different IP's. They are as follows: * Meld Sandbox: `3.12.70.233`, `18.188.161.75`, `52.14.94.218` * Meld Production: `23.20.254.181`, `44.195.151.201`, `44.196.135.166`, `54.158.91.174`, `54.173.48.67`, `184.73.192.20` # MX Balances Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/mx/mx-balances-mapping This data comes from MX's [/users/\{user\_guid}/accounts](https://docs.mx.com/api#core_resources_accounts_list_accounts) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example: MX `currency_code: "USD"`, `balance: 1203.45`, `available_balance: 1150.20` maps to Meld `currency: "USD"`, `currentAmount: 1203.45`, `availableAmount: 1150.20`. | Meld Field | Description | MX Response Field | | :-------------- | :---------------------------------- | :----------------- | | currency | The currency of the account balance | currency\_code | | currentAmount | The current amount in the account | balance | | availableAmount | The available amount in the account | available\_balance | | updatedAt | The last time balances were updated | updated\_at | # MX Identifiers Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/mx/mx-identifiers-mapping This data comes from MX's [/users/`{user_guid}`/members/`{member_guid}`/account\_numbers](https://docs.mx.com/api#verification_mx_widgets_list_account_numbers_by_account) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example: MX `account_number: "1111222233330000"` and `routing_number: "011401533"` map to Meld `ach.accountNumber` and `ach.routingNumber` respectively. MX does not return international identifiers (IBAN, BIC) or BACS (UK) account details. Those fields will be `null` in the Meld response when sourced from MX. | Meld Field | Description | MX Response Field | | :------------------- | :--------------------------------------------------------------------------------------------------- | :------------------ | | ach | ACH data object | | |    accountNumber | The account number | account\_number | |    routingNumber | The account routing number. Reference [Routing number](https://en.wikipedia.org/wiki/Routing_number) | routing\_number | | eft | EFT data object | | |    accountNumber | The account number | account\_number | |    institutionNumber | The account institution number | institution\_number | |    branchNumber | The institution branch number | transit\_number | | international | International data object | | |    iban | The International Bank Account Number (IBAN) for the financial account | *None* | |    bic | The Bank Identifier Code (BIC) for the financial account | *None* | | bacs | The BACS data object | | |    accountNumber | BACS account number | *None* | |    sortCode | BACS sort code | *None* | # MX Investment Holdings Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/mx/mx-investment-holdings-mapping This data comes from MX's [/users/\{user\_guid}/members/\{member\_guid}/holdings](https://docs.mx.com/api-reference/platform-api/reference/list-holdings-by-member) endpoint and is returned from Meld's [/bank-linking/investments/holdings](/api-reference/bank-linking/investments/bank-linking-investment-holdings-search) endpoint. Example: an MX holding with `symbol: "AAPL"`, `shares: 10`, `market_value: 1850.50`, `cost_basis: 180.00`, `currency_code: "USD"` maps to Meld `symbol: "AAPL"`, `quantity: 10`, `currentValue: 1850.50`, `costBasis: 180.00`, `currencyCode: "USD"`. MX does not return holding `description` or `isin`. Those fields will be `null` in the Meld response when sourced from MX. | Meld Field | Description | Mx Response Field | | :----------- | :------------------------------------------------------------------------- | :--------------------- | | symbol | The symbol of the security | symbol | | quantity | The number of shares of the security | shares | | currentValue | The total current value of the holding | market\_value | | costBasis | The purchase price of the holding, per share | cost\_basis | | currencyCode | The ISO currency code that was used to purchase the holding | currency\_code | | updatedAt | The last time the details of this holding were updated | updated\_at | | description | A description of the holding | **Not Available** | | closePrice | The price of the security at last market close | shares / market\_value | | isin | The global ISO number for an individual security. | **Not Available** | | cusip | A shortened version of the isin used for North American securities. | cusip | | type | The type of holding (ex: stock, etf). This is normalized across providers. | holding\_type | # MX Investment Transactions Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/mx/mx-investment-transactions-mapping This data comes from MX's [/users/\{user\_guid} /accounts/\{account\_guid}/transactions](https://docs.mx.com/api-reference/platform-api/reference/list-transactions-by-account) endpoint and is returned from Meld's [/bank-linking/investments/transactions](/api-reference/bank-linking/investments/bank-linking-investment-transactions-search) endpoint. Example: an MX investment transaction with `account.balance: 5200.10`, `account.guid: "ACT-123"`, `amount: 925.25`, `currency_code: "USD"`, `description: "BUY AAPL"`, `transacted_at: "2024-01-15"`, `posted_at: "2024-01-15"`, `category: "buy"` maps to Meld `cashBalance: 5200.10`, `accountId: "ACT-123"`, `amount: 925.25`, `currency: "USD"`, `description: "BUY AAPL"`, `transactionDate: "2024-01-15"`, `postedDate: "2024-01-15"`, `type: "buy"`. MX does not return per-security details on investment transactions. `symbol`, `quantity`, and `costBasis` are not populated when sourced from MX. | Meld Field | Description | Mx Response Field | | :-------------- | :---------------------------------------------------------- | :---------------- | | symbol | The symbol of the security | **Not available** | | quantity | The number of shares of the security | **Not available** | | costBasis | The purchase price of the holding, per share | **Not available** | | cashBalance | The cash balance of the account after the transaction | account.balance | | accountId | The Id of the financial account | account.guid | | amount | The total currency involved in the transaction | amount | | currency | The ISO currency code that was used to purchase the holding | currency\_code | | description | A description of the transaction | description | | status | The status of the transaction | status | | transactionDate | The date the transaction was initiated | transacted\_at | | postedDate | The date the transaction was finalized | posted\_at | | type | The investment transaction's type | category | # MX Owners Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/mx/mx-owners-mapping This data comes from MX's [/users/`{user_guid}` /members/`{member_guid}` /account\_owners](https://docs.mx.com/api#identification_identity_list_account_owners) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example: MX `address: "2982 Frances Vineyard"`, `city: "San Francisco"`, `state: "CA"`, `postal_code: "94114"`, `country: "US"` maps to Meld `addresses.data.street`, `addresses.data.city`, `addresses.data.region`, `addresses.data.postalCode`, `addresses.data.country`. MX does not indicate which contact item is primary. Meld defaults the `primary` field to `UNKNOWN` for addresses, emails, and phone numbers sourced from MX.
Meld Field Description MX Response Field
addresses The address(es) associated with this owner
   data The address data object
      street The street and residence number address
      city The city city
      region The region/state state
      postalCode The postal or zip code postal\_code
      country The ISO 3166-1 alpha-2 country code country
   primary Indicates if this is the owner's primary residence Does not provide.
Meld will\ default\ to `UNKNOWN`
emails The email(s) associated with this owner
   data The email address email
   primary Indicates if this is the owner's primary email Does not provide.
Meld will\ default\ to `UNKNOWN`
names The name(s) of this owner owner\_name
phoneNumbers The phone number(s) associated with this owner
   data The phone number phone
   primary Indicates if this is the owner's primary phone number Does not provide.
Meld will\ default\ to `UNKNOWN`
# MX to Meld Overall Mappings Source: https://docs.meld.io/docs/bank-linking/partner-details/mx/mx-to-meld-mappings You can always compare the raw responses from MX in the `serviceProviderDetails` section of a financial account. * [Financial Account Base Fields](#financial-account-base-fields) * [Balances](/docs/bank-linking/partner-details/mx/mx-balances-mapping) * [Identifiers](/docs/bank-linking/partner-details/mx/mx-identifiers-mapping) * [Owners](/docs/bank-linking/partner-details/mx/mx-owners-mapping) * [Transactions](/docs/bank-linking/partner-details/mx/mx-transactions-mapping) * [Investment Holdings](/docs/bank-linking/partner-details/mx/mx-investment-holdings-mapping) * [Investment Transactions](/docs/bank-linking/partner-details/mx/mx-investment-transactions-mapping) * [Normalized Account Types and Subtypes](#normalized-account-types-and-subtypes) ## Financial Account Base Fields This data comes from MX's [/users/\{user\_guid}/accounts](https://docs.mx.com/api#core_resources_accounts_list_accounts) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint.
Meld Field Description MX Response Field
name The account name name
truncatedAccount\ Number The last 4 digits of the account number accountNumber
status The real-time status of the account `INACTIVE` if MX fields\`\`\` is\_closed = TRUE \`\`\`or `is_hidden = TRUE`; Otherwise `ACTIVE`
type The type of the account. Mapped to a Meld standardized type type
subtype The subtype of the account. Mapped to a Meld standardized subtype subtype
## Normalized Account Types and Subtypes # MX Transactions Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/mx/mx-transactions-mapping This data comes from MX's [/users/\{user\_guid} /accounts/\{account\_guid}/transactions](https://docs.mx.com/api-reference/platform-api/reference/list-transactions-by-account) endpoint and is returned from Meld's [/bank-linking/transactions](/api-reference/bank-linking/transactions/bank-linking-transactions-search) endpoint. Example: an MX transaction with `amount: 89.40`, `currency_code: "USD"`, `original_description: "Uber 063015 SF**POOL**"`, `status: "POSTED"`, `transacted_at: "2024-01-15"`, `posted_at: "2024-01-15"` maps to Meld `amount: 89.40`, `currency: "USD"`, `description: "Uber 063015 SF**POOL**"`, `status: "POSTED"`, `transactionDate: "2024-01-15"`, `postedDate: "2024-01-15"`. | Meld Field | Description | MX Response Field | | :-------------- | :------------------------------------------- | :-------------------- | | amount | The amount of the transaction | amount | | currency | The currency used in the transaction | currency\_code | | description | The transaction's description | original\_description | | status | The status of the transaction | status | | transactionDate | The date and time the transaction was made | transacted\_at | | postedDate | The date and time the transaction was posted | posted\_at | # Plaid Partner Details Source: https://docs.meld.io/docs/bank-linking/partner-details/plaid/index [Plaid](https://plaid.com/) is a bank linking aggregator that Meld integrates with to power account aggregation in the United States and Canada. Through Plaid, Meld supports the following products: account base data, balances, identifiers (ACH/EFT/BACS/international), owners (identity), transactions, investment holdings, and investment transactions. This page covers supported countries, configuration settings, and Plaid-specific behavior. For field-by-field mappings, see the per-product mapping pages linked from [Plaid to Meld Overall Mappings](/docs/bank-linking/partner-details/plaid/plaid-to-meld-mappings). Do not attempt to set up webhooks from Plaid to Meld. Meld will pass Plaid the correct webhook URL when initiating the Plaid widget (in any environment). ## Supported Countries Meld supports Plaid institutions in the United States (US) and Canada (CA). ## Configuration Settings If you have your own [Plaid](https://plaid.com/) account that you wish to integrate with Meld, you will need to provide the following credentials securely on the Meld Integrations Tab on the dashboard: * *Client ID* * *Client Secret* In addition, you will need to complete Plaid's OAuth Registration form in order to enable OAuth institutions. ![](https://files.readme.io/02425af-image.png) ## Product Initialization Unlike the other service providers, Plaid will filter out any accounts belonging to the customer that do not support all of the products requested, thus limiting which accounts the customer can select for aggregation within Plaid's widget flow. In order to normalize account selection behavior across service providers and reduce customer abandonment due to missing accounts, Meld aims to ensure the largest breadth of accounts is made available as possible. To do so, Meld uses a subset of the products supplied in the [/connect/start](/api-reference/bank-linking/connect/bank-linking-connect-create) request that optimizes for account availability when initializing the connection with Plaid. This means that there may be financial accounts belonging to the connection that don't support every product requested, but this can be expected and replicates the behavior exhibited by all of the other service providers. There are several factors Meld takes into consideration when determining which products requested in the `/connect/start` request will be included in the minimal subset provided to Plaid. This determination is made as follows: 1. The products supported by the widest variety of account types take precedence for inclusion in the subset, and those that aren't supported by many account types are typically excluded. For example,`TRANSACTIONS` is supported by nearly all account types, so it is always provided to Plaid if it is one of the requested products in `/connect/start`. On the other hand, `IDENTIFIERS` (account/routing numbers) is only supported by checking and savings accounts, and thus will not be included in the subset (unless it is the only product requested). 2. Products deemed distinctive enough that their inclusion alone within the `/connect/start` request likely implies their importance to the applications that request them will take precedence for inclusion in the subset. For example, the investment type products (`INVESTMENT_TRANSACTIONS` and `INVESTMENT_HOLDINGS`) are specific enough that when requested, they are most likely critical to an application's use-case, and filtering out account types that don't support investments would be desirable. On the other hand, `OWNERS` is typically a supplementary product for most applications and typically won't be included in the subset. It's nice to have if available, but not important enough that it warrants filtering out accounts that don't have this information. **Note:** This minimizing of the product set *does not* imply that the products not included in the subset Meld provides to Plaid will never be aggregated, but rather that the omitted products are not being used to restrict which accounts the customer can select in Plaid's widget. Meld will still attempt to load all the requested products after connection completion, there is just no guarantee that every linked account supports all of them. You can read more about how Plaid handles product initialization on their own documentation [here](https://plaid.com/docs/link/initializing-products/#required-if-supported-products). ## Special considerations * You must complete several Plaid onboarding steps prior to integrating Plaid with Meld. These steps can be completed on the Plaid dashboard or by reaching out to your account manager. The steps are as follows: * Complete the [Security Questionnaire ](https://dashboard.plaid.com/overview/questionnaire-start)form * Complete the OAuth registration. See [here](https://plaid.com/docs/link/oauth/#complete-the-registration-requirements). * Enable Plaid's `transaction_refresh` product if triggering real-time refreshes is desired. Plaid will still auto-refresh transactions daily, but if forcing refreshes is applicable to your use case, then this is an additional product that must be enabled. You can do so by submitting a [product request form](https://dashboard.plaid.com/overview/request-products) on the plaid dashboard * Plaid is the only provider for which the user must select their institution again in their widget even if already selected previously in the Meld picker * If transactions is a specified product, historical transactions will be loaded by default In Plaid sandbox, use the test credentials `user_good` / `pass_good` to link mock institutions. Production data and behavior may differ — particularly for OAuth institutions, which require full registration before use. # Plaid Balances Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/plaid/plaid-balances-mapping This data comes from Plaid's [/accounts/balance/get](https://plaid.com/docs/api/accounts/) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example: a Plaid `accounts.balances.iso_currency_code` of `"USD"` maps to Meld `currency: "USD"`. A Plaid `accounts.balances.current` of `1203.45` maps to Meld `currentAmount: 1203.45`.
Meld Field Description Plaid Response Field
currency The currency of the account balance accounts.balances\ .iso\_currency\_code
currentAmount The current amount in the account accounts.balances.current
availableAmount The available amount in the account accounts.balances.available
updatedAt The last time balances were updated accounts.balances\ .last\_updated\_datetime
# Plaid Identifiers Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/plaid/plaid-identifiers-mapping This data comes from Plaid's [/auth/get](https://plaid.com/docs/api/products/auth/#auth-get-response-numbers-ach-account) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example: Plaid `numbers.ach.account: "1111222233330000"` and `numbers.ach.routing: "011401533"` map to Meld `ach.accountNumber` and `ach.routingNumber` respectively. | Meld Field | Description | Plaid Response Field | | :------------------- | :--------------------------------------------------------------------------------------------------- | :------------------------- | | ach | ACH data object | | |    accountNumber | The account number | numbers.ach.account | |    routingNumber | The account routing number. Reference [Routing number](https://en.wikipedia.org/wiki/Routing_number) | numbers.ach.routing | | eft | EFT data object | | |    accountNumber | The account number | numbers.eft.account | |    institutionNumber | The account institution number | numbers.eft.institution | |    branchNumber | The institution branch number | numbers.eft.branch | | international | International data object | | |    iban | The International Bank Account Number (IBAN) for the financial account | numbers.international.iban | |    bic | The Bank Identifier Code (BIC) for the financial account | numbers.international.bic | | bacs | The BACS data object | | |    accountNumber | BACS account number | numbers.bacs.account | |    sortCode | BACS sort code | numbers.bacs.sort\_code |
# Plaid Investment Holdings Mappings Source: https://docs.meld.io/docs/bank-linking/partner-details/plaid/plaid-investment-holdings-mappings This data comes from Plaid's [/investments/holdings/get](https://plaid.com/docs/api/products/investments/#investmentsholdingsget) endpoint and is returned from Meld's [/bank-linking/investments/holdings](/api-reference/bank-linking/investments/bank-linking-investment-holdings-search) endpoint. Example: a Plaid holding with `Securities.ticker_symbol: "AAPL"`, `Holdings.quantity: 10`, `Holdings.institution_value: 1850.50`, `Securities.iso_currency_code: "USD"` maps to Meld `symbol: "AAPL"`, `quantity: 10`, `currentValue: 1850.50`, `currencyCode: "USD"`. | Meld Field | Description | Plaid Response Field | | :----------- | :------------------------------------------------------------------------- | :---------------------------------- | | symbol | The symbol of the security | Securities.ticker\_symbol | | quantity | The number of shares of the security | Holdings.quantity | | currentValue | The total current value of the holding | Holdings.institution\_value | | costBasis | The purchase price of the holding, per share | Holdings.cost\_basis | | currencyCode | The ISO currency code that was used to purchase the holding | Securities.iso\_currency\_code | | updatedAt | The last time the details of this holding were updated | Holdings.institution\_price\_as\_of | | description | A description of the holding | Securities.name | | closePrice | The price of the security at last market close | Securities.close\_price | | isin | The global ISO number for an individual security. | Securities.isin | | cusip | A shortened version of the isin used for North American securities. | Securities.cusip | | type | The type of holding (ex: stock, etf). This is normalized across providers. | Securities.type | # Plaid Investment Transactions Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/plaid/plaid-investment-transactions-mapping This data comes from Plaid's [/investments/transactions/get](https://plaid.com/docs/api/products/investments/#investmentstransactionsget) endpoint and is returned from Meld's [/bank-linking/investments/transactions](/api-reference/bank-linking/investments/bank-linking-investment-transactions-search) endpoint. Example: a Plaid investment transaction with `Securities.ticker_symbol: "AAPL"`, `Investment_transactions.quantity: 5`, `Investment_transactions.amount: 925.25`, `Investment_transactions.iso_currency_code: "USD"`, `Investment_transactions.date: "2024-01-15"`, `Investment_transactions.type: "buy"` maps to Meld `symbol: "AAPL"`, `quantity: 5`, `amount: 925.25`, `currency: "USD"`, `transactionDate: "2024-01-15"`, `type: "buy"`. | Meld Field | Description | Plaid Response Field | | :-------------- | :---------------------------------------------------------- | :------------------------------------------- | | symbol | The symbol of the security | Securities.ticker\_symbol | | quantity | The number of shares of the security | Investment\_transactions.quantity | | costBasis | The purchase price of the holding, per share | Investment\_transactions.price | | cashBalance | The cash balance of the account after the transaction | Accounts.balances.current | | accountId | The Id of the financial account | Accounts.account\_id | | amount | The total currency involved in the transaction | Investment\_transactions.amount | | currency | The ISO currency code that was used to purchase the holding | Investment\_transactions.iso\_currency\_code | | description | A description of the transaction | Securities.name | | status | The status of the transaction | **Not Available** (always set to POSTED) | | transactionDate | The date the transaction was initiated | Investment\_transactions.date | | postedDate | The date the transaction was finalized | **Not Available** | | type | The type of investment transaction | Investment\_transactions.type | # Plaid Owners Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/plaid/plaid-owners-mapping This data comes from Plaid's [/identity/get](https://plaid.com/docs/api/products/identity/#identity-get-response-accounts-owners-addresses-data-street) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example: Plaid `accounts.owners.addresses.data.street: "2982 Frances Vineyard"` maps to Meld `addresses.data.street`. Plaid `accounts.owners.emails.primary: true` maps to Meld `emails.primary: true`.
Meld Field Description Plaid Response Field
addresses The address(es) associated with this owner
   data The address data object
      street The street and residence number accounts.owners.addresses\ .data.street
      city The city accounts.owners.addresses\ .data.city
      region The region/state accounts.owners.addresses\ .data.region
      postalCode The postal or zip code accounts.owners.addresses\ .data.postal\_code
      country The ISO 3166-1 alpha-2 country code accounts.owners.addresses\ .data.country
   primary Indicates if this is the owner's primary residence accounts.owners.addresses\ .primary
emails The email(s) associated with this owner
   data The email address accounts.owners.emails.data
   primary Indicates if this is the owner's primary email accounts.owners.emails.primary
names The name(s) of this owner accounts.owners.names
phoneNumbers The phone number(s) associated with this owner
   data The phone number accounts.owners\ .phone\_numbers.data
   primary Indicates if this is the owner's primary phone number accounts.owners\ .phone\_numbers.primary
# Plaid to Meld Overall Mappings Source: https://docs.meld.io/docs/bank-linking/partner-details/plaid/plaid-to-meld-mappings You can always compare the raw responses from Plaid in the `serviceProviderDetails` section of a financial account. * [Financial Account Base Fields](#financial-account-base-fields) * [Balances](/docs/bank-linking/partner-details/plaid/plaid-balances-mapping) * [Identifiers](/docs/bank-linking/partner-details/plaid/plaid-identifiers-mapping) * [Owners](/docs/bank-linking/partner-details/plaid/plaid-owners-mapping) * [Transactions](/docs/bank-linking/partner-details/plaid/plaid-transactions-mapping) * [Investment Holdings](/docs/bank-linking/partner-details/plaid/plaid-investment-holdings-mappings) * [Investment Transactions](/docs/bank-linking/partner-details/plaid/plaid-investment-transactions-mapping) * [Normalized Account Types and Subtypes](#normalized-account-types-and-subtypes) ## Financial Account Base Fields This data comes from Plaid's [/accounts/get](https://plaid.com/docs/api/accounts/#accounts-get-response-accounts-name) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. | Meld Field | Description | Plaid Response Field | | :--------------------- | :---------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------- | | name | The account name | accounts.name | | truncatedAccountNumber | The last 4 digits of the account number | accounts.mask | | status | The real-time status of the account | Due to Plaid only returning active bank accounts, it does not have this field.
Meld will populate the status as `ACTIVE` | | type | The type of the account. Mapped to a Meld standardized type | accounts.type | | subtype | The subtype of the account. Mapped to a Meld standardized subtype | accounts.subtype | ## Normalized Account Types and Subtypes # Plaid Transactions Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/plaid/plaid-transactions-mapping This data comes from Plaid's [/transactions/get](https://plaid.com/docs/api/products/transactions/) endpoint and is returned from Meld's [/bank-linking/transactions](/api-reference/bank-linking/transactions/bank-linking-transactions-search) endpoint. Example: a Plaid transaction with `amount: 89.40`, `iso_currency_code: "USD"`, `name: "Uber 063015 SF**POOL**"`, `pending: false`, `date: "2024-01-15"` maps to Meld `amount: 89.40`, `currency: "USD"`, `description: "Uber 063015 SF**POOL**"`, `status: "POSTED"`, `postedDate: "2024-01-15"`.
Meld Field Description Plaid Response Field
amount The amount of the transaction amount
currency The currency used in the transaction iso\_currency\_code unofficial\_currency\_code (if iso\_currency\_code is null)
description The transaction's description name
status The status of the transaction pending
* If returns `True`, Meld will populate the value as `PENDING`
* If returns `False`, Meld will populate the value as `POSTED`
transactionDate The date and time the transaction was made * For transactions with `PENDING` status: `date`
* For transactions with `POSTED` status: `authorizedDate`
* If the authorized date returns `null`, Meld will use the data from `date`
postedDate The date and time the transaction was posted * For transactions with `PENDING` status, it will return `null`
* For transactions with `POSTED` status: `date`
# Salt Edge Partners - Partner Details Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge-partners/index Salt Edge Partners is Salt Edge's PSD2-compliant open banking product, used for EEA and UK regulated bank connections. Meld supports Salt Edge Partners for Balances, Identifiers, Owners, and Transactions. **Salt Edge Partners vs Salt Edge** — Meld integrates with two distinct Salt Edge products: * **Salt Edge Partners** (this page) uses the PSD2-compliant Partners API ([/api/partners/v1](https://docs.saltedge.com/partners/v1/)) and is the correct choice for EEA-regulated open banking connections. * **[Salt Edge](../salt-edge)** uses Salt Edge's global Account Information API ([/api/v5](https://docs.saltedge.com/account_information/v5/)) and covers a wider set of non-EEA countries. The two products require separate credentials and use separate endpoints. If you have both, double-check which set you are configuring. ## Supported Countries Meld supports Salt Edge Partners institutions in Argentina (AR), Australia (AU), Austria (AT), Belarus (BY), Belgium (BE), Brazil (BR), Bulgaria (BG), Croatia (HR), Cyprus (CY), Czechia (CZ), Denmark (DK), Estonia (EE), Finland (FI), France (FR), Germany (DE), Greece (GR), Hungary (HU), Iceland (IS), Ireland (IE), Italy (IT), Latvia (LV), Liechtenstein (LI), Lithuania (LT), Luxembourg (LU), Malta (MT), Netherlands (NL), Norway (NO), Poland (PL), Portugal (PT), Romania (RO), Slovakia (SK), Slovenia (SI), Spain (ES), Sweden (SE), and the United Kingdom (UK). ## Configuration Settings If you have your own [Salt Edge Partners](https://docs.saltedge.com/partners/v1/) account that you wish to integrate with Meld, you will need to provide the following credentials securely on the Meld Integrations Tab on the dashboard: * *App ID* * *Secret* * *Signature Key* ![](https://files.readme.io/8d1d6a1-image.png) If you have both a Salt Edge and Salt Edge Partners account, the keys are different. Verify you are entering the correct credentials for each integration. ## Salt Edge Partners Webhooks Salt Edge Partners needs several webhook urls, which can be seen in the Meld dashboard: ![](https://files.readme.io/472c5e9-image.png) All of the urls will start with `/webhook/saltedgepartners//` but at the end will have a different event, such as `success`, `fail`, `destroy`, `notify`, `interactive`, or `service`. Here is how they would look in the Salt Edge Partners dashboard: ![](https://files.readme.io/276f837-image.png) ## Special considerations Salt Edge Partners does not support forced refreshes — only daily automatic refreshes. If you need on-demand refresh behavior, use a different provider for the same institution where available. Meld loads the last 90 days of transaction data by default for Salt Edge Partners connections (if transactions is a requested product). # Salt Edge Partners Balance Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge-partners/salt-edge-partners-balance-mapping This data comes from Salt Edge Partners' [api/partners/v1/accounts](https://docs.saltedge.com/partners/v1/#accounts-attributes) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example values: `currency: "EUR"`, `currentAmount: 2410.55`, `availableAmount: 2380.00`, `updatedAt: "2024-08-12T14:33:00Z"`. | Meld Field | Description | SaltEdge Partners Response Field | | :-------------- | :---------------------------------- | :------------------------------- | | currency | The currency of the account balance | currency\_code | | currentAmount | The current amount in the account | balance | | availableAmount | The available amount in the account | extra.available\_amount | | updatedAt | The last time balances were updated | updated\_at | # Salt Edge Partners Identifiers Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge-partners/salt-edge-partners-identifiers-mapping This data comes from Salt Edge Partners' [api/partners/v1/accounts](https://docs.saltedge.com/partners/v1/#accounts-extra) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example values: `international.iban: "DE89370400440532013000"`, `international.bic: "COBADEFFXXX"`, `bacs.accountNumber: "31926819"`, `bacs.sortCode: "601613"`. Salt Edge Partners does not provide US ACH (`ach.*`) or Canadian EFT (`eft.*`) identifiers. Identifier coverage varies per institution within the `extra` object. | Meld Field | Description | SaltEdge Partners Response Field | | :------------------- | :--------------------------------------------------------------------------------------------------- | :------------------------------- | | ach | ACH data object | | |    accountNumber | The account number | *None* | |    routingNumber | The account routing number. Reference [Routing number](https://en.wikipedia.org/wiki/Routing_number) | *None* | | eft | EFT data object | | |    accountNumber | The account number | *None* | |    institutionNumber | The account institution number | *None* | |    branchNumber | The institution branch number | *None* | | international | International data object | | |    iban | The International Bank Account Number (IBAN) for the financial account | extra.iban | |    bic | The Bank Identifier Code (BIC) for the financial account | extra.swift | | bacs | The BACS data object | | |    accountNumber | BACS account number | extra.account\_number | |    sortCode | BACS sort code | extra.sort\_code |
# Salt Edge Partners Owners Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge-partners/salt-edge-partners-owners-mapping This data comes from Salt Edge Partners' [api/partners/v1/holder\_info](https://docs.saltedge.com/partners/v1/#holder_info-show) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example values: `names: ["Jane Doe"]`, `addresses.data: { street: "10 Downing St", city: "London", region: "Greater London", postalCode: "SW1A 2AA", country: "GB" }`, `emails.data: ["jane.doe@example.com"]`. Salt Edge Partners does not indicate whether an address, email, or phone number is the owner's primary record. Meld defaults `addresses.primary`, `emails.primary`, and `phoneNumbers.primary` to `UNKNOWN`. | Meld Field | Description | SaltEdge Partners Response Field | | :--------------- | :---------------------------------------------------- | :---------------------------------------------------- | | addresses | The address(es) associated with this owner | addresses | |    data | The address data object | | |       street | The street and residence number | street | |       city | The city | city | |       region | The region/state | state | |       postalCode | The postal or zip code | post\_code | |       country | The ISO 3166-1 alpha-2 country code | country\_code | |    primary | Indicates if this is the owner's primary residence | Does not provide.
Meld will default to `UNKNOWN` | | emails | The email(s) associated with this owner | emails | |    data | The email address | | |    primary | Indicates if this is the owner's primary email | Does not provide.
Meld will default to `UNKNOWN` | | names | The name(s) of this owner | names | | phoneNumbers | The phone number(s) associated with this owner | phone\_numbers | |    data | The phone number | | |    primary | Indicates if this is the owner's primary phone number | Does not provide.
Meld will default to `UNKNOWN` | # Salt Edge Partners to Meld Overall Mappings Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge-partners/salt-edge-partners-to-meld-mappings You can always compare the raw responses from Salt Edge Partners in the `serviceProviderDetails` section of a financial account. [Financial Account Base Fields](#financial-account-base-fields) [Balances](./salt-edge-partners-balance-mapping) [Identifiers](./salt-edge-partners-identifiers-mapping) [Owners](./salt-edge-partners-owners-mapping) [Transactions](./salt-edge-partners-transactions-mapping) [Normalized Account Types and Subtypes](#normalized-account-types-and-subtypes) ## Financial Account Base Fields This data comes from SaltEdge Partners [api/partners/v1/accounts](https://docs.saltedge.com/partners/v1/#accounts-list) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint.
Meld Field Description SaltEdge Partners Response Field
name The account name name
truncatedAccountNumber The last 4 digits of the account number extra.account\_number
If extra.account\_number is null then:
extra.iban
If extra.iban is null then:
extra.bban
If extra.bban is null then:
extra.cards
status The real-time status of the account extra.status
Possible values:
* `active`
* `inactive`
* `unauthorized`
If null: `active`
type The type of the account. Mapped to a Meld standardized type nature
subtype The subtype of the account. Mapped to a Meld standardized subtype None. Meld subtype determined from SaltEdge Partners type.
## Normalized Account Types and Subtypes # Salt Edge Partners Transactions Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge-partners/salt-edge-partners-transactions-mapping This data comes from Salt Edge Partners' [api/partners/v1/transactions](https://docs.saltedge.com/partners/v1/#transactions-list) endpoint and is returned from Meld's [/bank-linking/transactions](/api-reference/bank-linking/transactions/bank-linking-transactions-search) endpoint. Example values: `amount: -22.40`, `currency: "EUR"`, `status: "POSTED"`, `transactionDate: "2024-08-10"`, `postedDate: "2024-08-11"`.
Meld Field Description SaltEdge Partners Response Field
amount The amount of the transaction amount
currency The currency used in the transaction currency\_code
payee The recipient of the transaction description
status The status of the transaction status
transactionDate The date and time the transaction was made made\_on
postedDate The date and time the transaction was posted extra.posting\_date\ *If extra.posting\_date is null then:*\ made\_on
# Salt Edge Partner Details Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge/index Salt Edge is a global open banking provider with broad coverage across the Americas, EMEA, and APAC. Meld supports Salt Edge for Balances, Identifiers, Owners, and Transactions. **Salt Edge vs Salt Edge Partners** — Meld integrates with two distinct Salt Edge products: * **Salt Edge** (this page) uses Salt Edge's global Account Information API ([/api/v5](https://docs.saltedge.com/account_information/v5/)) and covers a wider set of non-EEA countries. * **[Salt Edge Partners](../salt-edge-partners)** uses the PSD2-compliant Partners API ([/api/partners/v1](https://docs.saltedge.com/partners/v1/)) and is the correct choice for EEA-regulated open banking connections. The two products require separate credentials and use separate endpoints. If you have both, double-check which set you are configuring. ## Supported Countries Meld supports Salt Edge institutions in the United States (US), Argentina (AR), Australia (AU), Austria (AT), Belarus (BY), Brazil (BR), Bulgaria (BG), Canada (CA), Chile (CL), Dominican Republic (DR), Ecuador (EC), Egypt (EG), France (FR), Germany (DE), Hong Kong (HK), Hungary (HU), India (IN), Ireland (IE), Israel (IL), Italy (IT), Malaysia (MY), Mexico (MX), Netherlands (NL), New Zealand (NZ), North Macedonia (MK), Pakistan (PJ), Philippines (PH), Poland (PL), Republic of Moldova (MD), Romania (RO), Saudi Arabia (SA), Singapore (SG), Slovakia (SK), South Africa (ZA), Spain (ES), Switzerland (CH), Thailand (TH), Turkey (TR), Ukraine (UA), United Arab Emirates (AE), and United Kingdom (GB). ## Configuration Settings If you have your own [Salt Edge](https://www.saltedge.com/) (Open Banking) account that you wish to integrate with Meld, you will need to provide the following credentials securely on the Meld Integrations Tab on the dashboard: * *App ID* * *Secret* * *Signature Key* ![](https://files.readme.io/555c2a2-image.png) ## Salt Edge Webhooks Salt Edge needs several webhook urls, which can be seen in the Meld dashboard: ![](https://files.readme.io/936ae11-image.png) All of the urls will start with \/webhook/saltedge/`{accountId}`/ but at the end will have a different event, such as `success`, `fail`, `destroy`, `notify`, `interactive`, or `service`. Here is how they would look in the Salt Edge dashboard: ![](https://files.readme.io/b54472a-image.png) ## Special Considerations Meld loads the last 90 days of transaction data by default for Salt Edge connections (if transactions is a requested product). Salt Edge may require a request signature on certain calls. See [Salt Edge signature documentation](https://docs.saltedge.com/general/v5/#signature) for details. # Salt Edge Balances Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge/salt-edge-balances-mapping This data comes from Salt Edge's [/api/v5/accounts](https://docs.saltedge.com/account_information/v5/#accounts-attributes) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example values: `currency: "EUR"`, `currentAmount: 2410.55`, `availableAmount: 2380.00`, `updatedAt: "2024-08-12T14:33:00Z"`. | Meld Field | Description | SaltEdge Response Field | | :-------------- | :---------------------------------- | :---------------------- | | currency | The currency of the account balance | currency\_code | | currentAmount | The current amount in the account | balance | | availableAmount | The available amount in the account | extra.available\_amount | | updatedAt | The last time balances were updated | updated\_at | # Salt Edge Identifiers Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge/salt-edge-identifiers-mapping This data comes from Salt Edge's [/api/v5/accounts](https://docs.saltedge.com/account_information/v5/#accounts-extra) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example values: `international.iban: "GB29NWBK60161331926819"`, `international.bic: "NWBKGB2L"`, `bacs.accountNumber: "31926819"`, `bacs.sortCode: "601613"`. Salt Edge does not provide US ACH (`ach.*`) or Canadian EFT (`eft.*`) identifiers. Identifier coverage varies per institution within the `extra` object. | Meld Field | Description | SaltEdge Response Field | | :------------------- | :--------------------------------------------------------------------------------------------------- | :---------------------- | | ach | ACH data object | | |    accountNumber | The account number | *None* | |    routingNumber | The account routing number. Reference [Routing number](https://en.wikipedia.org/wiki/Routing_number) | *None* | | eft | EFT data object | | |    accountNumber | The account number | *None* | |    institutionNumber | The account institution number | *None* | |    branchNumber | The institution branch number | *None* | | international | International data object | | |    iban | The International Bank Account Number (IBAN) for the financial account | extra.iban | |    bic | The Bank Identifier Code (BIC) for the financial account | extra.swift | | bacs | The BACS data object | | |    accountNumber | BACS account number | extra.account\_number | |    sortCode | BACS sort code | extra.sort\_code |
# Salt Edge Owners Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge/salt-edge-owners-mapping This data comes from Salt Edge's [/api/v5/holder\_info](https://docs.saltedge.com/account_information/v5/#holder_info-show) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint. Example values: `names: ["Jane Doe"]`, `addresses.data: { street: "10 Downing St", city: "London", region: "Greater London", postalCode: "SW1A 2AA", country: "GB" }`, `emails.data: ["jane.doe@example.com"]`. Salt Edge does not indicate whether an address, email, or phone number is the owner's primary record. Meld defaults `addresses.primary`, `emails.primary`, and `phoneNumbers.primary` to `UNKNOWN`. | Meld Field | Description | SaltEdge Response Field | | :--------------- | :---------------------------------------------------- | :---------------------------------------------------- | | addresses | The address(es) associated with this owner | addresses | |    data | The address data object | | |       street | The street and residence number | street | |       city | The city | city | |       region | The region/state | state | |       postalCode | The postal or zip code | post\_code | |       country | The ISO 3166-1 alpha-2 country code | country\_code | |    primary | Indicates if this is the owner's primary residence | Does not provide.
Meld will default to `UNKNOWN` | | emails | The email(s) associated with this owner | emails | |    data | The email address | | |    primary | Indicates if this is the owner's primary email | Does not provide.
Meld will default to `UNKNOWN` | | names | The name(s) of this owner | names | | phoneNumbers | The phone number(s) associated with this owner | phone\_numbers | |    data | The phone number | | |    primary | Indicates if this is the owner's primary phone number | Does not provide.
Meld will default to `UNKNOWN` | # Salt Edge to Meld Overall Mappings Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge/salt-edge-to-meld-mappings You can always compare the raw responses from Salt Edge in the `serviceProviderDetails` section of a financial account. [Financial Account Base Fields](#financial-account-base-fields) [Balances](./salt-edge-balances-mapping) [Identifiers](./salt-edge-identifiers-mapping) [Owners](./salt-edge-owners-mapping) [Transactions](./salt-edge-transactions-mapping) [Normalized Account Types and Subtypes](#normalized-account-types-and-subtypes) ## Financial Account Base Fields This data comes from SaltEdge's [/api/v5/accounts](https://docs.saltedge.com/account_information/v5/#accounts-attributes) endpoint and is returned from Meld's [/bank-linking/accounts](/api-reference/bank-linking/accounts/bank-linking-accounts-search) endpoint.
Meld Field Description SaltEdge Response Field
name The account name name
truncatedAccount\ Number The last 4 digits of the account number extra.account\_number\ If extra.account\_number is null then:\*\ extra.iban\ *If extra.iban is null then:*\ extra.bban\ *If extra.bban is null then:*\ extra.cards
status The real-time status of the account extra.status\
Possible values:
- `active`
- `inactive`
- `unauthorized` * If null:\_ `active`
type The type of the account. Mapped to a Meld standardized type nature
subtype The subtype of the account. Mapped to a Meld standardized subtype * None\_. Meld subtype\ determined from SaltEdge type.
## Normalized Account Types and Subtypes # Salt Edge Transactions Mapping Source: https://docs.meld.io/docs/bank-linking/partner-details/salt-edge/salt-edge-transactions-mapping This data comes from Salt Edge's [/api/v5/transactions](https://docs.saltedge.com/account_information/v5/#transactions-list) endpoint and is returned from Meld's [/bank-linking/transactions](/api-reference/bank-linking/transactions/bank-linking-transactions-search) endpoint. Example values: `amount: -22.40`, `currency: "EUR"`, `status: "POSTED"`, `transactionDate: "2024-08-10"`, `postedDate: "2024-08-11"`.
Meld Field Description SaltEdge Response Field
amount The amount of the transaction amount
currency The currency used in the transaction currency\_code
payee The recipient of the transaction description
status The status of the transaction status
transactionDate The date and time the transaction was made made\_on
postedDate The date and time the transaction was posted extra.posting\_date\ *If extra.posting\_date is null then:*\ made\_on
# Bank Linking Sandbox Testing Guide Source: https://docs.meld.io/docs/bank-linking/testing-and-debugging/bank-linking-sandbox-testing-guide # Testing Information This page lists the test institutions and credentials each bank linking service provider exposes in their sandbox environment. Use it to validate end-to-end connection flows in your integration before pointing at production. Service providers have sandboxes that you can use to make test connections and fetch test data. Each service provider has its own set of test institutions. Some service provider sandboxes have limitations, for example Finicity's does not return investment data. Testing the repair flow is not possible in sandbox. Repair flows can only be exercised against production connections that have entered a broken state. You can also find current test credentials in your Meld dashboard sandbox if a provider updates them. ## Plaid Plaid has a very comprehensive sandbox environment that can be used to test a lot of features. You can check out their [testing guide](https://plaid.com/docs/sandbox/test-credentials/), or see the test credentials below. | Service Provider | Test Institutions | OAuth? | Username | Password | Pin | | :--------------- | :---------------- | :--------------- | ----------- | :---------- | :---------------- | | Plaid | Any | some (ex. Chase) | `user_good` | `pass_good` | `credential_good` | You can select any institution in Plaid's sandbox and use those credentials to complete a connection. Most institutions will connect using OAuth with Plaid's fake bank behind the scenes, called Platypus Bank. Additionally, Plaid makes it very clear when you are in their sandbox by displaying "You are currently in Sandbox mode." at the bottom of their widget, which is not seen in their Production environment. ![](https://files.readme.io/dbf66c9-image.png) In addition to Sandbox and Production, Plaid also has a third environment, Development. Plaid's development environment has several restrictions, such as not supporting the `OWNERS` product (which Plaid calls Identities), and needing to connect with real bank credentials. As such, Meld recommends using Plaid's sandbox for testing, and Plaid's production when going live. ### Plaid Investments Testing Plaid's sandbox supports creating [custom users](https://plaid.com/docs/sandbox/user-custom/) with custom data that Plaid will return when you successfully connect. This can be useful when wanting to test a specific feature with fake data, such as Investments from Plaid. Simply create a user with a custom username and pass that username instead of `user_good` when creating a connection. ## Mesh Mesh's sandbox supports the same list of institutions as in production (with a few exceptions). What differentiates sandbox connections from production connections are the credentials entered. These test credentials are institution-dependent, but are provided as "hints" in the Mesh widget. The username/password combinations are typically either of the following: | Service Provider | Test Institution Type | Username | Password | | :--------------- | :----------------------------- | ------------- | :--------------- | | Mesh | Username/Password integrations | SandboxUser | SandboxPassword | | Mesh | API key integrations | SandboxApiKey | SandboxSecretKey | If there are any exceptions, they will still be displayed as "hints" in the Mesh widget. ### Mesh Investments Testing Mesh's sandbox supports testing investments with the above test credentials. ## Finicity Finicity does not support using the same customer ID for connecting to a test account and to a real bank account, so if you see the error "Existing Finicity customer is not a test customer so cannot connect with test institutions" while trying to make a connection, use a different customer ID. Only Finicity's production environment supports connecting to real bank accounts. Finicity has a set of passwords that return data from various types of financial accounts. You can check out their [testing guide](https://developer.mastercard.com/open-banking-us/documentation/test-the-apis/), or see the test credentials below. | Service Provider | Test Institutions | OAuth | Username | Password | Account Types | | :--------------- | :---------------------------------------------- | :---- | -------- | :----------- | :---------------------------------------------------------------------------------------------- | | Finicity | `FinBank Profiles -A` or `FinBank Profiles - B` | Yes | anything | `profile_02` | Savings, IRA, 401k, Credit Card | | Finicity | `FinBank Profiles -A` or `FinBank Profiles - B` | Yes | anything | `profile_03` | Checking, Personal Investment, 401K, Roth, Savings (Joint Account owners) | | Finicity | `FinBank Profiles -A` or `FinBank Profiles - B` | Yes | anything | `profile_04` | Checking, 403B, 529, Rollover, Mortgage | | Finicity | `FinBank Profiles -A` or `FinBank Profiles - B` | Yes | anything | `profile_05` | Checking, Investment, Stocks, UGMA, UTMA (Joint Account owners) | | Finicity | `FinBank Profiles -A` or `FinBank Profiles - B` | Yes | anything | `profile_06` | Checking, Retirement, KEOGH, 457, Credit Card | | Finicity | `FinBank Profiles -A` or `FinBank Profiles - B` | Yes | anything | `profile_07` | Checking, Stocks, CD, Investment Tax-Deferred, Employee Stock | | Finicity | `FinBank Profiles -A` or `FinBank Profiles - B` | Yes | anything | `profile_08` | Checking, Primary Savings, Money Market, 401A, Line of credit | | Finicity | `FinBank Profiles -A` or `FinBank Profiles - B` | Yes | anything | `profile_09` | Checking, Savings, Checking Failed Report. Errors returned in the report include 102, 103, 185. | Finicity also returns the same set of data via an OAuth flow. Before you can connect this way, you must first register for FinBank OAuth through your Finicity account. | Service Provider | Test Institutions | OAuth | Username | Password | Account Types | | :--------------- | :---------------- | :---- | ------------ | :----------- | :---------------------------------------------------------------------------------------------- | | Finicity | `FinBank OAuth` | No | `profile_02` | `profile_02` | Savings, IRA, 401k, Credit Card | | Finicity | `FinBank OAuth` | No | `profile_03` | `profile_03` | Checking, Personal Investment, 401K, Roth, Savings (Joint Account owners) | | Finicity | `FinBank OAuth` | No | `profile_04` | `profile_04` | Checking, 403B, 529, Rollover, Mortgage | | Finicity | `FinBank OAuth` | No | `profile_05` | `profile_05` | Checking, Investment, Stocks, UGMA, UTMA (Joint Account owners) | | Finicity | `FinBank OAuth` | No | `profile_06` | `profile_06` | Checking, Retirement, KEOGH, 457, Credit Card | | Finicity | `FinBank OAuth` | No | `profile_07` | `profile_07` | Checking, Stocks, CD, Investment Tax-Deferred, Employee Stock | | Finicity | `FinBank OAuth` | No | `profile_08` | `profile_08` | Checking, Primary Savings, Money Market, 401A, Line of credit | | Finicity | `FinBank OAuth` | No | `profile_09` | `profile_09` | Checking, Savings, Checking Failed Report. Errors returned in the report include 102, 103, 185. | ### Finicity Investments Testing Testing Finicity investments is very difficult using Finicity's test accounts due to the lack of test data available. Therefore Meld recommends using real investment accounts to test Finicity investments. Note that Finicity does enforce a third party screen before the bank's login page, as shown below: ![](https://files.readme.io/eb82585-image.png) Sometimes, Finicity's sandbox may connect very quickly and cause an error. In this case, after entering the login username and password, you may see a blank white screen. This only happens when connecting test accounts (not real bank accounts) and can be ignored. ## MX Before testing with MX, note that MX's sandbox supports connecting to both real and test bank accounts, although the number of real connections is very limited. If you wish to connect to a test account, make sure you select a test institution. MX has a very comprehensive sandbox environment that can be used to test a lot of features. You can check out their [testing guide](https://docs.mx.com/testing/guides/testing), or see the test credentials below. | Service Provider | Test Institutions | OAuth? | Username | Password | | :--------------- | :---------------------------------------------------------------------------------------- | :----- | -------- | :------- | | MX | MX Bank | No | `mxuser` | anything | | MX | MX Bank (OAuth) | Yes | `mxuser` | anything | | MX | [MXCU (OAuth)](https://docs.mx.com/resources/test-platform/mxcu/testing-oauth-with-mxcu/) | Yes | `mxuser` | anything | MX does finish their aggregation within their widget, so if you are connecting an account with a lot of transactions, the widget may stay open a little longer than you expect. ### MX Investments Testing MX does have some investment data that can be accessed with the above test credentials, but it's pretty spotty. Meld recommends testing MX Investments with real investment accounts. ## Akoya Akoya has several test accounts in their sandbox environment that can be used to test different data types. You can check out their [testing guide](https://docs.akoya.com/docs/mikomo), or see the test credentials below. The only test institution Akoya supports is `Mikomo`. Meld recommends testing with `mikomo_9` as some Akoya test accounts return data in a different format from what is documented, which can cause errors. | Service Provider | Test Institutions | Username | Password | Account Types | | :--------------- | :---------------- | ------------- | :------------ | :----------------------------------------------------------- | | Akoya | `Mikomo` | `mikomo_1` | `mikomo_1` | Investment (I, TODI) | | Akoya | `Mikomo` | `mikomo_2` | `mikomo_2` | Investment (HSA, I, TODI) | | Akoya | `Mikomo` | `mikomo_3` | `mikomo_3` | Investment (I, IRRL, TIC, IRAB, IRA, TODJ, ROTH, TODI, 401K) | | Akoya | `Mikomo` | `mikomo_5` | `mikomo_5` | Investment (J, HSA, ROTH) | | Akoya | `Mikomo` | `mikomo_6` | `mikomo_6` | Investment (HSA, TODI, IRA, IRRL, NONP, NRMA, 401K) | | Akoya | `Mikomo` | `mikomo_7` | `mikomo_7` | Checking, Commercial Loan, Credit Card, 401k, J | | Akoya | `Mikomo` | `mikomo_9` | `mikomo_9` | Checking | | Akoya | `Mikomo` | `mikomo_10` | `mikomo_10` | Checking, College Savings, Brokerage, CD, Savings | | Akoya | `Mikomo` | `mikomo_11` | `mikomo_11` | Checking | | Akoya | `Mikomo` | `mikomo_2023` | `mikomo_2023` | Checking, College Savings, Brokerage, CD, Savings | ### Akoya Investments Testing Akoya supports testing investments through certain test users, such as mikomo\_2023. However some fields that would appear with real investment data may be missing in the test data. ## Yodlee Yodlee has several test accounts in their sandbox environment that can be used to test different data types. You can check out their [testing guide](https://developer.yodlee.com/), or see the test credentials below. | Service Provider | Test Institutions | Provider Id | Username | Password | Account Types | MFA Response | | :--------------- | :-------------------- | :---------- | --------------------- | :------------ | :---------------------------------------------- | :---------------------- | | Yodlee | `DAG Site` | 16441 | `YodTest.site16441.1` | `site16441.1` | Checking, Savings | N/A | | Yodlee | `DAG Site` | 16441 | `YodTest.site16441.2` | `site16441.2` | Checking, Savings, Credit Card, Loan, Brokerage | N/A | | Yodlee | `DAG Site SecurityQA` | 16486 | `YodTest.site16486.1` | `site16486.1` | Security Question Login | `Texas` and `w3schools` | | Yodlee | `DAG Site Multilevel` | 16442 | `YodTest.site16442.1` | `site16442.1` | OTP Login | `123456` | ### Yodlee Investments Testing Yodlee does have some investment data that can be accessed with the above test credentials, but it's pretty spotty. Meld recommends testing Yodlee Investments with real investment accounts. ## Salt Edge Salt Edge does not have a testing guide, but does have a [guide](https://docs.saltedge.com/account_information/v5/#dynamic_registration) to follow before going live. Besides that, their test credentials can be found below: | Service Provider | Test Institutions | Username | Password | Pin | | :--------------- | :------------------------------------------------------------------------- | ---------- | :------- | :------ | | Salt Edge | Anything starting with `Fake Bank` except for `Fake Bank with Client Keys` | `username` | `secret` | `12345` | ## Salt Edge Partners Salt Edge Partners does not have a testing guide, but does have a [guide](https://docs.saltedge.com/partners/v1/#before_going_live) to follow before going live. Besides that, their test credentials can be found below: | Service Provider | Test Institutions | Username | Password | Pin | | :----------------- | :--------------------------- | ---------- | :------- | :------ | | Salt Edge Partners | `Fake Bank with Client Keys` | `username` | `secret` | `12345` | # Activity Log Source: https://docs.meld.io/docs/bank-linking/testing-and-debugging/debugging-activity-log This feature is in beta. The Activity Log is Meld's searchable audit trail of every request, webhook, and event that flows between your application, Meld, and the underlying service providers. Developers use it to trace what happened on a specific connection, replay webhook delivery, and pinpoint where in the pipeline something went wrong. The Activity Log is currently in beta. Behavior and search parameters may change. One powerful tool to help you debug potential issues or see the paper trail is Meld's [Activity Log](/api-reference/beta/activity-log/activity-log-search). The activity log registers all activities performed by you or performed by Meld on behalf of you and presents them in an easy to trace manner. Some of the major types of activities captured by the activity log are: 1. [Webhooks](#track-webhooks): 1. Webhooks that the provider sends Meld. 2. Webhooks that Meld sends you. 2. [API calls Meld makes to a service provider on your behalf and the response](#raw-service-provider-data). # How to Access the Activity Log You can interact with the Activity Log in two ways: * **Meld dashboard** — log in to your Meld dashboard and navigate to the Activity Log section to browse recent activity in the UI. * **API** — call the [Activity Log Search](/api-reference/beta/activity-log/activity-log-search) and [Activity Log Details](/api-reference/beta/activity-log/activity-log-details-get) endpoints to query programmatically. # How to Use the Activity Log 1. Start by hitting Meld's [Activity Log endpoint](/api-reference/beta/activity-log/activity-log-search) with whatever search parameters you would like. The date params (`start` and `end`) are required but all other params are optional. The activities are ordered chronologically with the most recent at the top, and every activity has a timestamp. You should typically filter using an `actions` and either a `connectionId` or `financialAccountId`. If you don't have either of those, then a `customerId` or `externalCustomerId` can work as well, but those searches will take longer. Examples of `actions` you should filter by are: | Action | Meaning | | :------------------------------------- | :-------------------------------------------------------------------------------------- | | `INBOUND_REQUEST` | An API call you made to Meld | | `OUTBOUND_REQUEST_SERVICE_PROVIDER` | An API call that Meld made to a service provider on your behalf, including the response | | `INBOUND_REQUEST_WEBHOOK` | A webhook sent from the service provider to Meld | | `OUTBOUND_REQUEST_WEBHOOK` | A webhook that Meld sent to you | | `INSTITUTION_CONNECTION_IMPORT_FAILED` | A notification that an import for a particular connection has failed | 2. Once you find a particular activity that you would like more information about, grab the key of the activity and head to Meld's [Activity Log Details endpoint](/api-reference/beta/activity-log/activity-log-details-get). Pass in the key as a query param and you will now see all the details for that particular activity. These details include the request and response headers, the request and response body, the response code, and more. You can also see the raw response Meld received from a particular service provider here. This can help debug if for example data that looks suspicious is due to Meld's transformation or is the exact data that Meld received from the provider. # Use Cases Here are some potential use cases of the activity log: ## Track Webhooks See which webhooks were sent for a particular connection from the provider to Meld as well as from Meld to you. This can help if you might've missed a webhook, or if you want to see the full reason a connection has an issue (which typically comes to Meld via webhook). ## Raw Service Provider Data See the raw data the provider sent Meld to evaluate if an issue with a particular connection is on Meld's side or the provider's side. ## Why a Connection is No Longer Active See why a connection moved from `ACTIVE` to another status. This often uses one of the two examples above to debug the root cause for why this happened. ## Why an Import Failed When you import connections over to Meld, you will receive webhooks for connections that were successfully imported. However, to see which connections failed to be imported, use the activity log and search using the `INSTITUTION_CONNECTION_IMPORT_FAILED` action, which will also include the Meld reason for the import failing. One trick is to find the action for the import failing then look at the details of the action that happened right before it which will likely have the service provider's error, if there is one. The activity log is a very powerful tool. Please make your requests as specific as possible by leveraging the query params, or there may be too much data to return or to be of use. # Testing and Debugging Source: https://docs.meld.io/docs/bank-linking/testing-and-debugging/index Developer tools for testing and debugging bank linking connections Meld provides two primary tools to help you validate connections before going live and diagnose issues after launch. * [Sandbox Testing Guide](/docs/bank-linking/testing-and-debugging/bank-linking-sandbox-testing-guide): test institutions, credentials, and tips for testing with Meld and each bank linking service provider in sandbox. * [Activity Log](/docs/bank-linking/testing-and-debugging/debugging-activity-log): search and review raw connection-level data exchanged between Meld and service providers. # Bank Linking Webhooks Source: https://docs.meld.io/docs/bank-linking/webhook-events-bank-linking This page lists every webhook event Meld sends over the lifetime of a bank linking connection. For each event you'll see when it fires and what your application should do on receipt. For the end-to-end flow and the order webhooks arrive in, see [When is your data available?](/docs/bank-linking/get-connection-data/when-is-your-data-available). Make sure your webhook endpoint is configured before triggering connections in production. See [Setting Up Webhooks](/docs/bank-linking/onboarding/step-5-setting-up-webhooks) for setup, authentication, and retry behavior. ## Attributes All webhook events have the same attributes. | Key | Type | Description | | :---------- | :------------- | :----------------------------------------------------------------------------------- | | `eventType` | String | Type of event | | `eventId` | String | Meld's unique identifier for the event | | `timestamp` | OffsetDateTime | The date and time the event was created | | `accountId` | String | Account id | | `profileId` | String | Id of the webhook profile responsible for this event to be sent | | `version` | Date | API version of the payload | | `payload` | Object | An object containing additional information of the event depending on the event type | # Connection Webhooks See the [API Reference](/api-reference/bank-linking) for the full payload schema of each connection webhook. ### `BANK_LINKING_CONNECTION_COMPLETED` **When it fires:** Sent when the customer completes the connection with their institution either in the initial connect flow or a reconnect flow. This does not imply that financial account data has completed aggregating or is ready to be fetched, just that the customer has completed the widget flow. **What to do:** Record that the customer finished the widget so you can update your UI. Wait for `BANK_LINKING_ACCOUNTS_UPDATED` before attempting to fetch product data. The `BANK_LINKING_CONNECTION_STATUS_CHANGE` webhook will be sent in tandem with this one (with the `newStatus` being `ACTIVE`). However, if a connection goes from a broken state to `ACTIVE` without the user needing to reconnect (i.e. sometimes connections may break due to temporary institution unavailability but are eventually resolved on their own), then only `BANK_LINKING_CONNECTION_STATUS_CHANGE` will be issued containing both the old status and the new `ACTIVE` status. This is because `BANK_LINKING_CONNECTION_COMPLETED` is reserved solely for when the customer completes the widget flow. ```json theme={null} { "eventType": "BANK_LINKING_CONNECTION_COMPLETED", "eventId": "NGoTSGJYpd3cLv1iyHWSw9", "timestamp": "2021-12-09T21:58:29.329186Z", "accountId": "QfdzpX3te1cDaviihXA4D5dA6ghnT3", "profileId": "W9kBgKB7afMxYoYuy3rJuZ", "version": "2021-10-28", "payload": { "requestId": "91d7fb355e319532b178acc8a7ae1a69", "customerId": "Qg56BnxskKokRV8rVo21Af3T4k6QKY", "externalCustomerId": "testCustomer1", "connectionId": "WQ4mBt3BEX2cmhCvSPyfTu", "institutionId": "Ntj3AwgdridJc2rtfeheku", "institutionName": "Chase", "serviceProviderDetails": { "serviceProvider": "PLAID", "serviceProviderConnectionId": "NXrZpLwd1eUQQqymvl9nSkkV8DpeLLUWoXdZx" } } } ``` ### `BANK_LINKING_CONNECTION_STATUS_CHANGE` **When it fires:** Sent whenever a connection changes status. This webhook essentially encompasses all of the other connection status related webhooks and will be sent alongside them. Any service provider errors that occur post-completion will trigger this webhook as well, either due to an attempted aggregation failing or if the service provider notifies Meld via their own webhooks that a connection is in a broken state. This can happen when fetching products following an initial connection, or during daily/forced refreshes of existing connections. **What to do:** This is the webhook to subscribe to for all connection-health tracking. Inspect `newStatus` and `newStatusReason` and route the customer to the [repair flow](/docs/bank-linking/manage-connection-status/repairing-connections) if the connection is now `RECONNECT_REQUIRED` or `CUSTOMER_ACTION_REQUIRED`. The `serviceProviderDetails` will provide additional information on what caused the connection to end up in such state. A list of potential reasons and actions to take for each of them can be found on the [Connection Statuses and Errors](/docs/bank-linking/get-connection-data/connection-statuses-and-errors) page. If a connection requires a reconnect, you can still request information (such as balances, transactions, etc.) about the connection and you will receive the data from the last time the connection was still operational. ```json theme={null} { "eventType": "BANK_LINKING_CONNECTION_STATUS_CHANGE", "eventId": "UhK4iqKP57FeLPgqBi8EUk", "timestamp": "2023-08-22T20:54:02.960285Z", "accountId": "W9ju7atKP5Jaf5RPweGeis", "profileId": "W9kBgKB7afMxYoYuy3rJuZ", "version": "2023-07-31", "payload": { "customerId": "WQ4KqLo3SRSPeTVjxxwo2o", "externalCustomerId": "20230822-67a", "connectionId": "WQ4KqLuBYYTYHi2YATXYdB", "institutionId": "W4AAAFPgUVB85QnQDu5xhU", "oldStatus": "ACTIVE", "oldStatusReason": null, "newStatus": "RECONNECT_REQUIRED", "newStatusReason": "LOGIN_REQUIRED", "activeDuplicateConnectionId": null, "serviceProviderDetails": { "environment": "sandbox", "error": { "error_type": "ITEM_ERROR", "error_code": "ITEM_LOGIN_REQUIRED", "error_message": "the login details of this item have changed (credentials, MFA, or required user action) and a user login is required to update this information. use Link's update mode to restore the item to a good state", "display_message": null, "request_id": null, "causes": null, "status": 400, "documentation_url": null, "suggested_action": null }, "item_id": "NXrZpLwd1eUQQqymvl9nSkkV8DpeLLUWoXdZx", "serviceProvider": "PLAID", "serviceProviderConnectionId": "NXrZpLwd1eUQQqymvl9nSkkV8DpeLLUWoXdZx", "webhook_code": "ERROR", "webhook_type": "ITEM" } } } ``` # Financial Account Webhooks See the [API Reference](/api-reference/bank-linking) for the full payload schema of each account webhook. ### `BANK_LINKING_ACCOUNTS_UPDATING` **When it fires:** Sent once the connect flow is completed and the accounts are in the process of aggregating. At this time, only basic account info is available. Also sent during subsequent daily/forced refreshes when the accounts are in the process of aggregating again. **What to do:** Treat this as a signal that aggregation has started — you can show a loading state but should not yet fetch product data. Wait for `BANK_LINKING_ACCOUNTS_UPDATED` before calling product endpoints. *Financial Account fields for this event type:* | Key | Type | Description | | :--------------------------- | :--------------- | :--------------------------------------------------------------------------------------------------------- | | `financialAccounts` | Array of objects | | |     `id` | String | Unique identifier for the financial account | |     `name` | String | If present, the name of the account as provided by the institution. | |     `truncatedAccountNumber` | String | If present, the last 4 digits of the account number, sometimes referred to as the account mask. | |     `newAccount` | Boolean | Indicates if the account was newly added or is an existing account getting updated (i.e. during a refresh) | ```json theme={null} { "eventType": "BANK_LINKING_ACCOUNTS_UPDATING", "eventId": "38oVQntsutXnfzn6fZ3mxW", "timestamp": "2022-09-06T17:42:49.703620Z", "accountId": "W9ju7atKP5Jaf5RPweGeis", "profileId": "W9kBgKB7afMxYoYuy3rJuZ", "version": "2022-08-29", "payload": { "customerId": "WGti3bpsethEWYJrm2icuo", "externalCustomerId": "20220906-206", "connectionId": "WGu7we7dVTCnVkjJrpzVNm", "institutionId": "29KL1L2iYUV3zUCoSHhwvr", "requestId": "1f97b51f3771328bc07187c8a2c1fe30", "financialAccounts": [ { "id": "WGti3dJFc6A9r1pYcBmE6e", "name": "Plaid Checking", "truncatedAccountNumber": "0000", "newAccount": true }, { "id": "WGti3cCpeiAtWxL6pFQPTg", "name": "Plaid Saving", "truncatedAccountNumber": "1111", "newAccount": true } ], "serviceProviderDetails": { "serviceProvider": "PLAID", "serviceProviderConnectionId": "NXrZpLwd1eUQQqymvl9nSkkV8DpeLLUWoXdZx" } } } ``` ### `BANK_LINKING_ACCOUNTS_UPDATED` **When it fires:** Sent once aggregation has completed for the accounts belonging to the connection. Products are now available to be fetched for the accounts. This webhook should arrive shortly after the `BANK_LINKING_ACCOUNTS_UPDATING` webhook, but sometimes products may take a while to aggregate for certain institutions. Note that if no products have been updated, then the `financialAccounts` object in this webhook will be empty. **What to do:** Fetch the listed products for each financial account. See [Retrieving Connection Data](/docs/bank-linking/get-connection-data/retrieving-connection-data) and the per-account [aggregation overview](/docs/bank-linking/get-connection-data/when-is-your-data-available). *Financial Account fields for this event type:* | Key | Type | Description | | :------------------ | :--------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `financialAccounts` | Array of objects | | |     `id` | String | Unique identifier for the financial account | |     `products` | Array of Strings | The products that were updated for the account. This list will never include `TRANSACTIONS` because their updates are handled separately within the transactions webhooks. Products are considered updated even if nothing changed, and it is solely meant to indicate that the updated products were fetched from the service provider. | ```json theme={null} { "eventType": "BANK_LINKING_ACCOUNTS_UPDATED", "eventId": "Ja2HXauns8LHPYR1NhEbrh", "timestamp": "2022-09-06T17:42:51.853020Z", "accountId": "W9ju7atKP5Jaf5RPweGeis", "profileId": "W9kBgKB7afMxYoYuy3rJuZ", "version": "2022-08-29", "payload": { "customerId": "WGti3bpsethEWYJrm2icuo", "externalCustomerId": "20220906-206", "connectionId": "WGu7we7dVTCnVkjJrpzVNm", "institutionId": "29KL1L2iYUV3zUCoSHhwvr", "requestId": "1f97b51f3771328bc07187c8a2c1fe30", "successfullyAggregatedAt": "2023-08-22T19:48:55Z", "financialAccounts": [ { "id": "WGti3cCpeiAtWxL6pFQPTg", "products": [ "BALANCES", "IDENTIFIERS", "OWNERS" ] }, { "id": "WGti3dJFc6A9r1pYcBmE6e", "products": [ "BALANCES", "IDENTIFIERS", "OWNERS" ] } ], "serviceProviderDetails": { "serviceProvider": "PLAID", "serviceProviderConnectionId": "NXrZpLwd1eUQQqymvl9nSkkV8DpeLLUWoXdZx" } } } ``` ### `BANK_LINKING_ACCOUNTS_REMOVED` **When it fires:** Sent when individual financial accounts belonging to a connection have been deleted. This can occur when the customer revokes access to individual accounts or they close a financial account with their bank altogether. This webhook will always be issued following the `BANK_LINKING_CONNECTION_DELETED` event, as a connection deletion implies all of its financial accounts are deleted as well. **What to do:** Remove the listed financial account IDs from your local data store and stop displaying them to the customer. ```json theme={null} { "eventType": "BANK_LINKING_ACCOUNTS_REMOVED", "eventId": "GUVQ5N9tQpLFALKpRevt6C", "timestamp": "2021-12-09T19:09:23.283931Z", "accountId": "QfdzpX3te1cDaviihXA4D5dA6ghnT3", "profileId": "W9kBgKB7afMxYoYuy3rJuZ", "version": "2021-10-28", "payload": { "customerId": "WGuEzKYdKukkUFxmzVDUvm", "externalCustomerId": "20221031-34", "connectionId": "WGuEzMHgUFDzumu6zVRyw5", "institutionId": "W9kgBBLULSJFSKTwCpb5Gf", "financialAccounts": [ { "id": "WGuEzL3AC3sF155f2ASWpu" }, { "id": "WGuEzHzJzEiDTBhCcniN4D" }, { "id": "WGuEzHoQA6UgG1Nq1qQ1Qy" }, { "id": "WGuEzHswGCZCn2bS1N9Mkh" } ], "serviceProviderDetails": { "serviceProvider": "FINICITY", "serviceProviderConnectionId": "6026862732" } } } ``` # Transaction Webhooks See the [API Reference](/api-reference/bank-linking) for the full payload schema of each transaction webhook. ### `BANK_LINKING_TRANSACTIONS_AGGREGATED` **When it fires:** Captures net transactional effects for each financial account belonging to the connection. Sent after each aggregation in which transactions are added or modified. **What to do:** If you cache transactions locally, re-fetch the affected financial accounts using the `oldestTransactionUpdatedSearchKey` so you pick up new and updated transactions. See the [webhook flow](/docs/bank-linking/get-connection-data/when-is-your-data-available) for more details. ```json theme={null} { "eventType": "BANK_LINKING_TRANSACTIONS_AGGREGATED", "eventId": "FEfSjVLXjM81CkG9ejkWBvu3Kb6ND5", "timestamp": "2022-01-31T22:05:04.068964Z", "accountId": "QfdzpX3te1cDaviihXA4D5dA6ghnT3", "version": "2022-03-09", "payload": { "customerId": "WGv8FTrTroky93FYwAJNXc", "externalCustomerId": "testCustomer", "connectionId": "WGumJ72d21SDCyma5Ax51k", "requestId": "1f97b51f3771328bc07187c8a2c1fe30", "successfullyAggregatedAt": "2023-08-22T19:48:55Z", "isPartial": false, "financialAccounts": [ { "financialAccountId": "WGv8FSGqrwvgVtqY5CARE9", "oldestTransactionUpdatedSearchKey": "3ztRY2PPYjEyzuuAfttKi9omi7UbsLxTbPTDV8MDpPhg9trwrsLZkHCJtb7QPs7cq35DJWSGqnRPP8UxGDmy2o2DRZJnG826oCuePdcnkAaCFyUEqc2QLeteoqJ3xBKetdApYwoFtA39ZpMxyL77LPxZEyUsWa4NWpxAEMQUHV9pSqZcgG", "numTransactionsAdded": 3, "numInvestmentTransactionsAdded": 0, "numTransactionsUpdated": 1, "numInvestmentTransactionsUpdated": 0, "transactionIdsRemoved": [], "investmentTransactionIdsRemoved": [] }, { "financialAccountId": "WGv8FSsWTrgAXBtS2cVPWR", "oldestTransactionUpdatedSearchKey": "3ztRY2PPYjEyzuuAfttKi9omi7UbsLxTbPTDV8MDpPhg9trwrsLZkHCJtb7QPs7cq35DJWSGqnRPP8UxGDmy2o2DRZJnG826oCuePdcnkAaCFyUEqc2QLeteoqJ3xBKetdApYwoFtA39ZpMxyL77LPxZEyUsWa4NWpxAEMQUHV9pSqZcgG", "numTransactionsAdded": 0, "numInvestmentTransactionsAdded": 0, "numTransactionsUpdated": 17, "numInvestmentTransactionsUpdated": 0, "transactionIdsRemoved": [], "investmentTransactionIdsRemoved": [] } ], "serviceProviderDetails": { "environment": "sandbox", "item_id": "zqyK7Abwv7cXJnr5Xk9xS31lMgqdXDuo3ZDxb", "new_transactions": 8, "serviceProvider": "PLAID", "webhook_code": "INITIAL_UPDATE", "webhook_type": "TRANSACTIONS" } } } ``` ### `BANK_LINKING_HISTORICAL_TRANSACTIONS_AGGREGATED` **When it fires:** Captures net historical transaction effects for each financial account belonging to the connection. Sent at most once per connection after historical transactions (transactions older than 30 days) have been aggregated. By default, historical transaction aggregation is initiated after the connection is made. **What to do:** Fetch the older transactions if your application surfaces extended history. Check `historicalAggregationSucceeded` per account — some institutions do not support extended history and will report `false` with an explanatory `historicalAggregationError`. See the [webhook flow](/docs/bank-linking/get-connection-data/when-is-your-data-available) for more details. ```json theme={null} { "eventType": "BANK_LINKING_HISTORICAL_TRANSACTIONS_AGGREGATED", "eventId": "FEfSjVLXjM81CkG9ejkWBvu3Kb6ND5", "timestamp": "2022-01-31T22:05:04.068964Z", "accountId": "QfdzpX3te1cDaviihXA4D5dA6ghnT3", "version": "2022-03-09", "payload": { "customerId": "WGv8FTrTroky93FYwAJNXc", "externalCustomerId": "testCustomer", "connectionId": "WGumJ72d21SDCyma5Ax51k", "requestId": "1f97b51f3771328bc07187c8a2c1fe30", "successfullyAggregatedAt": "2023-08-22T19:48:55Z", "isPartial": false, "financialAccounts": [ { "financialAccountId": "WGv8FSGqrwvgVtqY5CARE9", "oldestTransactionUpdatedSearchKey": "3ztRY2PPYjEyzuuAfttKi9omi7UbsLxTbPTDV8MDpPhg9trwrsLZkHCJtb7QPs7cq35DJWSGqnRPP8UxGDmy2o2DRZJnG826oCuePdcnkAaCFyUEqc2QLeteoqJ3xBKetdApYwoFtA39ZpMxyL77LPxZEyUsWa4NWpxAEMQUHV9pSqZcgG", "numTransactionsAdded": 84, "numInvestmentTransactionsAdded": 0, "numTransactionsUpdated": 1, "numInvestmentTransactionsUpdated": 0, "transactionIdsRemoved": [], "investmentTransactionIdsRemoved": [], "historicalAggregationSucceeded": false, "historicalAggregationError": { "error": { "message": "Member's institution does not support extended transaction history.", "status": "bad_request", "type": "bad_request_error" }, "serviceProvider": "MX" } }, { "financialAccountId": "WGv8FSsWTrgAXBtS2cVPWR", "oldestTransactionUpdatedSearchKey": "3ztRY2PPYjEyzuuAfttKi9omi7UbsLxTbPTDV8MDpPhg9trwrsLZkHCJtb7QPs7cq35DJWSGqnRPP8UxGDmy2o2DRZJnG826oCuePdcnkAaCFyUEqc2QLeteoqJ3xBKetdApYwoFtA39ZpMxyL77LPxZEyUsWa4NWpxAEMQUHV9pSqZcgG", "numTransactionsAdded": 150, "numInvestmentTransactionsAdded": 0, "numTransactionsUpdated": 1, "numInvestmentTransactionsUpdated": 0, "transactionIdsRemoved": [], "investmentTransactionIdsRemoved": [], "historicalAggregationSucceeded": false, "historicalAggregationError": { "error": { "message": "Member's institution does not support extended transaction history.", "status": "bad_request", "type": "bad_request_error" }, "serviceProvider": "MX" } } ], "serviceProviderDetails": { "serviceProvider": "FINICITY", "serviceProviderConnectionId": "6026862732" } } } ``` # Gets details for activities Source: https://docs.meld.io/api-reference/beta/activity-log/activity-log-details-get /openapi/beta-20231219.json get /activity-log/details # Search activity log Source: https://docs.meld.io/api-reference/beta/activity-log/activity-log-search /openapi/beta-20231219.json get /activity-log Retrieve logged activity filtered by various parameters over a date range. See [here](https://docs.meld.io/docs/debugging-activity-log) for information on how to use the activity log and why. # Get crypto sell limits for crypto currencies Source: https://docs.meld.io/api-reference/service-providers/service-provider/service-providers-limits-crypto-currency-sells-get /openapi/serviceproviders-20231219.json get /service-providers/limits/crypto-currency-sells Returns a list of limits (minimums and maximums) in terms of crypto tokens for selling crypto. # Get crypto purchase limits for fiat currencies Source: https://docs.meld.io/api-reference/service-providers/service-provider/service-providers-limits-fiat-currency-purchase-get /openapi/serviceproviders-20231219.json get /service-providers/limits/fiat-currency-purchases Returns a list of limits (minimums and maximums) in terms of fiat currencies tokens for buying crypto. # Get KYC levels for fiat currencies Source: https://docs.meld.io/api-reference/service-providers/service-provider/service-providers-limits-kyc-fiat-level-get /openapi/serviceproviders-20231219.json get /service-providers/limits/kyc-fiat-levels Returns a list of KYC limits in terms of fiat currencies tokens for buying crypto. # Search crypto currencies Source: https://docs.meld.io/api-reference/service-providers/service-provider/service-providers-properties-crypto-currency-search /openapi/serviceproviders-20231219.json get /service-providers/properties/crypto-currencies Returns a list of properties which meet the search criteria. # Search defaults by country Source: https://docs.meld.io/api-reference/service-providers/service-provider/service-providers-properties-defaults-by-country-search /openapi/serviceproviders-20231219.json get /service-providers/properties/defaults/by-country Returns a list of default fiat currencies and payment methods per country. # Search fiat currencies Source: https://docs.meld.io/api-reference/service-providers/service-provider/service-providers-properties-fiat-currency-search /openapi/serviceproviders-20231219.json get /service-providers/properties/fiat-currencies Returns a list of properties which meet the search criteria. # Search payment methods Source: https://docs.meld.io/api-reference/service-providers/service-provider/service-providers-properties-payment-method-search /openapi/serviceproviders-20231219.json get /service-providers/properties/payment-methods Returns a list of properties which meet the search criteria. # Get all webhook event types that can be sent Source: https://docs.meld.io/api-reference/webhooks/profile/webhooks-events-get /openapi/webhooks-20231219.json get /notifications/webhooks/event-types # Create a new webhook profile Source: https://docs.meld.io/api-reference/webhooks/profile/webhooks-profiles-create /openapi/webhooks-20231219.json post /notifications/webhooks # Get an existing webhook profile Source: https://docs.meld.io/api-reference/webhooks/profile/webhooks-profiles-get /openapi/webhooks-20231219.json get /notifications/webhooks/{webhookProfileId} # Search webhook profiles Source: https://docs.meld.io/api-reference/webhooks/profile/webhooks-profiles-search /openapi/webhooks-20231219.json get /notifications/webhooks Returns a list of webhook profiles that match the query parameters. The webhook profiles are sorted by name. # Test an existing webhook profile Source: https://docs.meld.io/api-reference/webhooks/profile/webhooks-profiles-test /openapi/webhooks-20231219.json post /notifications/webhooks/{webhookProfileId}/test Sends a WEBHOOK_TEST event to the URL in the profile # Update an existing webhook profile Source: https://docs.meld.io/api-reference/webhooks/profile/webhooks-profiles-update /openapi/webhooks-20231219.json patch /notifications/webhooks/{webhookProfileId}