View as Markdown

llms.txt

This guide isn't available right now

This instance couldn't load its guide catalog. The guide returns as soon as the catalog is readable again.

Back to the guides

No such guide

This instance publishes no guide under that address. It may have been renamed, or it may belong to a feature this installation hasn't enabled.

Back to the guides

That guide is part of the product documentation

This guide is written for someone operating WinkPG through its screens rather than integrating against it, so it lives in the application's own help section instead of here. Sign in to WinkPG and open Help to read it.

Back to the guides

Guides Payments

ACH payments

How a bank debit clears, where to take one, and how to read its status from submission through settlement or return.

An ACH payment is a debit instruction against a bank account, cleared through the banking network in batches. That single sentence explains most of what makes ACH feel different from a card: the outcome confirms over the following days rather than in the moment, and the clearing happens on the ACH processor's side. WinkPG tracks the whole journey for you and records every status change on the transaction as it arrives.

This guide covers where you can take an ACH payment, how to store a bank account for later use, and how to read an ACH transaction's status from submission through settlement or return.

How an ACH payment differs from a card payment

The most useful thing to absorb is that an ACH sale is one operation. No authorization step reserves funds, so you place no hold, capture nothing afterward, and make no separate settlement decision. You submit the debit, and the banking network clears it.

Card ACH
Approval An authorization places a hold at the issuer, answered in seconds No authorization construct; the debit is submitted for clearing
Steps Authorize, capture, settle One sale, then clearing
Outcome timing Approved in seconds, funds move at the next batch close Confirmed over the following days
Where the status comes from The WinkPG settlement batch The ACH processor's clearing feed
After it succeeds A refund creates a new linked transaction The receiving bank can still return the debit

Because clearing runs on the processor's side, an ACH transaction sits at a Pending settlement status while it's in flight, and a long-running Pending on an ACH payment is normal in a way it would not be on a card. ACH payments are also kept out of the card settlement pipeline entirely, so their absence from the open batch is expected and correct.

Before you take ACH payments

An active ACH-capable processor profile on the merchant is the one prerequisite. WinkPG builds each merchant's payment-method capability from its active processor profiles, so once an ACH-capable profile is in place the bank-account tender becomes available on the surfaces below, and the transaction API accepts ACH submissions for that merchant.

Hosted Payment Pages offer ACH when it's selected in the page's payment methods. Every page carries its own list of methods, and the public page renders only the tenders the merchant can actually process. Both halves have to line up: the merchant needs the capability, and the page needs ACH selected. If you enable ACH capability on a merchant after a page was already saved, open that page and re-save it with ACH selected so the page offers it. The page editors show a notice when a saved method selection was reconciled against the merchant's current capabilities, which is your prompt to re-select.

Take an ACH payment

From the Virtual Terminal

The Virtual Terminal's check entry collects the account details directly: Name On Check, Routing Number, Account Number, Check Number, and an Account Type of Checking or Savings.

Below the account fields, the Virtual Terminal captures how the debit was authorized. You pick one of two answers to "How was this debit authorized?":

  • Telephone (TEL), for an authorization the account holder gave during a call.
  • Signed form (PPD), for an authorization the account holder gave on a signed written form.

Each choice renders the exact attestation statement you are confirming, and a checkbox records that you obtained the authorization described. This evidence is required: the server rejects a Virtual Terminal ACH submission that arrives without it. The statement text is stored verbatim on the transaction alongside a hash of its wording, so the language that authorized a given debit stays reproducible. The obligations the statement describes stay with the merchant: audio-recording or confirming a telephone authorization in writing before settlement, and retaining a signed form for the required period.

One detail worth separating: the Standard Entry Class code sent to the processor comes from the merchant's ACH processor profile configuration. The choice above records how the account holder authorized the debit and is kept as evidence.

From a Hosted Payment Page

A page that offers ACH renders a bank-account tab with the same account fields, plus the NACHA WEB authorization statement the payer accepts before submitting. Internet-initiated debits are authorized under the WEB Standard Entry Class, and the statement is what carries that authorization.

WinkPG ships default authorization language and captures the rendered text verbatim with the transaction, together with a content hash so you can tell which version of the wording authorized a given debit. You can override the language per page. Three defaults ship, and the page renders whichever fits the session: one for a single debit, one for a recurring arrangement, and one for saving an account with no charge.

From the API

The transaction create request accepts bank-account details in place of card details, and the result is the same ACH sale the two screens above produce. The merchant capability gate and the field rules below apply identically, so an integration and an operator get the same answers.

What gets validated

One rule set backs every surface, so the Virtual Terminal, the Hosted Payment Page, and the API can't disagree about what a valid account looks like:

  • The routing number is 9 digits and must pass the ABA checksum.
  • The account number is 1 to 17 digits.
  • An account type (checking or savings) must be selected.

Save a bank account without charging it

You can store a bank account for future use without taking any money. Since ACH has no authorization construct, the save-only flow is best understood as verification plus vaulting rather than any kind of approval. WinkPG validates the account details internally against the rules above. On success it stores the account in the vault, so it can be charged later with the payer's authorization. Nothing is submitted to the ACH network, and the account is stored only if the verification passes.

Two places do this:

  • A save-only Hosted Payment Page session. The payer sees the storage authorization language, which states explicitly that saving the account doesn't debit it today and that any future debit needs its own authorization. An approved save publishes the HostedPaymentPage.AchSaved event, so your systems can pick up the stored account as soon as it exists.
  • The back office, when you add a bank account to a customer record by hand. The verification runs before the account is vaulted, so an account that fails validation is reported to you and not stored.

The ACH status lifecycle

An ACH transaction's settlement status is the field to watch. It starts at Pending, moves through whichever of the in-flight statuses the processor reports, and ends at one of the terminal statuses below. The in-flight statuses only ever move forward, and the processor may skip any of them, so a transaction can go straight from Pending to Settled or stop at every step along the way. The exception to "ends" is a late return, where a transaction that already reads Settled moves again to SettlementRolledBack, which the next section covers.

Stage Settlement status What it means
Submitted Pending The transaction was created and handed to the ACH processor, which now has it.
In flight Accepted The processor accepted the entry and queued it for the next origination window.
In flight Verifying The processor is verifying the account before origination.
In flight Originated The entry was originated to the ACH network and is on its way to the receiving bank.
In flight PartiallySettled Part of the amount has settled.
Cleared Settled The debit cleared. The money has moved.
Returned SettlementRolledBack The receiving bank returned the debit. The NACHA return code and reason are recorded on the transaction.
Voided Not Eligible The entry was voided on the processor's side before it was originated to the ACH network, so it was never presented.
Errored Failed The processor reported an error on the entry.

Treat any status other than Settled, SettlementRolledBack, Not Eligible and Failed as still in flight, rather than matching on Pending specifically: which in-flight statuses a given transaction reports depends on the processor.

Status updates arrive on a scheduled synchronization that runs several times a day, and a separate daily reconciliation sweep runs behind it as a safety net. Each change WinkPG picks up is applied to the transaction and published as a notification, so both the screen and your own systems see the same transition.

Returns

When a receiving bank refuses a debit, it returns it with a standard NACHA return code and a reason, and WinkPG records both on the transaction. The codes are the familiar ones: R01 for insufficient funds, R02 for an account closed, and others in the same series. Reading the code tells you whether a retry is worth attempting or whether you need new account details from the customer.

A return can arrive after a debit has already reported as settled. That's a late return, and it's a normal part of how ACH works rather than a fault. WinkPG moves the transaction to SettlementRolledBack when it happens and flags the notification so you can tell a late return apart from one that arrived before settlement. The practical consequence for fulfilment is worth stating plainly: treat an ACH settlement as durable rather than as the last word, and weigh how long you wait against the value of the order.

Watch ACH from your own systems

Two subscribable events cover ACH, and both are available at merchant scope:

  • Transaction.AchStatusChanged fires on every ACH settlement-status change. The payload carries the transaction and merchant identifiers, the previous status, the new status, and, when the change is a return, the NACHA return code and reason.
  • Transaction.Returned fires specifically on a bank return. The payload carries the resulting settlement status, the return code and reason, the effective entry date when the processor supplies one, and a flag saying whether the return arrived after settlement.

Both event types can be filtered on Return Code, so a subscription can target the returns you actually want to act on (dunning on an insufficient-funds return, for example, while routing an account-closed return somewhere else).

The card settlement events don't apply to ACH. ACH clears on the processor's side rather than through the card batch pipeline, so Transaction.Settled is a card signal and these two events are the ACH equivalent. An integration that wants to know when an ACH debit is good should subscribe to Transaction.AchStatusChanged rather than waiting on a settlement event that won't arrive.

One thing to expect on a newly connected merchant: notifications begin with activity that happens after onboarding. The first synchronization brings the merchant's existing ACH history up to date, so the transaction records are correct from day one. It does that without generating notifications for those historical rows, so your endpoint isn't flooded with events about payments that resolved long ago.

Test ACH in the sandbox

Three sandbox paths exist, and they answer different questions. Pick the one that matches what you're trying to prove.

The loopback simulator answers in the moment. A transaction carrying check data runs on the ACH rail, where the cents of the amount select a NACHA return code: an amount ending in .01 comes back as R01 for insufficient funds, .02 as R02 for an account closed, with the rest of the series mapped the same way. What you get is a synchronous decline carrying the return code and reason on the response itself. Nothing is submitted anywhere, and no status changes later. You still receive the ordinary decline notification, Transaction.Declined, because the sale was refused. What you never receive is either ACH lifecycle event: a loopback return raises neither Transaction.AchStatusChanged nor Transaction.Returned, because no settlement status ever moves. Use this path to prove your code reads a return code off a declined response. To prove your webhook endpoint handles a return, drive an approved loopback sale with the ACH status endpoint below.

The ACH status endpoint moves a sandbox sale on demand. Send an ACH sale at an amount the simulator approves, then call POST /api/transactions/{id}/sandbox/ach-status to move it to whichever settlement status you want to test: any of the in-flight statuses, or any terminal outcome. It publishes the real Transaction.AchStatusChanged and Transaction.Returned events, with the same body the VeriCheck path produces, so this is how you prove an ACH webhook without waiting days. Sandbox only: it refuses a live key, and it refuses any transaction the simulator didn't answer.

The VeriCheck sandbox runs the real multi-day lifecycle. When your sandbox merchant's ACH processor profile points at VeriCheck's sandbox, a payment you originate there is accepted first and then settles or is returned on a later day, the same way it would in production. Each transition flows through the same status synchronization and publishes the same events, so this is the path that exercises an ACH webhook end to end.

Drive a sandbox sale to any ACH status

Send an approved ACH sale first. An amount whose cents aren't in the return table is approved, so 10.00 works; the transaction comes back with a settlement status of Pending, which is where a real ACH debit starts.

Then move it:

POST /api/transactions/{transactionId}/sandbox/ach-status
Content-Type: application/json

{
  "targetStatus": "SettlementRolledBack",
  "nachaReturnCode": "R01",
  "nachaReturnReason": "Insufficient funds"
}

targetStatus takes one of eight values, and each is the literal name your events will carry in NewStatus. Four are the in-flight statuses a debit passes through, in the order it passes through them:

Target status What it means Return detail
Accepted The processor accepted the entry and queued it for origination. Omit both fields.
Verifying The processor is verifying the account before origination. Omit both fields.
Originated The entry was originated to the ACH network. Omit both fields.
PartiallySettled Part of the amount has settled. Omit both fields.

And four are the terminal outcomes:

Target status What it means Return detail
SettlementSucceeded The debit cleared. Omit both fields.
SettlementRolledBack The receiving bank returned the debit. Both fields required.
NotEligible The entry was voided before it reached the ACH network. Omit both fields.
SettlementFailed The processor reported an error on the entry. Omit both fields.

The in-flight statuses only move forward, exactly as they do on the live rail. From Pending you can move to any of the four, and from each of them to any later one or to any terminal outcome; you can skip steps, because the processor does. What you can't do is move backwards: a transaction at Originated refuses Accepted and Verifying, and the refusal names both statuses. Walk one transaction through Accepted, Originated and SettlementSucceeded to make your endpoint receive each in-flight value on its way to a settlement, and expect PreviousStatus on each event to carry the status the transaction held before that step rather than always Pending.

Chain the calls to make a late return. Move a transaction to SettlementSucceeded, then move the same transaction to SettlementRolledBack. The second call flags the return as late, because the debit had already settled. An early return needs its own transaction: send a second sale and move it straight to SettlementRolledBack from Pending.

The response tells you what moved and which events went out:

{
  "transactionId": "8d1f...",
  "previousStatus": "SettlementSucceeded",
  "newStatus": "SettlementRolledBack",
  "isLateReturn": true,
  "achStatusChangedPublished": true,
  "returnedPublished": true
}

Repeating a step delivers nothing the second time. Each notification carries a deterministic id, and a repeat of the same id is dropped per destination. Moving one transaction to SettlementSucceeded twice therefore fires one webhook, not two, and a returned transaction is final. Use a fresh transaction to run the same step again.

A few things it refuses, all on purpose. A key stamped for the live world, or one carrying no environment stamp. A transaction any real processor handled: the endpoint fabricates an outcome, so it may only ever touch one the simulator answered. A card transaction, a refused sale (nothing was accepted, so there's nothing to settle or return), and a move the ACH lifecycle doesn't allow, such as settling a transaction that's already been returned or moving one back to an earlier in-flight status.

VeriCheck sandbox triggers

VeriCheck's sandbox selects the outcome from the payment description, or from the routing number for the error case:

To produce Send What happens
A return before settlement A payment description of Day2R01 The debit is returned on day 2 with R01 for insufficient funds, before it ever settles. The transaction moves to SettlementRolledBack and isn't flagged as a late return.
A late return A payment description of Day5R10 The debit settles, then is returned on day 5 with R10 for customer advises unauthorized. The transaction moves from Settled to SettlementRolledBack, and the return is flagged as late.
A settlement error A routing number of 770000000 VeriCheck reports an error on the entry rather than accepting it, and the result is recorded as a settlement failure rather than a return.

A settled payment needs no trigger. Originate any payment that isn't one of the three above and it clears normally, which is how you provoke the success case.

Timing decides whether you see anything. Create the payment before 6:00 PM Eastern so it processes in that day's batch window. Returns become visible after 10:00 AM Eastern on the day they're simulated, which is day 2 for Day2R01 and day 5 for Day5R10. A payment originated after the cutoff shifts the whole schedule by a day.

Events arrive on the synchronization, not on the transition. WinkPG reads ACH status from the processor on a schedule that runs several times a day, so a return that materializes at 10:00 AM Eastern reaches your endpoint at the next sync rather than the moment the processor recorded it. Plan for a lag of hours, and read the event's own timestamp as when the change happened.

Know which events to expect, and match on the payload value. Every status change raises Transaction.AchStatusChanged, whose NewStatus field carries the settlement status by its literal name: an in-flight value such as Accepted or Originated as the processor reports progress, then SettlementSucceeded for a debit that cleared or SettlementRolledBack for one that was returned. PreviousStatus carries the status the transaction held before that change: Pending on the first event, and the previous in-flight value after that. The screens show friendlier labels than the payload does, so match your code on the payload value rather than on what a transaction page displays. Transaction.Settled is a card signal and never arrives for an ACH transaction, which is by design rather than a gap. A return raises Transaction.AchStatusChanged and Transaction.Returned, both carrying the NACHA return code.

Test your endpoint without waiting

Waiting days to discover that your webhook URL was wrong is a poor use of a week. Both ACH event types can be delivered to your endpoint on demand, carrying the same body a real event carries:

  • Send sample in the developer portal delivers the event catalog's sample payload for a chosen event type to a saved webhook destination.
  • winkpg-integrator trigger Transaction.AchStatusChanged does the same from the command line. winkpg-integrator trigger --list shows the event types your key can see.

A sample is marked with a header rather than a payload field, so a receiver that ignores the header processes it exactly as it would the real thing. Prove your signature verification, routing, and deduplication this way first, then use the ACH status endpoint to prove the lifecycle. Reach for the VeriCheck sandbox when you want the real multi-day timing as well as the events.

See also

Reconnecting to the server

Could not reconnect

This session has ended

Attempt 1

Your work on this page is still here. Retrying keeps it; reloading starts the page again.

The server no longer holds this page's state, so it has to be loaded again.