Employee Bank Account Verification With Plaid

Creates a bank account for an employee and verifies it through Plaid. Use this flow when the employee connects their bank through Plaid Link instead of Worklio verifying the account another way.

The flow has four steps:

  1. Create the employee bank account with verificationType: 3 (Plaid).
  2. Request a Plaid link token from Worklio.
  3. Launch Plaid Link on the client and get a public token.
  4. Submit the public token and the selected account's details to Worklio to link the account.

All endpoints on this page require a bearer access token (see How to Get API Access). The path parameters are:

ParameterTypeDescription
CLIENT_IDintegerThe company's id, returned when you create the company.
EMPLOYEE_IDintegerThe employee's id, returned when you create the employee.
BANK_ACCOUNT_IDintegerThe employee bank account's id, returned in Step 1.

Step 1: Create the employee bank account

Creates the bank account record with Plaid as its verification method.

POST https://api.worklio.com/wep/companies/{CLIENT_ID}/employees/{EMPLOYEE_ID}/bankaccounts

Fields

FieldTypeDescription
accountHolderNamestringThe name of the account holder.
accountNumberstringThe bank account number.
routingNumberstringThe bank's routing number.
accountTypeintegerThe account type. The example uses 1.
verificationTypeintegerHow the account is verified. Set to 3 for Plaid.

Example request

require("dotenv").config();

const TOKEN = process.env.API_KEY;
const CLIENT_ID = process.env.CLIENT_ID;
const EMPLOYEE_ID = process.env.EMPLOYEE_ID;
const url = `https://api.worklio.com/wep/companies/${CLIENT_ID}/employees/${EMPLOYEE_ID}/bankaccounts`;

const payload = {
  accountHolderName: "Employee Plaid Verification",
  accountNumber: "6666660000",
  routingNumber: "011401533",
  accountType: 1,
  verificationType: 3
};

async function createEmployeeAccount() {
  const response = await fetch(url, {
    method: "POST",
    headers: {
      accept: "application/json",
      "api-version": "2.0",
      authorization: `Bearer ${TOKEN}`,
      "content-type": "application/json",
      "x-api-version": "2.0"
    },
    body: JSON.stringify(payload)
  });

  console.log(response.status);
  console.log(JSON.stringify(await response.json(), null, 2));
}

createEmployeeAccount();
curl -s -X POST "https://api.worklio.com/wep/companies/$CLIENT_ID/employees/$EMPLOYEE_ID/bankaccounts" \
  -H "accept: application/json" \
  -H "api-version: 2.0" \
  -H "authorization: Bearer $API_KEY" \
  -H "content-type: application/json" \
  -H "x-api-version: 2.0" \
  -d '{
    "accountHolderName": "Employee Plaid Verification",
    "accountNumber": "6666660000",
    "routingNumber": "011401533",
    "accountType": 1,
    "verificationType": 3
  }' | jq .

Example response

{
  "id": 2,
  "accountNumber": "6666660000",
  "routingNumber": "011401533",
  "accountHolderName": "Employee Plaid Verification",
  "accountType": 1,
  "verificationType": 3,
  "isVerified": false
}

Save the bank account id. Steps 2 and 4 use it as BANK_ACCOUNT_ID.


Step 2: Request a Plaid link token

Returns the link token that Plaid Link needs to start.

GET https://api.worklio.com/wep/companies/{CLIENT_ID}/employees/{EMPLOYEE_ID}/plaid/{BANK_ACCOUNT_ID}/token

The request has no body.

Example request

require("dotenv").config();

const TOKEN = process.env.API_KEY;
const CLIENT_ID = process.env.CLIENT_ID;
const EMPLOYEE_ID = process.env.EMPLOYEE_ID;
const BANK_ACCOUNT_ID = process.env.BANK_ACCOUNT_ID;
const url = `https://api.worklio.com/wep/companies/${CLIENT_ID}/employees/${EMPLOYEE_ID}/plaid/${BANK_ACCOUNT_ID}/token`;

async function getPlaidLinkToken() {
  const response = await fetch(url, {
    method: "GET",
    headers: {
      accept: "application/json",
      "api-version": "2.0",
      authorization: `Bearer ${TOKEN}`,
      "x-api-version": "2.0"
    }
  });

  console.log(response.status);
  console.log(JSON.stringify(await response.json(), null, 2));
}

getPlaidLinkToken();
curl -s -X GET "https://api.worklio.com/wep/companies/$CLIENT_ID/employees/$EMPLOYEE_ID/plaid/$BANK_ACCOUNT_ID/token" \
  -H "accept: application/json" \
  -H "api-version: 2.0" \
  -H "authorization: Bearer $API_KEY" \
  -H "x-api-version: 2.0" | jq .

Example response

{
  "linkToken": "link-sandbox-f5166a5d-9c28-4f66-a53d-3f7b12460b6e",
  "expiration": "2026-09-30T18:06:42Z"
}
FieldTypeDescription
linkTokenstringThe token you pass to Plaid Link in Step 3.
expirationstring (ISO 8601, UTC)When linkToken expires. Plaid Link can't start with an expired token. Request a new one.

Step 3: Launch Plaid Link and get a public token

This step runs on the client, using Plaid's Link SDK. No Worklio endpoint is involved. The code below is just an example.

Initialize Plaid Link with the linkToken from Step 2 and open it when the employee chooses to connect their bank:

<!DOCTYPE html>
<html>
<head>
  <title>Plaid Link Test</title>
  <script src="https://cdn.plaid.com/link/v2/stable/link-initialize.js"></script>
</head>
<body>
  <button id="link-button">Open Plaid Link</button>
  <pre id="output"></pre>

  <script>
    // Paste the link_token you got from Worklio's GET .../plaid/{companyBankId}/token call
    const LINK_TOKEN = "link-sandbox-f5166a5d-9c28-4f66-a53d-3f7b12460b6e";

    const handler = Plaid.create({
      token: LINK_TOKEN,
      onSuccess: (public_token, metadata) => {
        document.getElementById("output").textContent =
          "public_token:\n" + public_token + "\n\nmetadata:\n" + JSON.stringify(metadata, null, 2);
        console.log("public_token:", public_token);
        console.log("metadata:", metadata);
      },
      onExit: (err, metadata) => {
        console.log("Exited:", err, metadata);
      }
    });

    document.getElementById("link-button").addEventListener("click", () => {
      handler.open();
    });
  </script>
</body>
</html>

When the employee finishes connecting their bank, onSuccess returns a public_token and a metadata object. The metadata.account object describes the account the employee selected:

metadata:
{
  "status": null,
  "link_session_id": "01a0f2a4-013d-7d7f-9086-b269ea037f95",
  "institution": {
    "name": "Tartan Bank",
    "institution_id": "ins_109511"
  },
  "accounts": [
    {
      "id": "ywW1A7wnJ6FZNMKQqGAKcabWQPg6G7f4Kb1yZ",
      "name": "Plaid Checking",
      "mask": "0000",
      "type": "depository",
      "subtype": "checking",
      "verification_status": null,
      "class_type": null
    }
  ],
  "account": {
    "id": "ywW1A7wnJ6FZNMKQqGAKcabWQPg6G7f4Kb1yZ",
    "name": "Plaid Checking",
    "mask": "0000",
    "type": "depository",
    "subtype": "checking",
    "verification_status": null,
    "class_type": null
  },
  "account_id": "ywW1A7wnJ6FZNMKQqGAKcabWQPg6G7f4Kb1yZ",
  "transfer_status": null,
  "wallet": null,
  "add_card": null,
  "session_group_id": null,
  "public_token": "public-sandbox-aea24cda-3e34-4392-a228-01d2fc34dc2f"
}

The public token is a different token from the linkToken. Map the onSuccess values to the Step 4 fields as follows:

Plaid valueStep 4 field
public_tokenpublicToken
metadata.account.idaccountId
metadata.account.maskaccountMask
metadata.account.nameaccountName
metadata.account.typeaccountType
metadata.account.subtypeaccountSubtype

Step 4: Link the account

Submits the public token and the selected account's details. Worklio exchanges the public token and links the account.

POST https://api.worklio.com/wep/companies/{CLIENT_ID}/employees/{EMPLOYEE_ID}/plaid/{BANK_ACCOUNT_ID}/link

Fields

FieldTypeRequiredDescription
publicTokenstringYesThe public_token from Step 3.
accountIdstringYesPlaid's identifier for the selected account (metadata.account.id). This is not the BANK_ACCOUNT_ID path parameter, which is Worklio's id from Step 1.
accountMaskstringYesLast 4 digits of the account number, as reported by Plaid.
accountNamestringYesThe account's display name, as reported by Plaid.
accountTypestringYesPlaid's account type, for example "depository".
accountSubtypestringYesPlaid's account subtype, for example "checking".

Example request

require("dotenv").config();

const TOKEN = process.env.API_KEY;
const CLIENT_ID = process.env.CLIENT_ID;
const EMPLOYEE_ID = process.env.EMPLOYEE_ID;
const BANK_ACCOUNT_ID = process.env.BANK_ACCOUNT_ID;
const url = `https://api.worklio.com/wep/companies/${CLIENT_ID}/employees/${EMPLOYEE_ID}/plaid/${BANK_ACCOUNT_ID}/link`;

const payload = {
  publicToken: "public-sandbox-aea24cda-3e34-4392-a228-01d2fc34dc2f",
  accountId: "ywW1A7wnJ6FZNMKQqGAKcabWQPg6G7f4Kb1yZ",
  accountMask: "0000",
  accountName: "Plaid Checking",
  accountType: "depository",
  accountSubtype: "checking"
};

async function linkPlaidAccount() {
  const response = await fetch(url, {
    method: "POST",
    headers: {
      accept: "application/json",
      "api-version": "2.0",
      authorization: `Bearer ${TOKEN}`,
      "content-type": "application/json",
      "x-api-version": "2.0"
    },
    body: JSON.stringify(payload)
  });

  console.log(response.status);
  console.log(JSON.stringify(await response.json(), null, 2));
}

linkPlaidAccount();
curl -s -X POST "https://api.worklio.com/wep/companies/$CLIENT_ID/employees/$EMPLOYEE_ID/plaid/$BANK_ACCOUNT_ID/link" \
  -H "accept: application/json" \
  -H "api-version: 2.0" \
  -H "authorization: Bearer $API_KEY" \
  -H "content-type: application/json" \
  -H "x-api-version: 2.0" \
  -d '{
    "publicToken": "public-sandbox-aea24cda-3e34-4392-a228-01d2fc34dc2f",
    "accountId": "ywW1A7wnJ6FZNMKQqGAKcabWQPg6G7f4Kb1yZ",
    "accountMask": "0000",
    "accountName": "Plaid Checking",
    "accountType": "depository",
    "accountSubtype": "checking"
  }' | jq .

Example response

{
  "linkAccountStatus": 1,
  "verifiedAt": "2026-09-30T14:11:00Z",
  "verifiedExp": "2027-09-30T14:11:00Z",
  "availableBalance": 100,
  "currentBalance": 110,
  "lastBalance": "2026-09-30T14:11:00Z",
  "isRefreshEnabled": false,
  "nextCheckOn": "2026-09-30T15:11:00Z",
  "accountNumber": "6666660000",
  "routingNumber": "011401533",
  "accountMask": "0000",
  "linkAccountNumber": "1111222233330000",
  "linkRoutingNumber": "011401533",
  "failedCount": 0
}{
  "linkAccountStatus": 1,
  "verifiedAt": "2026-09-30T14:11:00Z",
  "verifiedExp": "2027-09-30T14:11:00Z",
  "availableBalance": 100,
  "currentBalance": 110,
  "lastBalance": "2026-09-30T14:11:00Z",
  "isRefreshEnabled": false,
  "nextCheckOn": "2026-09-30T15:11:00Z",
  "accountNumber": "6666660000",
  "routingNumber": "011401533",
  "accountMask": "0000",
  "linkAccountNumber": "1111222233330000",
  "linkRoutingNumber": "011401533",
  "failedCount": 0
}
FieldDescription
linkAccountStatusInteger status of the link. The example shows 1. Named values for this field aren't confirmed yet.
verifiedAt / verifiedExpWhen verification completed, and when it expires.
availableBalance / currentBalanceBalance snapshot at verification time.
lastBalanceTimestamp of the last balance check.
isRefreshEnabledWhether Worklio automatically refreshes this account's balance.
nextCheckOnWhen the next scheduled balance check runs.
accountNumber / routingNumberThe account and routing numbers on file, as submitted in Step 1.
accountMaskLast 4 digits of the account number.
linkAccountNumber / linkRoutingNumberThe account and routing numbers as reported through Plaid.
failedCountNumber of failed verification attempts.

If you want to update plaid link connection you need to also pass institutionId. Without passing it you will get an Institution ID does not match the linked account! error.

{
  "status": 0,
  "code": "400",
  "errorCode": "UpdateLinkedAccount",
  "message": "10/20/2026 - 11:01:04 AM : Institution ID does not match the linked account!",
  "stackTrace": "",
  "pagination": {
    "pageNo": 0,
    "pageSize": 0,
    "totalRecords": 0,
    "totalPages": 0,
    "dataToken": ""
  },
  "validationErrors": null
}

Incorrect publicToken will result in Failed to exchange public token for access token.

{
  "status": 0,
  "code": "400",
  "errorCode": "UpdateLinkedAccount",
  "message": "10/20/2026 - 11:03:38 AM : Failed to exchange public token for access token.",
  "stackTrace": "",
  "pagination": {
    "pageNo": 0,
    "pageSize": 0,
    "totalRecords": 0,
    "totalPages": 0,
    "dataToken": ""
  },
  "validationErrors": null
}

Notes

  • Two different account ids. BANK_ACCOUNT_ID in the URL is Worklio's id for the employee bank account (Step 1). accountId in the Step 4 body is Plaid's id for the account the employee selected (Step 3). Don't swap them.
  • Two different tokens. The linkToken (Step 2) starts Plaid Link. The public_token (Step 3) is what you submit to Worklio in Step 4.
  • Order matters. Create the account with verificationType: 3 (Step 1) before requesting a link token. The token endpoint needs the BANK_ACCOUNT_ID from Step 1.
  • Expired link token. If expiration passes before the employee finishes Plaid Link, request a new token with the same Step 2 call.

Did this page help you?