Pay out to a bank account

Pay a person from your Moov wallet to their bank account, by ACH or instant payments (RTP or FedNow), and handle the payout if it comes back.

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.balance plus send-funds.ach for ACH payouts, or send-funds.instant-bank for 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 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.status to failed with a failureCode such as account-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.status to returned, fills in destination.achDetails.return.code (for example R03, no account), changes the transfer status to reversed, 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

Next steps