ACH Verify Transaction

Validate bank account information before processing payments

ACH Verification

An ACH Verify transaction validates a customer's bank account information without transferring funds. This is useful for confirming bank account details before processing payments or setting up recurring billing.

When to Use ACH Verify

Use CaseRecommendation
Before first paymentVerify to catch invalid account details early
Setting up recurring paymentsVerify and tokenize before the first scheduled payment
Updating payment methodsVerify new bank account before replacing existing
High-value transactionsVerify before processing large ACH debits
Braintree processorRequired - Must verify before sale transactions

Important: When using Braintree as your processor, you must run a verify transaction before processing ACH sales. This is due to Braintree's bank account vaulting requirements.

Processor Support

Not all processors support ACH verification. Check the table below:

ProcessorVerify SupportedVerification MethodNotes
Braintree✅ YesBank account vaultingRequired before sale
Payrix✅ YesZero-dollar transactionTest in test mode first
Adyen✅ YesZero-dollar authorization
Adyen for Platforms✅ YesZero-dollar authorization
Finix✅ YesZero-dollar authorization
Authorize.Net✅ YesZero-dollar authorization
AffiniPay✅ Yes$1.00 authorizationAmount is not captured

Note on Verification Depth: Zero-dollar verifications typically confirm that the routing number is valid and the account number format is correct. They may not verify account ownership, available balance, or that the account is open. For enhanced verification, some processors offer third-party verification services (like GIACT for Payrix).

Endpoint

POST /v1/transactions

Request Format

Using a Token (Recommended)

If you have an existing ACH token, use it directly:

{
  "type": "verify",
  "merchantId": "mid_abc123",
  "payment": {
    "token": "tkn_xyz789"
  }
}

Token Field Inheritance: If the token was created with firstName, lastName, accountBusinessName, or achMandate, those values are automatically used for the transaction. You don't need to include them in the request.

Overriding Token Fields: If you include firstName, lastName, accountBusinessName, or achMandate in the request, those values will be used for the transaction and the token will be updated with the new values.

Using Raw Bank Account Details

Alternatively, you can pass bank account details directly:

{
  "type": "verify",
  "merchantId": "mid_abc123",
  "payment": {
    "bankAccount": {
      "type": "personalChecking",
      "account": "123456789",
      "routing": "987654321",
      "bankCountry": "USA"
    }
  },
  "firstName": "John",
  "lastName": "Doe"
}

Verify with Tokenization

To create a multi-use token for future transactions, add ?tokenize=true:

POST /v1/transactions?tokenize=true
{
  "type": "verify",
  "merchantId": "mid_abc123",
  "payment": {
    "bankAccount": {
      "type": "personalChecking",
      "account": "123456789",
      "routing": "987654321",
      "bankCountry": "USA"
    }
  },
  "firstName": "John",
  "lastName": "Doe"
}

Verify for Braintree (ACH Mandate Required)

When using Braintree, you must include the achMandate field:

{
  "type": "verify",
  "merchantId": "mid_abc123",
  "payment": {
    "token": "tkn_xyz789"
  },
  "achMandate": "I authorize [Your Company] to verify my bank account and process future debits as agreed."
}

Note: If your token was created with an achMandate, you don't need to include it again unless you want to update the authorization text.

Request Fields

Required Fields

FieldTypeDescription
typestringMust be "verify"
merchantIdstringThe merchant ID
payment.tokenstringExisting ACH token (recommended)
payment.bankAccountobjectRaw bank account details (alternative to token)

Bank Account Fields (when not using token)

FieldTypeDescription
bankAccount.typestringAccount type (see below)
bankAccount.accountstringAccount number (4-17 digits)
bankAccount.routingstringRouting number (9 digits)
bankAccount.bankCountrystringMust be "USA"

Conditional Fields

FieldTypeConditionDescription
firstNamestringPersonal accounts (if not on token)Customer's first name
lastNamestringPersonal accounts (if not on token)Customer's last name
accountBusinessNamestringCorporate accounts (if not on token)Business name
achMandatestringBraintree processor (if not on token)Authorization text

Note: When using a token, these conditional fields are only required if they weren't provided when the token was created. If included, they will override the token's stored values.

Account Types

ValueDescription
personalCheckingIndividual checking account
personalSavingsIndividual savings account
corporateCheckingBusiness checking account
corporateSavingsBusiness savings account

Response Format

Successful Verification

{
  "id": "txn_01abc123def456",
  "type": "verify",
  "currency": "USD",
  "merchantId": "mid_abc123",
  "authorization": {
    "status": "A",
    "processorCode": "00",
    "processorMessage": "Bank account verified",
    "approvedAmount": 0
  },
  "payment": {
    "type": "personalChecking",
    "last4": "6789",
    "bin": "987654321"
  },
  "processor": {
    "id": "pfmCon_xyz789",
    "name": "Payrix",
    "routingSource": "plan"
  },
  "firstName": "John",
  "lastName": "Doe",
  "createdOn": "2024-01-15T10:30:00Z"
}

Verification with Token

When using ?tokenize=true:

{
  "id": "txn_01abc123def456",
  "type": "verify",
  "currency": "USD",
  "merchantId": "mid_abc123",
  "authorization": {
    "status": "A",
    "processorCode": "00",
    "processorMessage": "Bank account verified",
    "approvedAmount": 0
  },
  "payment": {
    "token": "tkn_xyz789",
    "type": "personalChecking",
    "last4": "6789",
    "bin": "987654321"
  },
  "processor": {
    "id": "pfmCon_xyz789",
    "name": "Payrix",
    "routingSource": "plan"
  },
  "firstName": "John",
  "lastName": "Doe",
  "createdOn": "2024-01-15T10:30:00Z"
}

Failed Verification

{
  "id": "txn_01abc123def456",
  "type": "verify",
  "currency": "USD",
  "merchantId": "mid_abc123",
  "authorization": {
    "status": "D",
    "processorCode": "R03",
    "processorMessage": "No account/unable to locate account",
    "approvedAmount": 0
  },
  "payment": {
    "type": "personalChecking",
    "last4": "6789",
    "bin": "987654321"
  },
  "processor": {
    "id": "pfmCon_xyz789",
    "name": "Payrix",
    "routingSource": "plan"
  },
  "firstName": "John",
  "lastName": "Doe",
  "createdOn": "2024-01-15T10:30:00Z"
}

Braintree Verify-Before-Sale Flow

When using Braintree, follow this required flow:

Step 1: Verify bank account
        ↓
Step 2: Store the token from verify response
        ↓
Step 3: Use token for sale transactions

Step 1: Verify and Tokenize

POST /v1/transactions?tokenize=true
{
  "type": "verify",
  "merchantId": "mid_abc123",
  "payment": {
    "bankAccount": {
      "type": "personalChecking",
      "account": "123456789",
      "routing": "987654321",
      "bankCountry": "USA"
    }
  },
  "firstName": "John",
  "lastName": "Doe",
  "achMandate": "I authorize Example Corp to debit my bank account."
}

Step 2: Store the Token

Save the payment.token value from the response (e.g., tkn_xyz789) in your database associated with the customer.

Step 3: Process Sale with Token

{
  "type": "sale",
  "merchantId": "mid_abc123",
  "amount": 5000,
  "currency": "USD",
  "payment": {
    "token": "tkn_xyz789"
  }
}

Common Verification Failures

Processor CodeMeaningRecommended Action
R01Insufficient fundsN/A for verify (no funds debited)
R02Account closedAsk customer for different account
R03No account/unable to locateVerify account number with customer
R04Invalid account numberCustomer entered incorrect account
R07Authorization revokedCustomer revoked ACH authorization
R10Customer advises not authorizedCustomer disputes the authorization
R16Account frozenAsk customer for different account
R20Non-transaction accountAccount doesn't allow debits

Best Practices

1. Always Verify Before Recurring Payments

async function setupRecurringPayment(customerId, bankAccount, customer) {
  // Step 1: Verify the bank account
  const verifyResult = await preczn.transactions.create({
    type: 'verify',
    merchantId: MERCHANT_ID,
    payment: { bankAccount },
    firstName: customer.firstName,
    lastName: customer.lastName
  }, { tokenize: true });

  if (verifyResult.authorization.status !== 'A') {
    throw new Error('Bank account verification failed');
  }

  // Step 2: Store the token for recurring use
  await savePaymentMethod(customerId, verifyResult.payment.token);

  return verifyResult.payment.token;
}

2. Handle Verification Failures Gracefully

function handleVerificationResult(result) {
  switch (result.authorization.status) {
    case 'Approved':
      return { success: true, token: result.payment.token };

    case 'Declined':
      return {
        success: false,
        message: 'We could not verify this bank account. Please check your account and routing numbers.',
        code: result.authorization.processorCode
      };

    default:
      return {
        success: false,
        message: 'There was an error verifying your bank account. Please try again.',
        retry: true
      };
  }
}

3. Verify Before High-Value Transactions

For transactions over a certain threshold, always verify first:

async function processLargePayment(amount, paymentMethod) {
  const VERIFY_THRESHOLD = 100000; // $1,000.00

  if (amount >= VERIFY_THRESHOLD && !paymentMethod.verified) {
    const verifyResult = await verifyBankAccount(paymentMethod);
    if (verifyResult.authorization.status !== 'A') {
      throw new Error('Verification required for large transactions');
    }
  }

  return processPayment(amount, paymentMethod);
}

Error Messages

These are Preczn API errors returned when a request fails validation or processing. The error and message fields correspond to the API response format documented in the ACH Error Reference.

Bank Account Validation Errors

ErrorMessageCauseSolution
Bad RequestRouting number is requiredMissing routing fieldInclude the routing field in bankAccount
Bad RequestRouting number must be exactly 9 digitsRouting number length incorrectVerify the routing number is exactly 9 digits
Bad RequestRouting number is invalid 'USA' bank routing numberFails ABA checksum validationVerify the routing number with customer's bank
Bad RequestAccount number is requiredMissing account fieldInclude the account field in bankAccount
Bad RequestAccount number must be between 4 and 17 digitsAccount number length invalidVerify account number is 4-17 digits
Bad RequestAccount type is requiredMissing type fieldInclude the type field in bankAccount
Bad RequestAccount type must be one of: PersonalChecking, PersonalSavings, CorporateChecking, CorporateSavingsInvalid account typeUse a valid account type value
Bad RequestBank country is requiredMissing bankCountry fieldInclude bankCountry: "USA"
Bad RequestBank country must be one of: USAUnsupported countryACH only supports US bank accounts

Customer Information Errors

ErrorMessageCauseSolution
Bad RequestPersonal ACH accounts require firstName and lastNamePersonal account missing name fieldsInclude both firstName and lastName fields
Bad RequestCorporate ACH accounts require accountBusinessName or firstName and lastNameCorporate account missing identificationInclude accountBusinessName or both firstName and lastName
Bad RequestACH mandate is required for Braintree ACH transactionsBraintree requires authorization textInclude achMandate field

Token Errors

ErrorMessageCauseSolution
Bad Requestpayment.token must be a valid token IDInvalid or expired tokenVerify the token ID is correct and exists

Configuration Errors

ErrorMessageCauseSolution
Bad RequestACH transaction not supported for this PlanMerchant's plan lacks ACH processorContact support to enable ACH

For a complete list of errors, see the ACH Error Reference.