Pay out to a bank account
In this recipe, your partner account pays a recipient, such as a contractor, seller, or claimant. Your Moov wallet is the source, the recipient's bank account is the destination, and the recipient only needs the transfers capability. You'll create the recipient, link and verify their bank account, choose a rail, send the payout, and react to the webhooks that tell you whether it arrived.
Prerequisites
- Everything in Before you start: test mode API keys, authentication headers, your partner account ID, and a webhook endpoint
- On your partner account:
wallet.balanceplussend-funds.achfor ACH payouts, orsend-funds.instant-bankfor instant payouts. Check them with the list capabilities endpoint. - A wallet balance that covers the payout. To add funds, follow fund and spend from a wallet; in test mode, weekday ACH debits complete in about an hour.
Steps
Create the recipient's account
Create an individual account for the recipient with the create account endpoint. A payout recipient only needs transfers, which Moov enables automatically, so you don't need to request capabilities or collect identity documents for this account.
curl -X POST "https://api.moov.io/accounts" \
-H "Authorization: Bearer {token}" \
-H "X-Moov-Version: v2026.07.00" \
-d '{
"accountType": "individual",
"profile": {
"individual": {
"name": { "firstName": "Jules", "lastName": "Jackson" },
"email": "julesjacksonyoga@moov.io"
}
},
"foreignID": "contractor-1042"
}'
Save the accountID. Deeper guide: create an individual account.
Link the recipient's bank account
Link the bank account with the link bank account endpoint. In test mode, use routing number 021000021 to get an account that's eligible for instant payments, or 322271627 for an ACH-only account. Any account number works.
curl -X POST "https://api.moov.io/accounts/{recipientAccountID}/bank-accounts" \
-H "Authorization: Bearer {token}" \
-H "X-Moov-Version: v2026.07.00" \
-H "X-Wait-For: payment-method" \
-d '{
"account": {
"holderName": "Jules Jackson",
"holderType": "individual",
"accountNumber": "12345654321",
"bankAccountType": "checking",
"routingNumber": "021000021"
}
}'
Save the bankAccountID. With X-Wait-For: payment-method, the response lists the new payment methods. You can also link through Plaid or MX, or let the recipient add the account themselves with the payment methods Drop. Deeper guide: link bank accounts.
Verify the bank account
Credits to a bank account (ach-credit-standard, ach-credit-same-day, instant-bank-credit) are available as soon as the account is linked. Verification is still a best practice for payouts, because it confirms the recipient owns the account before you send money, and it's required if you'll ever debit the account.
Start instant micro-deposit verification with the initiate verification endpoint. Moov sends $0.01 with a 4-digit code, over RTP when the bank supports it and same-day ACH otherwise.
curl -X POST "https://api.moov.io/accounts/{recipientAccountID}/bank-accounts/{bankAccountID}/verify" \
-H "Authorization: Bearer {token}" \
-H "X-Moov-Version: v2026.07.00" \
-H "X-Wait-For: rail-response"
The recipient finds the code in their bank transactions and enters it in your app. Submit it with the complete verification endpoint (PUT on the same path, body {"code": "MV1234"}). In test mode, the code is 0001. The bank account's status changes to verified. Deeper guide: bank account verification.
Choose the payment methods
You need two payment method IDs: the moov-wallet payment method on your partner account (the source) and a bank credit payment method on the recipient's account (the destination). List the recipient's payment methods for this bank account with the list payment methods endpoint:
curl -X GET "https://api.moov.io/accounts/{recipientAccountID}/payment-methods?sourceID={bankAccountID}" \
-H "Authorization: Bearer {token}" \
-H "X-Moov-Version: v2026.07.00"
Pick the destination by speed:
| Payment method | Arrives | Notes |
|---|---|---|
instant-bank-credit |
Seconds, 24/7/365 | Instant payments (RTP or FedNow). Only present if the receiving bank is eligible. Final once sent. |
ach-credit-same-day |
Same business day if sent before the cutoff | Up to $500,000 per transfer |
ach-credit-standard |
1-2 business days | Lowest cost |
Then get your own moov-wallet payment method with the same endpoint on your partner account and paymentMethodType=moov-wallet. To ask Moov which combinations are valid for a specific amount, use the transfer options endpoint. Deeper guides: payment methods and ACH processing speeds.
Send the payout
Create the transfer with the create transfer endpoint. This example sends an instant payment; for ACH, swap in the ach-credit-same-day or ach-credit-standard payment method ID. Nothing else in the request changes.
curl -X POST "https://api.moov.io/accounts/{partnerAccountID}/transfers" \
-H "Authorization: Bearer {token}" \
-H "X-Moov-Version: v2026.07.00" \
-H "X-Idempotency-Key: 0f8e2c6a-7d41-4b9e-a3c5-6e1f2d3c4b5a" \
-H "X-Wait-For: rail-response" \
-d '{
"source": { "paymentMethodID": "{partnerWalletPaymentMethodID}" },
"destination": { "paymentMethodID": "{recipientInstantBankPaymentMethodID}" },
"amount": { "currency": "USD", "value": 25000 },
"description": "Invoice 1042 payout"
}'
For instant payments, the response includes destination.instantBankDetails with the network used (rtp or fednow) and a rail status. For ACH, it includes destination.achDetails with a traceNumber the recipient can give their bank. To pass the payout fee to the recipient, see payout fees. Deeper guides: send funds and instant payments.
Handle failures and returns
Subscribe to transfer.updated and bankAccount.updated, then look up the transfer when either fires:
curl -X GET "https://api.moov.io/accounts/{partnerAccountID}/transfers/{transferID}" \
-H "Authorization: Bearer {token}" \
-H "X-Moov-Version: v2026.07.00"
What happens depends on the rail:
- Instant payments: the receiving bank answers in seconds. A rejection sets
instantBankDetails.statustofailedwith afailureCodesuch asaccount-closed, and the money stays in your wallet. Instant payments that complete can't be returned. - ACH credits: the receiving bank can return the credit after it's sent, typically within two banking days. A return sets
destination.achDetails.statustoreturned, fills indestination.achDetails.return.code(for exampleR03, no account), changes the transferstatustoreversed, and credits the amount back to your wallet.
Either way, check the bank account's status. If it's errored, the account can't be used again; ask the recipient to link a new one. If it's verificationFailed, re-verify it. To test returns, send an ACH credit for 5503 ($55.03, R03) or an instant payment for 7204 ($72.04, account closed). Deeper guides: ACH returns and bank account status.
What to watch for
| Signal | What it means | What to do |
|---|---|---|
bankAccount.updated with status verified |
The recipient confirmed ownership | Allow payouts to this account |
transfer.updated with status completed |
Funds were delivered (instant) or the ACH credit settled | Mark the payout as paid |
transfer.updated with status failed |
The payout was rejected before funds moved, or your wallet didn't have enough funds (wallet-insufficient-funds) |
Check failureReason; fund your wallet or fix the bank account and retry with a new idempotency key |
transfer.updated with status reversed |
An ACH credit was returned after it was sent | Read the return code; funds are back in your wallet |
bankAccount.updated with status errored |
The bank account can't receive payments | Ask the recipient to link a new bank account |