Skip to main content

ACH Payments

ACH (Automated Clearing House) transfers move funds directly between bank accounts. They typically cost less than card transactions but are not instant — settlement timing is provider-dependent and may take anywhere from same-day to several business days.

Provider ACH support
Nuvei

ACH is fully supported. Use TRANSACTION_PROVIDER.NUVEI (or your Nuvei merchantTransactionProviderId) when initiating.

Stripe

ACH is not supported via AccruPay's Stripe integration. Use Nuvei if you need ACH.


Initiate an ACH payment

ACH is initiated entirely from your server. There is no frontend form step.

import AccruPay, {
CURRENCY,
COUNTRY_ISO_2,
ENTITY_TYPE,
TRANSACTION_ACH_SECCODE,
} from '@accrupay/node';

const sdk = new AccruPay({
apiSecret: process.env.ACCRUPAY_API_SECRET!,
});

const transaction = await sdk.transactions.payments.ach.initiate({
merchantTransactionProviderId: process.env.ACCRUPAY_PROVIDER_ID,
data: {
merchantInternalTransactionCode: 'ach-txn-001',
amount: 50000n, // $500.00
currency: CURRENCY.USD,
storePaymentMethod: false,
customer: {
merchantInternalCustomerCode: 'customer-123',
},
billing: {
billingFirstName: 'Jane',
billingLastName: 'Smith',
billingEmail: 'jane@example.com',
billingAddressCountry: COUNTRY_ISO_2.US,
},
ach: {
accountNumber: '123456789012',
routingNumber: '021000021',
secCode: TRANSACTION_ACH_SECCODE.WEB,
entityType: ENTITY_TYPE.INDIVIDUAL,
},
},
});

console.log(transaction.id, transaction.status);
BigInt amounts

AccruPay amounts are bigint in the smallest currency unit (cents for USD). 50000n = $500.00.


ACH data fields

The ach object inside data carries the bank account details:

FieldTypeRequiredDescription
accountNumberstringYesCustomer's bank account number
routingNumberstringYesABA routing number of the customer's bank
secCodeTRANSACTION_ACH_SECCODENoStandard Entry Class code — defaults to provider behavior if omitted
entityTypeENTITY_TYPENoWhether the account holder is an individual or a business

SEC codes

The SEC code classifies how the transaction was authorized. Use the code that matches how you collected the customer's banking details:

CodeValueUse case
CCDBusiness-to-businessCorporate credit or debit — business account-to-account transfers
WEBInternet-initiatedCustomer authorized via an online form or web application
TELTelephone-initiatedCustomer authorized verbally over the phone
UNKNOWNUnknown / unspecifiedAuthorization method is not known — use only when no other code applies

Settlement timing

ACH is an asynchronous payment rail. When you call ach.initiate():

  • The transaction is submitted to the provider immediately.
  • The initial status will often be PENDING — not SUCCEEDED.
  • Settlement timing is provider-dependent — do not fulfill orders based on PENDING status.
  • The bank can return the transaction (e.g. insufficient funds, invalid account) even after the initial submission succeeds.
ACH settlement windows
Nuvei

1–5 business days depending on the SEC code (CCD, WEB, TEL) and the receiving bank's processing schedule. Standard ACH Next-Day and Same-Day options may be available depending on your Nuvei account configuration.

Other providers

ACH settlement timing varies by PSP and ACH network tier (Same-Day, Next-Day, Standard). Consult your provider's documentation for exact settlement SLAs and return window timelines.

import { TRANSACTION_STATUS } from '@accrupay/node';

switch (transaction.status) {
case TRANSACTION_STATUS.SUCCEEDED:
// Funds confirmed
break;

case TRANSACTION_STATUS.PENDING:
case TRANSACTION_STATUS.STARTED:
// Expected for ACH — wait for a webhook or poll later
await markOrderPending(transaction);
break;

case TRANSACTION_STATUS.FAILED:
case TRANSACTION_STATUS.DECLINED:
// Bank rejected the transfer
throw new Error('ACH transfer failed');
}
Do not fulfill immediately

Unlike card payments, an ACH transaction with status PENDING has not settled. Do not release goods or services until the status reaches SUCCEEDED or you receive a confirmation webhook.


Storing the payment method

Set storePaymentMethod: true to save the bank account for future ACH debits:

const transaction = await sdk.transactions.payments.ach.initiate({
merchantTransactionProviderId: process.env.ACCRUPAY_PROVIDER_ID,
data: {
// ...
storePaymentMethod: true,
ach: {
accountNumber: '123456789012',
routingNumber: '021000021',
secCode: TRANSACTION_ACH_SECCODE.WEB,
},
},
});

The stored payment method ID will be available on the resulting transaction once processing completes.


Testing

Use your provider's designated sandbox routing and account numbers to test ACH flows in the sandbox environment. Most providers publish specific test values that trigger different outcomes (success, return, insufficient funds). Refer to your provider's sandbox documentation for the correct values.


Next steps