> ## Documentation Index
> Fetch the complete documentation index at: https://docs.spritz.finance/llms.txt
> Use this file to discover all available pages before exploring further.

# ACH returns

> Understand, track, and reconcile returned ACH debits.

ACH is not a real-time, final network. An ACH debit can be **returned** after it
settles: the account holder's bank sends the money back, sometimes days or weeks later,
for reasons like insufficient funds or a claim that the debit wasn't authorized. Because
a [linked bank on-ramp](/guides/use-cases/linked-bank-onramp) delivers crypto against an
ACH pull, a return can mean funds you already moved need to be reconciled. This page
explains how returns work and how Spritz surfaces them.

## When returns arrive

Returns land within windows set by the ACH rules, which vary by reason:

* **Administrative** returns (wrong or closed account, invalid number) usually arrive
  within about two banking days.
* **Unauthorized** returns (the account holder disputes the debit) can arrive up to 60
  calendar days after settlement.

Spritz groups returns into three buckets, `unauthorized`, `administrative`, and
`other`, so you can treat them differently.

## How Spritz surfaces a return

When a debit is returned:

* The deposit's `status` and `debitStatus` become `returned`, and the deposit carries a
  `returnCode` and `returnReason`.
* The funding source becomes `disabled`, with `statusReason: "returned"` and
  `permanent: true`. Read the ACH code from the deposit or return record; private
  source-disablement inputs are not returned.
* The `achDebitReturn.created` [webhook](/guides/webhooks) fires.
  `achDebitReturn.updated` can fire as details change. Delivery order is not guaranteed.

<Warning>
  A deposit can move through `completed` before its bank later returns the debit. Keep
  processing deposit and return webhooks after completion. The aggregate deposit can
  also show `failed` or `refunded` because of its crypto-release path while its
  `debitStatus` is already `settled`; a later ACH return still moves it to `returned`.
  Use `debitStatus` and `releaseStatus` to explain the current state. None of these
  aggregate states makes a settled ACH debit permanently immune to a later return.
</Warning>

You can also list returns directly:

```bash theme={null}
# Add HMAC headers as documented in Authentication. Integrator endpoints have no user Authorization.
curl "https://platform.spritz.finance/v1/integrator/ach-debit/returns?returnBucket=unauthorized"
```

Fetch one return by the public `dr_...` ID from its webhook:

```bash theme={null}
# Add HMAC headers as documented in Authentication. Integrator endpoints have no user Authorization.
curl "https://platform.spritz.finance/v1/integrator/ach-debit/returns/dr_01JV7Q8M4Y8K6N2Z5P3R1T9W0X"
```

Each return record ties the return back to its deposit and quantifies your exposure:

| Field                                         | Meaning                                                                                                       |
| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `depositId`, `onRampId`, `userId`, `sourceId` | What and who the return relates to                                                                            |
| `returnCode`, `returnReason`                  | The ACH return reason (see below)                                                                             |
| `reportingBucket`                             | `unauthorized`, `administrative`, or `other`                                                                  |
| `occurredAt`                                  | When the return was received                                                                                  |
| `cryptoStateAtReturn`                         | Whether the crypto had already left: `not_released`, `in_flight`, `partially_confirmed`, or `fully_confirmed` |
| `lossAmountUsd`, `atRiskAmountUsd`            | Realized loss and still-at-risk amount                                                                        |
| `sourceAction`                                | The action applied to the funding source; currently `disabled`                                                |
| `userAction`                                  | What happened to the user's ACH access: `none`, `review_required`, `restricted`, or `disabled`                |

`cryptoStateAtReturn` is the key exposure signal: a return before the crypto is released
costs you nothing, while a return after `fully_confirmed` delivery is a realized loss.

Store `returnCode` for reconciliation and support, but branch the user experience only
on `userAction`. Treat `returnReason` as supporting text for logs and support tooling,
not as a stable machine-readable value.

## User and frontend handling

Every return disables the bank account that produced it. Cache invalidation can make a
funding-source read briefly lag the returned deposit, so reconcile it with bounded
polling after either `achDebitReturn.*` webhook. Never offer the source again once it is
disabled. `userAction` is the authoritative result for the user's wider ACH access.
Spritz's escalation policy is intentionally not public; do not derive or reproduce it
from return codes or return counts.

Use direct, action-oriented copy:

| `userAction`      | Suggested message                                                                 |
| ----------------- | --------------------------------------------------------------------------------- |
| `none`            | “This bank account can no longer be used. Link another bank account to continue.” |
| `review_required` | “ACH deposits are paused while we review it. Contact support for help.”           |
| `restricted`      | “ACH deposits are temporarily restricted. Contact support for help.”              |
| `disabled`        | “ACH deposits are disabled. Contact support for help.”                            |

Do not show a retry button for the disabled source. For `none`, offer another bank. For
every other action, show the support path.

## Common return codes

ACH return codes follow the NACHA standard (`R01`, `R02`, and so on). The ones you'll
see most:

| Code  | Reason                                                              |
| ----- | ------------------------------------------------------------------- |
| `R01` | Insufficient funds                                                  |
| `R02` | Account closed                                                      |
| `R03` | No account or unable to locate account                              |
| `R04` | Invalid account number                                              |
| `R05` | Unauthorized debit to a consumer account using a corporate SEC code |
| `R07` | Authorization revoked by the customer                               |
| `R08` | Payment stopped                                                     |
| `R10` | Customer advises the debit is unauthorized                          |
| `R16` | Account frozen                                                      |
| `R20` | Non-transaction account                                             |
| `R29` | Corporate customer advises not authorized                           |

This is a subset. The full set of codes is defined by NACHA, and `returnReason` carries
the human-readable text for whatever code applies.

## Preventing avoidable returns

Only debit accounts the user linked and clearly authorized. Treat a blocked deposit as
final for that attempt: do not vary amounts or retry in a loop. Reconcile every return
promptly and follow `userAction`; Spritz may pause a source, user, or integration before
any new money moves.

## Reconciling

<Steps>
  <Step title="Listen for the webhook">
    Subscribe to `achDebitReturn.created` and `achDebitReturn.updated` so you learn about
    returns and synchronize current state. Subscribe to `achDebit.returned` separately
    when your integration sends push notifications. Never infer a return push from a
    generic return update.
  </Step>

  <Step title="Persist the return">
    Key your record by the return `id`. Fetch
    `GET /v1/integrator/ach-debit/returns/{id}` for authoritative state whenever either
    event arrives. Reconcile the paginated `GET /v1/integrator/ach-debit/returns` feed
    after outages or missed deliveries.
  </Step>

  <Step title="Assess exposure">
    Use `cryptoStateAtReturn` and `lossAmountUsd` to decide whether the return is a real
    loss or was caught before delivery.
  </Step>

  <Step title="Update the user experience">
    Remove the disabled source from the payment picker. Branch on `userAction` to offer a
    different bank or the support path.
  </Step>
</Steps>
