ACH Best Practices

Guidelines for maximizing ACH transaction success rates

ACH Best Practices

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