Automated Micro-deposits

Learn how to authenticate your users in a secure and frictionless micro-deposit flow

The Automated Micro-deposits authentication flow is supported for ~3,200 financial institutions in the US only. Plaid will make a single micro-deposit and then automatically verify it within one to two business days.

Not all Plaid Developer accounts are enabled for Instant Match, Automated Micro-deposits, or Same Day Micro-deposits by default. To enable these features or check your status, contact your account manager or submit a product access Support ticket.

The Automated Micro-deposit flow

A user connects their financial institution using the following connection flow:

  1. Starting on a page in your app, the user clicks an action that opens Plaid Link, with the correct Auth configuration.
  2. Inside of Plaid Link, the user selects their institution, and authenticates with their credentials, providing their legal name, account and routing number, and selects their account type.
  3. Upon successful authentication, Link closes with a public_token and a metadata account status of pending_automatic_verification.
  4. Behind the scenes, Plaid sends a single micro-deposit to the user's account and will automatically verify the deposited amounts within one to two business days.
  5. When verification succeeds or fails, Plaid sends an Auth webhook, which you can use to notify the user that their account is ready to move money. Once this step is done, your user's Auth data is verified and ready to fetch.
  6. Finally, Plaid will reverse the micro-deposit to pull back the deposit amount from the user's bank account.

Configure & Create a link_token

Create a link_token with the following parameters:

  • products array containing auth – If you depend on multiple Plaid products, you can always call product endpoints later, once you have received and verified an access_token.
  • country_codes set to ['US'] – Micro-deposit verification is currently only available in the United States.
  • A webhook URL to receive a POST HTTPS request sent from Plaid's servers to your application server, after Automated Micro-deposits succeeds or fails verification of a user's micro-deposits.
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
37
// Using Express
const express = require('express');
const app = express();
app.use(express.json());
const plaid = require('plaid');
const client = new plaid.Client({
clientID: process.env.PLAID_CLIENT_ID,
secret: process.env.PLAID_SECRET,
env: plaid.environments.sandbox,
});
app.post('/create_link_token', async (request, response) => {
try {
// Get the client_user_id by searching for the current user
const user = await User.find(...);
const clientUserId = user.id;
// Create the link_token with all of your configurations
const tokenResponse = await client.createLinkToken({
user: {
client_user_id: clientUserId,
},
client_name: 'Plaid Test App',
products: ["auth"],
country_codes: ['US'],
language: 'en',
webhook: 'https://webhook.sample.com',
});
response.json(tokenResponse);
} catch (e) {
// Display error on client
return response.send({ error: e.message });
}
});

Initialize Link with a link_token

After creating a link_token for the auth product, use it to initialize Plaid Link.

When the user inputs their username and password, and account and routing numbers for the financial institution, the onSuccess() callback function will return a public_token, with verification_status equal to 'pending_automatic_verification'.

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
const linkHandler = Plaid.create({
// Fetch a link_token configured for 'auth' from your app server
token: (await $.post('/create_link_token')).link_token,
onSuccess: (public_token, metadata) => {
// Send the public_token and accounts to your app server
$.post('/exchange_public_token', {
publicToken: public_token,
accounts: metadata.accounts,
});
metadata = {
...,
link_session_id: String,
institution: { name: 'Bank of the West', institution_id: 'ins_100017' },
accounts: [{
id: 'vzeNDwK7KQIm4yEog683uElbp9GRLEFXGK98D',
mask: '1234',
name: null,
type: 'depository',
subtype: 'checking' | 'savings',
verification_status: 'pending_automatic_verification'
}]
}
},
// ...
});
// Open Link on user-action
linkHandler.open();
Display a "pending" status in your app

Because Automated verification usually takes between one to two days to complete, we recommend displaying a UI in your app that communicates to a user that verification will occur automatically and is currently pending.

You can use the verification_status key returned in the onSuccess metadata.accounts object once Plaid Link closes successfully.

1
verification_status: 'pending_automatic_verification';

You can also fetch the verification_status for an Item's account via the Plaid API to obtain the latest account status.

Exchange the public token

In your own backend server, call the /item/public_token/exchange endpoint with the Link public_token received in the onSuccess callback to obtain an access_token. Persist the returned access_token, account_id, and item_id in your database in relation to the user.

Note that micro-deposits will only be delivered to the ACH network in the Production environment. To test your integration outside of Production, see Testing automated micro-deposits in Sandbox.

1
2
3
4
5
6
7
8
9
// publicToken and accountID are sent from your app to your backend-server
const accountID = 'vzeNDwK7KQIm4yEog683uElbp9GRLEFXGK98D';
const publicToken = 'public-sandbox-b0e2c4ee-a763-4df5-bfe9-46a46bce993d';
// Obtain an access_token from the Link public_token
const response = await client.exchangePublicToken(publicToken).catch((err) => {
// handle error
});
const accessToken = response.access_token;
1
2
3
4
5
{
"access_token": "access-sandbox-5cd6e1b1-1b5b-459d-9284-366e2da89755",
"item_id": "M5eVJqLnv3tbzdngLDp9FL5OlDNxlNhlE55op",
"request_id": "m8MDnv9okwxFNBV"
}

Handle Auth webhooks

Before you can call /auth/get to fetch Auth data for a user's access_token, a micro-deposit first need to post successfully to the user's bank account. Because Plaid uses Same Day ACH to send a single micro-deposit amount, this process usually takes between one to two days.

Once the deposit has arrived in the user's account, Plaid will automatically verify the deposit transaction and send an AUTOMATICALLY_VERIFIED webhook to confirm the account and routing numbers have been successfully verified.

Attempting to call /auth/get on an unverified access_token will result in a PRODUCT_NOT_READY error.

1
2
3
4
5
6
7
8
> POST https://your_app_url.com/webhook
{
"webhook_type": "AUTH",
"webhook_code": "AUTOMATICALLY_VERIFIED",
"item_id": "zeWoWyv84xfkGg1w4ox5iQy5k6j75xu8QXMEm",
"account_id": "vzeNDwK7KQIm4yEog683uElbp9GRLEFXGK98D"
}

Occasionally automatic verification may fail, likely due to erroneous user input, such as an incorrect account and routing number pair. If the Item is unable to be verified within seven days, Plaid will send a VERIFICATION_EXPIRED webhook. When verification fails, the Item is permanently locked; we recommend prompting your user to retry connecting their institution via Link.

1
2
3
4
5
6
7
8
> POST https://your_app_url.com/webhook
{
"webhook_type": "AUTH",
"webhook_code": "VERIFICATION_EXPIRED",
"item_id": "zeWoWyv84xfkGg1w4ox5iQy5k6j75xu8QXMEm",
"account_id": "vzeNDwK7KQIm4yEog683uElbp9GRLEFXGK98D"
}

The example code below shows how to handle AUTOMATICALLY_VERIFIED and VERIFICATION_EXPIRED webhooks and call /auth/get to retrieve account and routing data.

If you are using the Sandbox environment, you can use the /sandbox/item/set_verification_status endpoint to test your integration.

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
// This example uses Express to receive webhooks
const app = require('express')();
const bodyParser = require('body-parser');
app.use(bodyParser);
app.post('/webhook', async (request, response) => {
const event = request.body;
// Handle the event
switch (event.webhook_code) {
case 'AUTOMATICALLY_VERIFIED':
const accessToken = lookupAccessToken(event.item_id);
const authResponse = await client.getAuth(accessToken);
const numbers = authResponse.numbers;
break;
case 'VERIFICATION_EXPIRED':
// handle verification failure; prompt user to re-authenticate
console.error('Verification failed for', event.item_id);
break;
default:
// Unexpected event type
return response.status(400).end();
}
// Return a response to acknowledge receipt of the event
response.json({ received: true });
});
app.listen(8000, () => console.log('Running on port 8000'));

Check the account verification status (optional)

In some cases you may want to implement logic to display the verification_status of an Item that is pending automated verification in your app. The /accounts/get API endpoint allows you to query this information.

1
2
3
4
5
6
7
8
9
// Fetch the accountID and accessToken from your database
const accountID = 'vzeNDwK7KQIm4yEog683uElbp9GRLEFXGK98D';
const accessToken = 'access-sandbox-5cd6e1b1-1b5b-459d-9284-366e2da89755';
const response = await client.getAccounts(accessToken).catch((err) => {
// handle error
});
const account = response.accounts.find((a) => a.account_id === accountID);
const verificationStatus = account.verification_status;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"accounts": [
{
"account_id": "vzeNDwK7KQIm4yEog683uElbp9GRLEFXGK98D",
"balances": { Object },
"mask": "0000",
"name": "Plaid Checking",
"official_name": "Plaid Gold Checking",
"type": "depository"
"subtype": "checking" | "savings",
"verification_status":
"pending_automatic_verification" |
"automatically_verified" |
"verification_expired",
},
...
],
"item": { Object },
"request_id": String
}

Fetch Auth data

Finally, we can retrieve Auth data once automated verification has succeeded:

1
2
3
4
5
6
7
const accessToken = 'access-sandbox-5cd6e1b1-1b5b-459d-9284-366e2da89755';
// Instantly fetch Auth numbers
const response = await client.getAuth(accessToken, {}).catch((err) => {
// handle error
});
const numbers = response.numbers;
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
{
"numbers": {
"ach": [
{
"account_id": "vzeNDwK7KQIm4yEog683uElbp9GRLEFXGK98D",
"account": "9900009606",
"routing": "011401533",
"wire_routing": "021000021"
}
],
"eft": [],
"international": [],
"bacs": []
},
"accounts": [
{
"account_id": "vzeNDwK7KQIm4yEog683uElbp9GRLEFXGK98D",
"balances": { Object },
"mask": "0000",
"name": null,
"official_name": null,
"verification_status": "automatically_verified",
"subtype": "checking" | "savings",
"type": "depository"
}
],
"item": { Object },
"request_id": "m8MDnv9okwxFNBV"
}

Check out the /auth/get API reference documentation to see the full Auth request and response schema.