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:
- Create the employee bank account with
verificationType: 3(Plaid). - Request a Plaid link token from Worklio.
- Launch Plaid Link on the client and get a public token.
- 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:
| Parameter | Type | Description |
|---|---|---|
CLIENT_ID | integer | The company's id, returned when you create the company. |
EMPLOYEE_ID | integer | The employee's id, returned when you create the employee. |
BANK_ACCOUNT_ID | integer | The 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
| Field | Type | Description |
|---|---|---|
accountHolderName | string | The name of the account holder. |
accountNumber | string | The bank account number. |
routingNumber | string | The bank's routing number. |
accountType | integer | The account type. The example uses 1. |
verificationType | integer | How 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"
}| Field | Type | Description |
|---|---|---|
linkToken | string | The token you pass to Plaid Link in Step 3. |
expiration | string (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 value | Step 4 field |
|---|---|
public_token | publicToken |
metadata.account.id | accountId |
metadata.account.mask | accountMask |
metadata.account.name | accountName |
metadata.account.type | accountType |
metadata.account.subtype | accountSubtype |
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
| Field | Type | Required | Description |
|---|---|---|---|
publicToken | string | Yes | The public_token from Step 3. |
accountId | string | Yes | Plaid'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. |
accountMask | string | Yes | Last 4 digits of the account number, as reported by Plaid. |
accountName | string | Yes | The account's display name, as reported by Plaid. |
accountType | string | Yes | Plaid's account type, for example "depository". |
accountSubtype | string | Yes | Plaid'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
}| Field | Description |
|---|---|
linkAccountStatus | Integer status of the link. The example shows 1. Named values for this field aren't confirmed yet. |
verifiedAt / verifiedExp | When verification completed, and when it expires. |
availableBalance / currentBalance | Balance snapshot at verification time. |
lastBalance | Timestamp of the last balance check. |
isRefreshEnabled | Whether Worklio automatically refreshes this account's balance. |
nextCheckOn | When the next scheduled balance check runs. |
accountNumber / routingNumber | The account and routing numbers on file, as submitted in Step 1. |
accountMask | Last 4 digits of the account number. |
linkAccountNumber / linkRoutingNumber | The account and routing numbers as reported through Plaid. |
failedCount | Number 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_IDin the URL is Worklio's id for the employee bank account (Step 1).accountIdin 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. Thepublic_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 theBANK_ACCOUNT_IDfrom Step 1. - Expired link token. If
expirationpasses before the employee finishes Plaid Link, request a new token with the same Step 2 call.
Updated 37 minutes ago
