Overview

This guide dives into leveraging Dwolla’s Open Banking Services in collaboration with MX to streamline bank account verification within your Dwolla-powered application. Open banking empowers your users to more securely share their financial data with Dwolla and your application, eliminating the need for manual data entry and improving the overall user experience. We’ll walk you through integrating MX Connect Widget to enable seamless bank account verification within your Dwolla integration. Dwolla’s powerful Exchange Sessions API acts as the intermediary, orchestrating a secure connection between your application and MX. This established connection facilitates real-time verification of your user’s bank account details. To gain hands-on experience, we recommend following along with the provided integration-examples sample app, which provides a practical understanding of the integration process for your own application.

Instant Account Verification (IAV)

Instant Account Verification (IAV) is a one-time process that verifies the bank account being added by an end user is open and active. At the end of this guide, you’ll obtain a Funding Source URL, which is a unique identifier that represents a bank account being used for account-to-account (A2A) payments.

Prerequisites

Before starting the integration, make sure you have completed the following prerequisites:
  1. Dwolla Account: Set up a Dwolla production or sandbox account.
  2. Create a Customer: Before creating a funding source, your application will need to create a Customer. A Customer is a user type, either business or personal, that identifies the end-user that is sending/receiving payments. Check out the Dwolla Create a Customer API documentation for guidance.
You do not need an MX account or contract with MX to leverage MX via Dwolla’s Open Banking Services. Dwolla handles the integration with MX for you, simplifying the process.

Sandbox Testing

Testing within the sandbox environment is an essential step before deploying your account verification solution to a production environment. The sandbox acts as a safe, isolated testing ground that mirrors real-world scenarios with test data. This allows you to validate the functionality of your integration without using actual user accounts or financial information. By thoroughly testing in the sandbox, you can identify and correct any potential issues before they impact your live users.

MX Test Credentials

When using MX Connect in the Sandbox environment, you can use the following test credentials to simulate various scenarios. These credentials are provided by MX for sandbox testing purposes.
UsernamePasswordConnection StatusDescription
mxuserAny value not described below✅ CONNECTEDSuccessful aggregation with no MFA.
mxuserchallenge❌ CHALLENGEDIssues an MFA challenge. Answer with correct to simulate a correct MFA response, or use one of the passwords below that simulate a server error. Use anything else to simulate an incorrect answer.
mxuseroptions❌ CHALLENGEDIssues an MFA challenge of the type OPTIONS. Answer with correct to simulate a correct MFA response, or use one of the passwords below that simulate a server error. Use anything else to simulate an incorrect answer.
mxuserimage❌ CHALLENGEDIssues an MFA challenge of type IMAGE. Answer with correct to simulate a correct MFA response, or use one of the passwords below that simulate a server error. Use anything else to simulate an incorrect answer.
mxuserBAD_REQUEST❌ FAILEDExternal server returns a 400 error with the message, “You must fill out the username and password fields.” If using the Connect Widget, this will display the message: “There was a problem validating your credentials with MX Bank. Please try again later.”
mxuserUNAUTHORIZED❌ DENIEDExternal server returns a 401 error with the message, “Invalid credentials.” If using the Connect Widget, this will display the message: “The credentials entered do not match those at MX Bank. Please correct them below to continue.”
mxuserINVALID❌ DENIEDExternal server returns a 401 error with the message, “The login and/or password are invalid.” If using the Connect Widget, this will display the message: “The credentials entered do not match those at MX Bank. Please correct them below to continue.”
mxuserLOCKED❌ LOCKEDExternal server returns a 401 error with the message, “The credentials are valid, but the user is locked.” If using the Connect Widget, this will display the message: “Your account is locked. Please log in to the appropriate website for MX Bank and follow the steps to resolve the issue.”
mxuserDISABLED❌ DENIEDExternal server returns a 401 error with the message, “The credentials are valid, but the user is locked.” This password may also be used as an MFA answer. If using the Connect Widget, this will display the message: “The credentials entered do not match those at MX Bank. Please correct them below to continue.”
mxuserSERVER_ERROR❌ FAILEDExternal server returns a 500 error with the message, “Internal server error.” This password may also be used as an MFA answer. If using the Connect Widget, this will display the message: “There was a problem validating your credentials with MX Bank. Please try again later.”
mxuserUNAVAILABLE❌ FAILEDExternal server returns a 503 error with the message, “Service is Unavailable.” This password may also be used as an MFA answer. If using the Connect Widget, this will display the message: “There was a problem validating your credentials with MX Bank. Please try again later.”
These credentials allow you to test different authentication and connection scenarios with MX in the sandbox environment.

SDK Usage

MX Connect Widget for Seamless Verification

The MX Connect Widget is a pre-built user interface which simplifies the bank account verification process. It is designed to be embedded into your application using one of MX’s SDKs. This guide will focus on using the Web Widget SDK. You have the option to integrate it in a webview for mobile applications.

Dwolla Node SDK for API Interactions

Throughout this guide, we’ll utilize the Dwolla Node SDK to interact with Dwolla’s API endpoints. This SDK simplifies making requests and handling responses for various Dwolla functionalities.
While we are using Dwolla’s Node SDK for the sake of demonstration in this guide, Dwolla also offers additional libraries for other server-side programming languages.

Integration Steps

Step 1 - Initiate Exchange Session with MX

To begin, you will create an exchange session for your Customer in Dwolla using the Exchange Sessions API. This session will specify MX as the exchange partner. The Exchange Partner ID for MX can be found by calling the List Exchange Partners API endpoint.
Exchange Sessions are single-use. Once a user starts the IAV flow initiated by creation of a session, it becomes invalid and cannot be reused.

Example: Initiating an Exchange Session via Dwolla API

import { Client } from "dwolla-v2";

const dwolla = new Client({
  key: "YOUR_KEY",
  secret: "YOUR_SECRET",
  environment: "sandbox", // or 'production'
});

// Retrieve MX's exchange partner href
export async function getExchangePartnerHref(): Promise<string> {
  const response = await dwolla.get("/exchange-partners");
  const partnersList = response.body._embedded["exchange-partners"];
  const mxPartner = partnersList.find(partner => partner.name.toLowerCase() === "mx");
  console.log("MX Exchange partner retrieved successfully:", mxPartner._links.self.href);
  return mxPartner._links.self.href;
}

// Create an exchange session for a Customer
export async function createExchangeSession(customerId: string): Promise<string> {
  const exchangePartnerHref = await getExchangePartnerHref();

  const requestBody = {
    _links: {
      'exchange-partner': {
        href: exchangePartnerHref
      }
    }
  };

  const response = await dwolla.post(`customers/${customerId}/exchange-sessions`, requestBody);
  const location = response.headers.get("location");
  console.log(location);
  return location;
}

// Example usage
const exchangeSessionUrl = await createExchangeSession("your-customer-id");
console.log("Exchange session URL:", exchangeSessionUrl); // => https://api.dwolla.com/exchange-sessions/fcd15e5f-8d13-4570-a9b7-7fb49e55941d

Step 2 - Retrieve Exchange Session and Initialize MX Connect Widget

After creating the exchange session, retrieve the session details from Dwolla to obtain the external-provider-session URL, which you will use to initialize the MX Connect Widget.

Backend: Retrieve MX Connect Widget URL

// Retrieve exchange session by ID
export async function getExchangeSessionUrl(exchangeSessionId: string): Promise<string> {
  const response = await dwolla.get(`/exchange-sessions/${exchangeSessionId}`);
  const externalProviderSessionUrl = response.body._links["external-provider-session"].href;
  console.log(externalProviderSessionUrl);
  return externalProviderSessionUrl;
}

// Example usage:
const mxWidgetUrl = await getExchangeSessionUrl("your-exchange-session-id");
console.log("MX Widget URL:", mxWidgetUrl);

Frontend: Initialize MX Connect Widget

Install the @mxenabled/web-widget-sdk package using your preferred package manager. Using npm:
npm install --save @mxenabled/web-widget-sdk
Using yarn:
yarn add @mxenabled/web-widget-sdk
Initialize MX Connect Widget
import { ConnectWidget } from "@mxenabled/web-widget-sdk";
import { useEffect, useRef } from "react";

export default function ConnectMXPage() {
  const widgetUrl = "your-external-provider-session-url"; // Retrieved from the previous step
  const widgetRef = useRef<HTMLDivElement>(null);
  const widgetInstance = useRef<ConnectWidget | null>(null);

  useEffect(() => {
    if (widgetRef.current && widgetUrl) {
      const options = {
        container: widgetRef.current,
        url: widgetUrl,
        onConnectedPrimaryAction: handleConnectedPrimaryAction
        // Add other event handlers as needed
      };

      // Mount the widget
      widgetInstance.current = new ConnectWidget(options);
    }

    // Unmount the widget when the component unmounts
    return () => {
      if (widgetInstance.current) {
        widgetInstance.current.unmount();
        widgetInstance.current = null;
      }
    };
  }, [widgetUrl]);

  /**
   * Handles the mx/connect/connected/primaryAction event.
   * This event indicates that the connection process is complete.
   */
  const handleConnectedPrimaryAction = () => {
    if (widgetInstance.current) {
      widgetInstance.current.unmount();
      widgetInstance.current = null;
    }
    // Redirect to account selection page
    router.push("/account-selection");
  };

  return (
    <div>
      <div id="connect-widget" ref={widgetRef}></div>
    </div>
  );
}
The MX Connect Widget provides a secure interface where users can select their financial institution and authenticate using their online banking credentials. The onConnectedPrimaryAction callback is triggered when the user successfully completes the connection process.

Step 3 - List Available Exchange Connections

After the user successfully completes the MX Connect flow, use the list available exchange connections endpoint to retrieve a list of available exchange connections associated with the customer. These connections represent the external bank accounts that the user has authorized through MX Connect that can be used to create funding sources.

Backend: Retrieve Available Exchange Connections

// List available exchange connections for a customer
export async function getAvailableExchangeConnections(customerId: string): Promise<any[]> {
  try {
    const response = await dwolla.get(`/customers/${customerId}/available-exchange-connections`);
    return response.body._embedded["available-exchange-connections"];
  } catch (error) {
    console.error("Error retrieving available exchange connections:", error);
    return [];
  }
}

// Example usage
const availableConnections = await getAvailableExchangeConnections("your-customer-id");
console.log("Available connections:", availableConnections);

Frontend: Account Selection Interface

Create an interface for users to select from their available bank accounts:
import React, { useState, useEffect } from "react";
import { RadioGroup, FormControlLabel, Radio } from "@mui/material";

export default function AccountSelectionPage() {
  const [availableAccounts, setAvailableAccounts] = useState([]);
  const [selectedBank, setSelectedBank] = useState("");

  // Fetch available exchange connections when the component mounts
  useEffect(() => {
    async function fetchAvailableAccounts() {
      const customerId = sessionStorage.getItem("customerId");
      if (customerId) {
        try {
          const connections = await getAvailableExchangeConnections(customerId);
          setAvailableAccounts(connections);
        } catch (error) {
          console.error("Error fetching available exchange connections:", error);
        }
      }
    }

    fetchAvailableAccounts();
  }, []);

  const handleSubmit = async (event) => {
    event.preventDefault();
    
    const selectedAccount = availableAccounts.find(
      (account) => account.name === selectedBank
    );
    
    if (selectedAccount) {
      // Navigate to create exchange with the selected account
      router.push(`/create-exchange?token=${selectedAccount.availableConnectionToken}`);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <RadioGroup 
        value={selectedBank} 
        onChange={(e) => setSelectedBank(e.target.value)}
      >
        {availableAccounts.map((account) => (
          <FormControlLabel
            key={account.availableConnectionToken}
            value={account.name}
            control={<Radio />}
            label={account.name}
          />
        ))}
      </RadioGroup>
      <button type="submit">Continue</button>
    </form>
  );
}

Step 4 - Create Exchange

Once the user selects an account, you will make a request to the create an exchange endpoint, providing the availableConnectionToken of the chosen available exchange connection. This creates an “exchange” representing the link between the Dwolla customer and their external bank account.
// Create an exchange for a Dwolla customer using available connection token
export async function createExchange(
  customerId: string,
  availableConnectionToken: string
): Promise<string> {
  const exchangePartnerHref = await getExchangePartnerHref(); // Retrieve MX's exchange partner href

  const requestBody = {
    _links: {
      "exchange-partner": {
        href: exchangePartnerHref,
      },
    },
    mx: {
      availableConnectionToken: availableConnectionToken,
    },
  };

  const response = await dwolla.post(
    `customers/${customerId}/exchanges`,
    requestBody
  );

  return response.headers.get("location"); // URL of the created exchange
}

// Example usage
const exchangeUrl = await createExchange(
  "your-customer-id",
  "available-connection-123"
);

console.log("Exchange URL:", exchangeUrl); // => https://api.dwolla.com/exchanges/fcd15e5f-8d13-4570-a9b7-7fb49e55941d

Step 5 - Create Funding Source

After successfully creating the exchange, create a funding source for the Customer. Before creating a funding source, your application will need to create a Customer. A Customer is a user type, either business or personal, that identifies the end-user that is sending/receiving payments. This involves calling Dwolla’s Create a Funding Source endpoint, where you’ll provide the exchange resource obtained from the previous step. In the following function, once a response is received, it will extract the Location header value, which is the fully-qualified URL specifying the resource location of the Customer’s funding source.
// Creates a funding source for a customer
export async function createFundingSource(
  customerId: string,
  exchangeId: string,
  name: string,
  type: string
): Promise<string> {
  const exchangeUrl = `https://api.dwolla.com/exchanges/${exchangeId}`;

  const requestBody = {
    _links: {
      exchange: {
        href: exchangeUrl,
      },
    },
    bankAccountType: type,
    name: name,
  };

  const response = await dwolla.post(
    `customers/${customerId}/funding-sources`,
    requestBody
  );

  const location = response.headers.get("location");
  return location; // URL of the created funding source
}

// Example usage:
const fundingSourceUrl = await createFundingSource(
  "your-customer-id",
  "your-exchange-id",
  "Jane Doe's Checking",
  "checking"
);

console.log("Funding Source URL:", fundingSourceUrl); // => https://api.dwolla.com/funding-sources/f41ab99c-7748-4f84-a3ed-3f669c002f4f

Verification Complete

Dwolla responds with a 201 code and triggers the customer_funding_source_added and customer_funding_source_verified webhooks indicating successful funding source creation and verification.

Resources