Facilitate account-to-account transfers

If you’re looking for a way to facilitate a payment between two accounts on your platform (what we describe as an account-to-account transfer), this guide will cover the flow for your use case. Some examples of account-to-account transfers include:

  • A marketplace enabling businesses to pay their vendors
  • A service platform allowing customers to pay their service providers
  • A SaaS company helping contractors to pay subcontractors
  • An HR tool with a feature where companies can pay their employees

Key players

For the purposes of this guide, we’ll assume you are a developer building a fitness marketplace with the following key players:

  • Fitness platform that connects gyms with fitness instructors (account facilitating the transfer)
  • Gym (account sending money, otherwise known as the source account)
  • Fitness instructor (account receiving money, otherwise known as the destination account)

In this use case, the fitness platform is not directly involved in the transaction. Their role is to help the gym pay a fitness instructor they’ve hired and booked through the platform.

1. Get your access token

If you are using a server-side integration, you can skip this step.

Assuming you’ve already completed a basic setup of your own account, you’ll need to generate an access token. You will include a new access token as the Moov Drop onboarding.token whenever you onboard a customer.

1
2
3
4
curl -X POST "https://api.moov.io/oauth2/token" \
  -u "PUBLIC_KEY:PRIVATE_KEY" \
  --data-urlencode "grant_type=client_credentials" \
  --data-urlencode  "scope=/accounts.write" \
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import { Moov, SCOPES } from '@moovio/node';

const moov = new Moov({
  accountID: "YOUR_MOOV_ACCOUNT_ID",
  publicKey: "PUBLIC_KEY",
  secretKey: "PRIVATE_KEY",
  domain: "YOUR_DOMAIN"
});

const scopes = [SCOPES.ACCOUNTS_CREATE];
try {
  const {token} = await moov.generateToken(scopes);
  // Do something with token
} catch(err) {
  // Handle any errors
}

2. Set up the source account

With Moov, all transfers represent the movement of money from a source to a destination. You will start by creating the source account and linking a bank account as the funding source. If you are looking to link a card rather than a bank account, see our guide on accepting card payments.

Create the source account

Start by creating a Moov account for the gym. Then, request the send-funds capability for the gym’s account. The capabilities GET endpoint will specify what information Moov needs about the gym as a company before we enable the requested capability. Read our capabilities guide for more information.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
curl -X POST "https://api.moov.io/accounts" \
  -H 'Authorization: Bearer {token}' \
  --data-raw '{
    "accountType": "business",
    "profile": {
      "business": {
        "legalBusinessName": "Whole Body Fitness LLC",
        "businessType": "llc"
      }
    },
    "capabilities": ["send-funds"],
    "foreignId": "your-correlation-id"
  }'\
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
const moov = Moov(token);

const accountPayload = {
  accountType: "business",
  profile: {
    business: {
      legalBusinessName: "Whole Body Fitness LLC",
      businessType: "llc"
    }
  },
  capabilities: ["send-funds"],
  foreignId: "your-correlation-id"
};

const account = await moov.accounts.create({accountPayload});
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
const moov = new Moov(credentialsObject);

const accountPayload = {
  accountType: "business",
  profile: {
    business: {
      legalBusinessName: "Whole Body Fitness LLC",
      businessType: "llc"
    }
  },
  capabilities: ["send-funds"],
  foreignId: "your-correlation-id"
};

const account = await moov.accounts.create(accountPayload);

Submit required verification information

Since the source account is a business requesting the send-funds capability, you will be required to submit certain pieces of information to Moov before we enable the capability. To get a list of everything you need to submit about the gym as a company, use the the capabilities GET endpoint. Read our capabilities guide for more information.

1
2
curl -X GET "https://api.moov.io/accounts/{accountID}/capabilities/" \
  -H "Authorization: Bearer {token}" \
1
2
3
4
5
const moov = new Moov(credentialsObject);

const accountID = "accountID";

moov.accounts.capabilities.list(accountID);
1
2
3
4
5
const moov = Moov(token);

const accountID = "accountID";

moov.accounts.capabilities.list(accountID);

Add business representatives

If you haven’t already added business representatives for the source account, you will need to do so as a required verification step. You can use the add representatives POST endpoint. Read our guide on business representatives for more information.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
curl -X POST "https://api.moov.io/accounts/{accountID}/representatives" \
  -H "Authorization: Bearer {token}" \
  --data-raw '{
    "name": {
      "firstName": "Amanda",
      "lastName": "Yang"
    },
    "address": {
      "addressLine1": "12 Main Street",
      "city": "Cabot Cove",
      "stateOrProvince": "ME",
      "postalCode": "04103",
      "country": "US"
    },
    "birthDate": {
      "day": 10,
      "month": 11,
      "year": 1985
    },
    "email": "amanda@classbooker.dev",
    "governmentID": {
      "ssn": {
        "full":"111111111",
        "lastFour":"1111"
      }
    },
    "responsibilities": {
      "isController": false,
      "isOwner": true,
      "ownershipPercentage": 38,
      "jobTitle": "CEO"
    }
  }'
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
const moov = new Moov(credentialsObject);

const accountID = "accountID";
const representativePayload = {
  name: {
    firstName: "Amanda",
    lastName: "Yang"
  },
  address: {
    addressLine1: "12 Main Street",
    city: "Cabot Cove",
    stateOrProvince: "ME",
    postalCode: "04103",
    country: "US"
  },
  birthDate: {
    day: 10,
    month: 11,
    year: 1985
  },
  email: "amanda@classbooker.dev",
  governmentID: {
    ssn: {
      full: "111111111",
      lastFour: "1111"
    }
  },
  responsibilities: {
    isController: false,
    isOwner: true,
    ownershipPercentage: 38,
    jobTitle: "CEO"
  }
};

const account = await moov.representatives.create(accountID, representativePayload);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
const moov = Moov(token);

const accountID = "accountID";
const representativePayload = {
  name: {
    firstName: "Amanda",
    lastName: "Yang"
  },
  address: {
    addressLine1: "12 Main Street",
    city: "Cabot Cove",
    stateOrProvince: "ME",
    postalCode: "04103",
    country: "US"
  },
  birthDate: {
    day: 10,
    month: 11,
    year: 1985
  },
  email: "amanda@classbooker.dev",
  governmentID: {
    ssn: {
      full: "111111111",
      lastFour: "1111"
    }
  },
  responsibilities: {
    isController: false,
    isOwner: true,
    ownershipPercentage: 38,
    jobTitle: "CEO"
  }
};

const account = await moov.representatives.create(accountID, representativePayload);

In addition, you’ll need to add the gym’s bank account as a funding source for paying the instructors.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
curl -X POST "https://api.moov.io/accounts/{account_id}/bank-accounts" \
  -H "Authorization: Bearer {token}" \
  --data-raw '{
    "account": {
      "holderName": "Jules Jackson",
      "holderType": "individual",
      "accountNumber": "0004321567000",
      "bankAccountType": "checking",
      "routingNumber": "123456789"
    }
  }'\
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const moov = Moov(token);

const accountID = "accountID";
const bankAccountPayload = {
  holderName: "Jules Jackson",
  holderType: "individual",
  accountNumber: "0004321567000",
  bankAccountType: "checking",
  routingNumber: "123456789"
};

moov.accounts.bankAccounts.link({accountID, bankAccountPayload});
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const moov = new Moov(credentialsObject);

const accountID = "accountID";
const bankAccountPayload = {
  holderName: "Jules Jackson",
  holderType: "individual",
  accountNumber: "0004321567000",
  bankAccountType: "checking",
  routingNumber: "123456789"
};

const bankAccount = await moov.bankaccounts.link(accountID, bankAccountPayload);

Complete micro-deposit verification or IAV

The gym must complete the micro-deposit verification process before you can use their account to send funds.

Initiate micro-deposits

1
2
curl -X POST "https://api.moov.io/accounts/{accountID}/bank-accounts/{bankAccountID}/micro-deposits" \
  -H "Authorization: Bearer {token}" \
1
2
3
4
5
6
const moov = new Moov(credentialsObject);

const accountID = "accountID";
const bankAccountID = "bankAccountID";

const response = await moov.bankAccounts.initMicroDeposits(accountID, bankAccountID);

Confirm micro-deposits

1
2
3
4
5
6
7
8
curl -X POST "https://api.moov.io/accounts/{accountID}/bank-accounts/{bankAccountID}/micro-deposits" \
  -H "Authorization: Bearer {token}" \
  --data-raw '{
    "amounts": [
      18,
      21
    ]
  }'\
1
2
3
4
5
6
const moov = new Moov(credentialsObject);

const accountID = "accountID";
const bankAccountID = "bankAccountID";

const response = await moov.bankAccounts.initMicroDeposits(accountID, bankAccountID);
1
2
3
4
5
6
7
8
const moov = Moov(token);

const accountID = "accountID";
const bankAccountID = "bankAccountID";
const microDeposits = [18, 21]


moov.accounts.bankAccounts.completeMicroDepositVerification({accountID, bankAccountID, microDeposits});

Instant account verification

Alternatively, you can use instant account verification (IAV) with Plaid Link or MX if you have an existing relationship with Plaid or MX.

3. Set up the destination account

Next, you will set up the Moov account for the fitness instructor. This will be the destination account, since the fitness instructor is the one who will be receiving the funds.

Create the destination account

Start by creating a destination account as shown below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
curl -X POST "https://api.moov.io/accounts" \
  -H "Authorization: Bearer {token}" \
  --data-raw '{
    "accountType": "individual",
    "profile": {
      "individual": {
        "email": "julesjacksonyoga@moov.io",
        "name": {
          "firstName": "Jules",
          "lastName": "Jackson"
        }
      }
    },
    "foreignID": "your-correlation-id"
  }'\
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
const moov = new Moov(credentialsObject);

const accountPayload = {  
  accountType: "individual",
  profile: {
    individual: {
      email: "julesjacksonyoga@moov.io",
      name: {
        firstName: "Jules",
        lastName: "Jackson"
      }
    },
    foreignID: "your-correlation-id"
  }
};

const account = await moov.accounts.create(accountPayload);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
const moov = Moov(token);

const accountPayload = {  
  accountType: "individual",
  profile: {
    individual: {
      email: "julesjacksonyoga@moov.io",
      name: {
        firstName: "Jules",
        lastName: "Jackson"
      }
    },
    foreignID: "your-correlation-id"
  }
};

const account = await moov.accounts.create({accountPayload});

Add a bank account for the fitness instructor. In our scenario, the fitness instructor’s bank account will only be used to receive funds, so micro-deposit verification is not required.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
curl -X POST "https://api.moov.io/accounts/{account_id}/bank-accounts" \
  -H "Authorization: Bearer {token}" \
  --data-raw '{
    "account": {
      "holderName": "Jules Jackson",
      "holderType": "individual",
      "accountNumber": "0004321567000",
      "bankAccountType": "checking",
      "routingNumber": "123456789"
    }
  }'\
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const moov = new Moov(credentialsObject);

const accountID = "accountID";
const bankAccountPayload = {
  holderName: "Jules Jackson",
  holderType: "individual",
  accountNumber: "0004321567000",
  bankAccountType: "checking",
  routingNumber: "123456789"
};

const bankAccount = await moov.bankaccounts.link(accountID, bankAccountPayload);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const moov = Moov(token);

const accountID = "accountID";
const bankAccountPayload = {
  holderName: "Jules Jackson",
  holderType: "individual",
  accountNumber: "0004321567000",
  bankAccountType: "checking",
  routingNumber: "123456789"
};

moov.accounts.bankAccounts.link({accountID, bankAccountPayload});

4. Initiate the payment

Now that both the gym and fitness instructor have been set up with the right capabilities and bank accounts, you’re ready to create the payment.

Get payment methods

First, get a list of the available payment methods for both accounts through the retrieve transfer options POST endpoint. The response includes all the payment methods the gym and fitness instructor can use based on their linked bank accounts and the amount of the transfer. If the gym is funding the payment with a linked bank account, you’ll always use a payment method type ach-debit-fund. The gym could also pay from their Moov wallet. Select the payment method you’d like to use for the transfer.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
curl -X POST "https://api.moov.io/transfer-options" \
  -H "Authorization: Bearer {token}" \
  --data-raw '{
    "amount": {
      "value": 100,
      "currency": "USD"
    }
    "destination": {
      "accountID": "UUID"
    },
    "source": {
      "accountID": "UUID"
    }
  }'\
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
const moov = new Moov(credentialsObject);

const sourceAccountID = "sourceAccountID";
const destinationAccountID = "destinationAccountID";
const transferOptions = {
  amount: {
    value: 100, 
    currency: "USD"
  },
  destination: {
    accountID: destinationAccountID
  },
  source: {
    accountID: sourceAccountID
  }
}

const options = await moov.transfers.getTransferOptions(transferOptions);

Create the transfer

You can now initiate a transfer between the two accounts, using the payment method IDs you got earlier in the transfer options request.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
curl -X POST "https://api.moov.io/transfers" \
  -H "Authorization: Bearer {token}" \
  -H "X-Idempotency-Key: UUID" \
  -H "X-Wait-For: rail-response" \
  --data-raw '{
    "amount": {
      "value": 100,
      "currency": "USD"
    },
    "destination": {
      "paymentMethodID": "UUID"
    },
    "source": {
      "paymentMethodID": "UUID"
    }
  }'\
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
const moov = new Moov(credentialsObject);

const transfer = await moov.transfers.create({
  amount: {
    value: 100,
    currency: "USD"
  },
  destination: {
    paymentMethodID: "destination-payment-method-id"
  },
  source: {
    paymentMethodID: "source-payment-method-id"
  }
});

What’s next

If you’re interested in other use cases, you can dive into our guides:

Summary Beta