Skip to content

Accept an ACH payment

An ACH debit pulls funds from a customer's checking or savings account. It costs less than a card payment and suits large or recurring amounts, but it settles more slowly and it can come back days later as a return.

ACH runs through VeriCheck. Card transactions run through TSYS. A merchant account is configured for one or the other, so the payment method and the credentials have to agree.

Before you start

  • Your Api-Key and Client-Id — see Authentication
  • A processor integration configured for ACH
  • The customer's authorization to debit the account, in a form the SEC code below describes

Send the debit

POST /v1/transactions with type set to SALE and a bank account as the payment method.

ACH asks for more required fields than a card does. All six of these must be present:

FieldNotes
payment_method.typeACH
payment_method.routing_numberExactly 9 digits
payment_method.account_number4 to 20 digits
payment_method.account_typeCHECKING or SAVINGS
payment_method.first_nameRequired, unlike on a card
payment_method.last_nameRequired, unlike on a card

Two optional fields matter more than they look:

  • standard_entry_class defaults to WEB. It declares how the customer authorized the debit, and the right value is a compliance question, not a preference. See the table below.
  • description is at most 30 characters. Only the first 10 reach the customer's bank statement. Left blank, it falls back to the merchant name. Put something recognizable in the first 10 characters or expect disputes.

Choosing a SEC code

The Standard Entry Class code records how you obtained authorization. Send the one that matches what actually happened.

CodeUse it for
WEBConsumer authorized over the internet. The default
PPDConsumer authorized in writing
TELConsumer authorized by telephone
CCDBusiness-to-business
POPCheck converted at the point of sale
BOCCheck converted after the sale

ACH takes only SALE and PAYOUT

A bank account accepts type of SALE to pull funds in, and PAYOUT to send them out. There is no ACH authorization, so type: AUTH with a bank account returns 400 with INVALID_ACTION and the message "Payment method not supported for this transaction type".

That also means the capture flow does not apply to ACH. An ACH sale is a single step.

Approved is not final

This is the part that catches card-shaped integrations.

A card decline arrives in the response. An ACH return arrives days later, long after you saw APPROVED, and often after the transaction has already settled. Insufficient funds, a closed account, or a customer revoking authorization all surface this way.

So an approved ACH transaction is provisional. Do not treat it as final for anything expensive to reverse — shipping goods, crediting an account balance — until you have given returns time to arrive.

Four fields on the transaction carry it:

FieldBehavior
is_returnedtrue once returned. Always present. Always false on a card
returned_atWhen the processor sent the return. Always present, null if none
return_codeThe three-character NACHA code. Omitted entirely, not null, when there is no return
return_reasonThe same in words. Also omitted rather than null

Two consequences for your code:

  • Read is_returned alongside is_settled, not instead of it. A returned transaction may well have settled first.
  • return_code and return_reason are absent, not null. Code that reads body["return_code"] without checking for the key will throw on every transaction that was never returned — which is nearly all of them.

There are no webhooks, so poll GET /v1/transactions to find returns.

Next steps