ACH Best Practices

Reduce ACH returns and failures by validating bank accounts, getting customer authorization, planning for settlement timing, and handling webhooks.

Follow these best practices to ensure successful ACH transactions, reduce returns, and provide a great customer experience.

Bank Account Validation

1. Validate Routing Numbers

Always validate routing numbers before submitting transactions:

  • Format: Must be exactly 9 numeric digits
  • Check Digit: Use the ABA routing number algorithm to verify validity
  • Real-Time: Validate during data entry to catch errors early
// Example: Basic routing number format validation
function isValidRoutingFormat(routing) {
  return /^\d{9}$/.test(routing);
}

// Example: ABA check digit validation
function isValidRoutingCheckDigit(routing) {
  if (!/^\d{9}$/.test(routing)) return false;

  const weights = [3, 7, 1, 3, 7, 1, 3, 7];
  let sum = 0;

  for (let i = 0; i < 8; i++) {
    sum += parseInt(routing[i]) * weights[i];
  }

  const checkDigit = (10 - (sum % 10)) % 10;
  return checkDigit === parseInt(routing[8]);
}

2. Validate Account Numbers

  • Length: Must be between 4 and 17 digits
  • Format: Numeric characters only
  • Display: Show last 4 digits for customer confirmation

3. Match Account Types

Ensure the account type matches the actual bank account:

Account TypeWhen to Use
personalCheckingIndividual's checking account
personalSavingsIndividual's savings account
corporateCheckingBusiness checking account
corporateSavingsBusiness savings account

Tip: Using the wrong account type may cause transaction failures or returns.

Customer Authorization

1. Obtain Proper Authorization

ACH regulations require explicit customer authorization before debiting their account. Include:

  • Clear disclosure of the amount to be debited
  • Frequency of debits (one-time vs. recurring)
  • Cancellation policy and how to revoke authorization
  • Contact information for questions or disputes

2. Store Authorization Records

Keep records of:

  • Date and time of authorization
  • IP address (for online authorizations)
  • Authorization text shown to customer
  • Customer's acceptance confirmation

3. ACH Mandate Text

For some processors (like Braintree), include an achMandate field with the authorization text:

{
  "achMandate": "I authorize [Your Company] to debit my bank account for the amount of $XX.XX on [date]. I understand that I can revoke this authorization by contacting [email protected]."
}

Required Information by Account Type

Personal Accounts

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

Required: firstName and lastName (root-level fields)

Corporate Accounts

{
  "payment": {
    "bankAccount": {
      "type": "corporateChecking",
      "account": "123456789",
      "routing": "987654321",
      "bankCountry": "USA"
    }
  },
  "accountBusinessName": "Acme Corporation"
}

Required: accountBusinessName

Transaction Timing

1. Understand Settlement Times

  • ACH transactions take 1-3 business days to settle
  • Returns can occur up to 60 days after the transaction
  • Plan your fulfillment accordingly

2. Set Customer Expectations

Communicate clearly:

  • "Funds will be debited within 1-3 business days"
  • "Your order will ship after payment confirmation"
  • "You'll receive an email when the payment is complete"

3. Handle Business Days

ACH network operates on banking business days:

  • Processing: Monday - Friday (excluding federal holidays)
  • Cut-off times: Transactions submitted after cut-off process next business day
  • Weekend submissions: Process on the next business day

Webhook Implementation

1. Always Use Webhooks

Never rely on synchronous responses for ACH transaction status:

// Subscribe to these events
const achWebhookEvents = [
  'transaction.pending',   // Transaction submitted
  'transaction.approved',  // Transaction settled
  'transaction.declined',  // Transaction failed
  'transaction.errored'    // Processing error
];

2. Handle All Status Changes

function handleTransactionWebhook(event) {
  switch (event.type) {
    case 'transaction.approved':
      // Fulfill order, update records
      fulfillOrder(event.data.transactionId);
      break;

    case 'transaction.declined':
      // Notify customer, request new payment
      handleDecline(event.data);
      break;

    case 'transaction.errored':
      // Log error, alert support team
      handleError(event.data);
      break;

    case 'transaction.pending':
      // Update UI to show processing
      updateOrderStatus(event.data.transactionId, 'processing');
      break;
  }
}

3. Implement Retry Logic

For failed webhooks, implement exponential backoff:

Attempt 1: Immediate
Attempt 2: 1 minute
Attempt 3: 5 minutes
Attempt 4: 30 minutes
Attempt 5: 2 hours

Error Prevention

1. Pre-validate All Fields

Check before submission:

FieldValidation
Routing Number9 digits, valid check digit
Account Number4-17 digits
Account TypeValid enum value
Bank Country"USA"
First/Last NameRequired for personal accounts
Business NameRequired for corporate accounts

2. Handle Common Errors Gracefully

ErrorCustomer Message
Invalid routing number"Please check your routing number. It should be 9 digits and can be found on the bottom of your check."
Invalid account number"Please verify your account number. It should be between 4 and 17 digits."
ACH not supported"Bank account payments are not available for this merchant."

3. Provide Clear Instructions

Help customers find their bank details:

  • Include an image showing where to find routing/account numbers on a check
  • Explain the difference between checking and savings accounts
  • Offer alternative payment methods if ACH fails

Recurring Payments

1. Use Multi-Use Tokens

Tokenize bank accounts for recurring billing:

POST /v1/transactions?tokenize=true

2. Handle Failed Recurring Payments

Implement a retry strategy:

  1. Immediate retry: Try once more in case of temporary failure
  2. Delayed retry: Try again in 3-5 days
  3. Customer notification: Alert customer after 2 failures
  4. Grace period: Allow time for customer to update payment method
  5. Suspension: Suspend service after multiple failures

3. Keep Tokens Updated

  • Notify customers before token expiration
  • Provide easy way to update bank account information
  • Remove tokens when customers close accounts

Security Best Practices

1. Never Log Sensitive Data

// BAD - Don't log account numbers
console.log('Processing payment for account:', accountNumber);

// GOOD - Log only safe identifiers
console.log('Processing payment for transaction:', transactionId);

2. Use Tokenization

  • Never store raw bank account numbers
  • Use Preczn tokens for all storage and processing
  • Tokenize immediately upon collection

3. Validate Webhook Signatures

Always verify webhook signatures before processing:

function verifyWebhookSignature(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

Testing

1. Use Test Account Numbers

ScenarioAccountRouting
Approved123456789987654321
Declined000000000888888888
Error111111111999999999

2. Test All Scenarios

  • Successful transactions
  • Declined transactions
  • Network errors
  • Webhook delivery
  • Refund processing
  • Partial refunds

3. Test Edge Cases

  • Maximum amount transactions
  • Minimum amount transactions
  • International characters in names
  • Special characters in business names

Summary Checklist

Before going live, verify:

  • Routing number validation implemented
  • Account number validation implemented
  • Account type selection available
  • Customer authorization captured and stored
  • ACH mandate included (if using Braintree)
  • Webhooks configured and tested
  • Error handling implemented
  • Customer-facing error messages defined
  • Retry logic for recurring payments
  • Security measures in place
  • Test scenarios completed

Did this page help you?