Verify bank accounts
ach-debit-fund, ach-debit-collect) after verification.
Choose a verification method
| Method | Use it when | How it works | Time to verify | Limits |
|---|---|---|---|---|
| Instant micro-deposits | Default for most integrations | One $0.01 credit with a code, sent over RTP or FedNow, or same-day ACH if the bank doesn't support instant payments | Same session for banks that support instant payments | Code valid 14 days, 5 attempts per code, 3 verifications per bank account |
| Micro-deposits | You need the traditional two-amount flow | Two small credits the user confirms | 3-5 business days | Amounts valid 30 days |
| Plaid | You already use Plaid | Instant account verification (IAV) with a Plaid processor token; links and verifies in one request | Instant | Requires a Plaid contract and production keys |
| MX | You already use MX | IAV with an MX authorization code; links and verifies in one request | Instant | Requires an MX account |
For instant and traditional micro-deposits, link the bank account first. When verification succeeds, the bank account status changes to verified and Moov sends a bankAccount.updated webhook.
Instant micro-deposits
Instant micro-deposits verify ownership in a single session and meet Nacha bank account validation requirements. Moov sends a $0.01 credit with a verification code, and the user enters the code from their bank transaction history.
Users have 14 days and five attempts to enter the code. If the code expires or is entered incorrectly five times, initiate verification again. Each bank account can be verified up to three times.
Initiate
Initiate the instant bank account verification process through Moov's instant micro-deposit verification POST endpoint. A 4-digit numeric code is generated and sent with a $0.01 credit to the user's bank account. The code appears on the user’s transaction history as MV{code}.
Use the X-Wait-For: rail-response header to synchronously receive the outcome of the instant credit in the response payload. The response will contain the details of the verificationMethod and status. The verificationMethod (instant for RTP or FedNow, ach for same-day ACH) is automatically selected based on the bank's capabilities.
curl -X POST "https://api.moov.io/accounts/{accountID}/bank-accounts/{bankAccountID}/verify" \
-H "Authorization: Bearer {token}" \
-H "x-moov-version: v2026.07.00"
mc, _ := moov.NewClient()
var accountID string
var bankAccountID string
mc.InstantVerificationInitiate(ctx, accountID, bankAccountID)
const result = await moov.bankAccounts.initiateVerification({
accountID: "9d6d433b-c13a-48d7-849a-1897fa71375b",
bankAccountID: "bd092468-393d-4ee0-85a8-48238fee70d2",
});
res = moov.bank_accounts.initiate_verification(account_id="9d6d433b-c13a-48d7-849a-1897fa71375b", bank_account_id="bd092468-393d-4ee0-85a8-48238fee70d2")
res = s.bank_accounts.initiate_verification(account_id: '9d6d433b-c13a-48d7-849a-1897fa71375b', bank_account_id: 'bd092468-393d-4ee0-85a8-48238fee70d2')
InitiateBankAccountVerificationResponse res = sdk.bankAccounts().initiateVerification()
.accountID("9d6d433b-c13a-48d7-849a-1897fa71375b")
.bankAccountID("bd092468-393d-4ee0-85a8-48238fee70d2")
.call();
var res = await sdk.BankAccounts.InitiateVerificationAsync(
accountID: "9d6d433b-c13a-48d7-849a-1897fa71375b",
bankAccountID: "bd092468-393d-4ee0-85a8-48238fee70d2"
);
$response = $sdk->bankAccounts->initiateVerification(
accountID: '9d6d433b-c13a-48d7-849a-1897fa71375b',
bankAccountID: 'bd092468-393d-4ee0-85a8-48238fee70d2'
);
Possible response status:
new: Verification initiated, credit pendingsent-credit: Credit sent, available for verification in the external bank accountfailed: Verification failed due to credit rejection/return, seeexceptionDetails
Complete
Complete the verification by submitting the 4-digit code via the instant micro-deposit verification PUT endpoint. You'll receive a 2XX response if the correct code is provided. An incorrect code will result in a 409 response with details.
The code is case-insensitive and can be entered with or without the "MV" prefix. The code can follow any of the following accepted formats:
MV0000mv00000000
curl -X PUT "https://api.moov.io/accounts/{accountID}/bank-accounts/{bankAccountID}/verify" \
-H "Authorization: Bearer {token}" \
-H "x-moov-version: v2026.07.00" \
--data-raw '{
"code":"MV1234"
}'
mc, _ := moov.NewClient()
var accountID string
var bankAccountID string
mc.InstantVerificationComplete(ctx, accountID, bankAccountID, "MV1234")
const result = await moov.bankAccounts.completeVerification({
accountID: "2116e60b-0ea7-447e-a7af-5d647f92f06a",
bankAccountID: "96606c15-fd37-49fe-9af2-4e42dcf44fe3",
completeBankAccountVerification: {
code: "MV1234",
},
});
res = moov.bank_accounts.complete_verification(account_id="2116e60b-0ea7-447e-a7af-5d647f92f06a", bank_account_id="96606c15-fd37-49fe-9af2-4e42dcf44fe3", code="MV1234")
res = s.bank_accounts.complete_verification(account_id: '2116e60b-0ea7-447e-a7af-5d647f92f06a', bank_account_id: '96606c15-fd37-49fe-9af2-4e42dcf44fe3', complete_bank_account_verification: Models::Components::CompleteBankAccountVerification.new(
code: 'MV1234'
))
CompleteBankAccountVerificationResponse res = sdk.bankAccounts().completeVerification()
.accountID("2116e60b-0ea7-447e-a7af-5d647f92f06a")
.bankAccountID("96606c15-fd37-49fe-9af2-4e42dcf44fe3")
.completeBankAccountVerification(CompleteBankAccountVerification.builder()
.code("MV1234")
.build())
.call();
var res = await sdk.BankAccounts.CompleteVerificationAsync(
accountID: "2116e60b-0ea7-447e-a7af-5d647f92f06a",
bankAccountID: "96606c15-fd37-49fe-9af2-4e42dcf44fe3",
body: new CompleteBankAccountVerification() {
Code = "MV1234",
}
);
$completeBankAccountVerification = new Components\CompleteBankAccountVerification(
code: 'MV1234',
);
$response = $sdk->bankAccounts->completeVerification(
accountID: '2116e60b-0ea7-447e-a7af-5d647f92f06a',
bankAccountID: '96606c15-fd37-49fe-9af2-4e42dcf44fe3',
completeBankAccountVerification: $completeBankAccountVerification
);
Upon successful verification, the bank account status will be updated to verified, enabling ACH debit transactions.
Check status
Check the status of the verification using the instant micro-deposit verification GET endpoint. The response includes the verificationMethod and the current status.
{
"status": "successful",
"verificationMethod": "instant"
}
The status will indicate the following:
new: Verification initiated, credit pending to the payment networksent-credit: Credit sent, available for verificationfailed: Verification failed, description provided, user needs to add a new bank accountexpired: Verification expired after 14 days, reinitiate verificationmax-attempts-exceeded: Five incorrect attempts, reinitiate verification
Micro-deposits
Traditional micro-deposits send two small credits to the bank account. The account holder confirms ownership by entering both amounts in your app. Moov debits the same amount at the same time to recoup the credits.
| Detail | Value |
|---|---|
| Arrival | 3-5 business days, depending on how the bank posts transactions |
| Cutoff | Requests before 4:00 PM ET are processed the same day; later requests the next day |
| Amounts | Each credit is up to $0.49, with the description "AcctVerify" |
| Expiration | 30 days; after that, initiate micro-deposits again |
| ACH return | Bank account status changes to verificationFailed or errored, depending on the return code (see bank account status) |
Initiate micro-deposits
Use the initiate micro-deposits POST endpoint. A successful request returns 204 and the bank account status changes to pending.
curl -X POST "https://api.moov.io/accounts/{accountID}/bank-accounts/{bankAccountID}/micro-deposits" \
-H "Authorization: Bearer {token}" \
-H "x-moov-version: v2026.07.00"
mc, _ := moov.NewClient()
var accountID string
var bankAccountID string
mc.MicroDepositInitiate(ctx, accountID, bankAccountID)
const result = await moov.bankAccounts.initiateMicroDeposits({
accountID: "ced90412-ebb4-43d4-a171-0a929ed5fb37",
bankAccountID: "f2b630cc-a8ae-44be-970b-7bed47b2d952",
});
res = moov.bank_accounts.initiate_micro_deposits(account_id="ced90412-ebb4-43d4-a171-0a929ed5fb37", bank_account_id="f2b630cc-a8ae-44be-970b-7bed47b2d952")
res = s.bank_accounts.initiate_micro_deposits(account_id: 'ced90412-ebb4-43d4-a171-0a929ed5fb37', bank_account_id: 'f2b630cc-a8ae-44be-970b-7bed47b2d952')
InitiateMicroDepositsResponse res = sdk.bankAccounts().initiateMicroDeposits()
.accountID("ced90412-ebb4-43d4-a171-0a929ed5fb37")
.bankAccountID("f2b630cc-a8ae-44be-970b-7bed47b2d952")
.call();
// handle response
}
}
var res = await sdk.BankAccounts.InitiateMicroDepositsAsync(
accountID: "ced90412-ebb4-43d4-a171-0a929ed5fb37",
bankAccountID: "f2b630cc-a8ae-44be-970b-7bed47b2d952"
);
$response = $sdk->bankAccounts->initiateMicroDeposits(
accountID: 'ced90412-ebb4-43d4-a171-0a929ed5fb37',
bankAccountID: 'f2b630cc-a8ae-44be-970b-7bed47b2d952'
);
Complete micro-deposits
After the credits arrive, submit both amounts in cents with the complete micro-deposits PUT endpoint. When the amounts match, the bank account status changes to verified.
curl -X PUT "https://api.moov.io/accounts/{accountID}/bank-accounts/{bankAccountID}/micro-deposits" \
-H "Authorization: Bearer {token}" \
-H "x-moov-version: v2026.07.00" \
--data-raw '{
"amounts": [
18,
21
]
}'
mc, _ := moov.NewClient()
var accountID string
var bankAccountID string
amounts := []int{18, 21}
mc.MicroDepositConfirm(ctx, accountID, bankAccountID, amounts)
const result = await moov.bankAccounts.completeMicroDeposits({
accountID: "af0efd2b-a1fa-40fd-998b-2007b8df0766",
bankAccountID: "b468e916-699b-4590-becf-ede76d1413cb",
completeMicroDeposits: {
amounts: [
18,
21,
],
},
});
res = moov.bank_accounts.complete_micro_deposits(account_id="af0efd2b-a1fa-40fd-998b-2007b8df0766", bank_account_id="b468e916-699b-4590-becf-ede76d1413cb", amounts=[
18,
21,
])
res = s.bank_accounts.complete_micro_deposits(account_id: 'af0efd2b-a1fa-40fd-998b-2007b8df0766', bank_account_id: 'b468e916-699b-4590-becf-ede76d1413cb', complete_micro_deposits: Models::Components::CompleteMicroDeposits.new(
amounts: [
18,
21,
]
))
CompleteMicroDepositsResponse res = sdk.bankAccounts().completeMicroDeposits()
.accountID("af0efd2b-a1fa-40fd-998b-2007b8df0766")
.bankAccountID("b468e916-699b-4590-becf-ede76d1413cb")
.completeMicroDeposits(CompleteMicroDeposits.builder()
.amounts(List.of(
18L,
21L))
.build())
.call();
var res = await sdk.BankAccounts.CompleteMicroDepositsAsync(
accountID: "af0efd2b-a1fa-40fd-998b-2007b8df0766",
bankAccountID: "b468e916-699b-4590-becf-ede76d1413cb",
body: new CompleteMicroDeposits() {
Amounts = new List<long>() {
18,
21,
},
}
);
$completeMicroDeposits = new Components\CompleteMicroDeposits(
amounts: [
18,
21,
],
);
$response = $sdk->bankAccounts->completeMicroDeposits(
accountID: 'af0efd2b-a1fa-40fd-998b-2007b8df0766',
bankAccountID: 'b468e916-699b-4590-becf-ede76d1413cb',
completeMicroDeposits: $completeMicroDeposits
);
Plaid
Moov's partnership with Plaid allows mutual customers to verify bank accounts through instant account verification (IAV).
You have two options to integrate Plaid:
Moov-managed Plaid integration
Once Plaid has completed the OAuth registration process, you can use Moov.js to link bank accounts using Plaid. See our Moov.js Plaid Link documentation.
Bring your own Plaid integration
If you are an existing Plaid customer, follow these steps to connect Plaid with Moov:
- Follow Plaid's documentation on adding Moov to your app
- Verify the user's bank account with Plaid Link
- Use the output from Plaid Link to retrieve a Plaid
processor_tokenvia their/processor/token/createendpoint - Pass the
processor_tokento Moov's bank accountPOSTendpoint
Once you've completed these steps, Moov will inspect the Plaid processor token for bank account information and treat it as a verified bank account that's ready to be used as a source for a Moov account.
processor_token. Plaid sandbox keys will fail when passed to Moov.
MX
Moov's partnership with MX allows mutual customers to verify bank accounts through instant account verification (IAV).
To enable instant account verification (IAV), you will need to:
- Set up your accounts
- Get authorization code
- Pass the MX authorization code to Moov's bank account
POSTendpoint
Set up your MX account
You will need an MX account and a Moov account in order to use this integration. If you don't have a Moov account, you can sign up for one. Refer to our quick start guide for guidance on getting your API keys and configuring your Moov account once you've signed up.
Before you can use the Moov + MX integration, you will also need to have an API key and a client ID for your MX account.
Your MX account must also have the following enabled:
has_payment_processinghas_platform_apihas_account_verification
Create an MX authorization code
An authorization code is a one-time use token used to authenticate your application with MX. You'll create an authorization code by passing the following information to the Request Payment Processor Authorization Code MX endpoint.
{
"payment_processor_authorization_code": {
"user_guid": "USR-ba246888-cc62-40a6-8967-baa76dbf1cc7",
"member_guid": "MBR-4145eb50-3721-4f48-a983-d874c8bd5eb9",
"account_guid": "ACT-dc10aeb9-66a0-4c5c-b6a7-720b637f1fb6"
}
}
Pass the MX authorization code to Moov
Once you've successfully created an authorization code, you'll pass that authorization code to Moov via the bank account POST endpoint.
curl -X POST "https://api.moov.io/accounts/{accountID}/bank-accounts" \
-H "Authorization: Bearer {token}" \
-H "x-moov-version: v2026.07.00" \
--data-raw '{
"mx": {
"authorizationCode": "mx-authorization-code"
}
}'
const moov = Moov(token);
const accountID = "accountID";
const mxAuthorizationCode = "token";
moov.accounts.bankAccounts.link({accountID, mxAuthorizationCode});
Moov will then retrieve bank account details directly from MX, so you won’t need to directly handle sensitive bank information when linking bank accounts.
The bank account must have passed validation with MX, otherwise the bank account will be added in the verificationFailed status.