Taming R29 ACH returns with policy gates and deterministic retries

“Anyone else seeing a spike in ACH returns with R29? We just got 47 back in one batch.”

That was our payments ops Slack on a Monday morning a few weeks ago, right after we pushed a same-day ACH debit run for a chunk of B2B customers (mostly invoice collections). The bank portal showed the returns coming back as R29 (Corporate Customer Advises Not Authorized), which is basically the worst kind of ambiguous: it might be a legit customer dispute, it might be a formatting/SEC-code mismatch, or it might be a customer’s treasury team tightening filters and rejecting anything that doesn’t match their internal allowlist.

The immediate impact was ugly. We had cash forecasting built around that batch landing, and suddenly we were staring at a six-figure hole in expected receipts for the week. On top of that, our support team started getting “why did you try to pull funds?” emails from customers, and our risk/compliance folks were (rightfully) nervous about us blindly retrying anything that could be interpreted as unauthorized.

What was broken in our process wasn’t “we don’t know what R29 means” — it was that we had no governed, repeatable way to decide what happens next. Historically it was a messy combo of:

  • someone exporting the return report from the bank portal
  • someone else matching it to invoices in the ERP
  • a manager deciding (in Slack) whether we retry, switch to wire, or pause collections
  • and then an analyst manually updating customer status + sending emails

We tried letting an LLM “triage” the returns once, and it immediately reinforced why AI alone can’t be trusted to execute financial actions. It was great at summarizing patterns, but it also confidently suggested retrying a subset that absolutely should have been held. In payments, “confident” is not the same as “correct,” and the blast radius is real.

We ended up putting Autom Mate in the middle as the execution layer so we could keep the decisioning assistive, but make the actions deterministic and auditable.

Behind the scenes, the flow is pretty straightforward but it changed everything:

  • Bank return file lands in a monitored mailbox / SFTP drop.
  • Autom Mate picks it up and normalizes it (we map return codes + trace numbers into a consistent schema).
  • If return code is R29, Autom Mate automatically opens a ticket in our ops queue (we use an ITSM-style queue for payments incidents) and posts a summary into a dedicated Teams channel with the customer list + total $ exposure.
  • Then it routes the case through a policy gate:
    • if the customer is on our “no-retry without written confirmation” list, it auto-places a collections hold
    • if the amount is above our threshold, it requires dual approval (collections lead + risk)
    • if it’s below threshold and the customer has a clean history, it can propose a retry window but still waits for an explicit approval
  • Only after approvals does Autom Mate execute the next step via a controlled REST/HTTP action (either creating the retry instruction in our payments system or updating the customer’s collection status + notifying support).
  • Every step (who approved, what rule fired, what payload was sent) is logged so we can answer the inevitable “why did we retry this?” question without reconstructing Slack threads.

The biggest win wasn’t just speed (though we went from ~half a day of spreadsheet chaos to ~30 minutes to get to a clean decision queue). It was that we stopped doing the two dangerous things we were previously doing under pressure:

  1. retrying returns because “it probably failed for a dumb reason”
  2. freezing customers because “we don’t know what happened”

Now we’re consistent: R29 always becomes a governed workflow with the right holds, approvals, and a paper trail.

Curious how others handle R29 specifically in B2B collections — do you treat it as an automatic hard stop, or do you have a safe retry policy that doesn’t create compliance headaches?