# 

## What is an ACH return?

An ACH return occurs when the receiving bank rejects or reverses an ACH transaction. Unlike card chargebacks, ACH returns follow NACHA rules and have specific return reason codes.

## Common return codes

| Code | Name | Description | Timing |
|  --- | --- | --- | --- |
| R01 | Insufficient funds | Not enough money in the account | 2 business days |
| R02 | Account closed | The account has been closed | 2 business days |
| R03 | No account / unable to locate | Account number is invalid | 2 business days |
| R04 | Invalid account number | Account number structure is wrong | 2 business days |
| R08 | Payment stopped | Customer requested a stop payment | 2 business days |
| R10 | Customer advises not authorized | Customer claims they did not authorize the debit | 60 calendar days |
| R29 | Corporate customer advises not authorized | Corporate account disputes authorization | 2 business days |


## Return timeline

```mermaid
---
config:
  theme: 'neutral'
---
gantt
    title ACH return windows
    dateFormat  YYYY-MM-DD
    axisFormat  %a

    section Debit (payment)
    Transaction submitted       :a1, 2026-03-25, 1d
    Standard return window      :a2, after a1, 2d
    Unauthorized return (R10)   :a3, 2026-03-25, 60d

    section Credit (payout)
    Transaction submitted       :b1, 2026-03-25, 1d
    Standard return window      :b2, after b1, 2d
```

## Detecting returns

When an ACH transaction is returned, the `transaction_state` changes to `failed`. Check the transaction periodically or use batch status polling.

### Poll the transaction

```shell
curl https://api.dev.paradisegateway.net/v1/transactions/TRANSACTION-01KEW32V6YNV11T33XGDR7TGWC \
  -H "APIKEY: YOUR-API-KEY"
```

A returned transaction looks like:

```json
{
  "id": "TRANSACTION-01KEW32V6YNV11T33XGDR7TGWC",
  "type": "sale",
  "amount": 250000,
  "transaction_state": "failed",
  "response_status": "declined",
  "response_message": "ACH return: R01 - Insufficient funds",
  "correlation_id": "e5f6a7b8-c9d0-1234-ef01-23456789abcd"
}
```

## Handling returns in your application

```mermaid
---
config:
  theme: 'neutral'
---
flowchart TD
    A[ACH transaction submitted] --> B[Poll transaction state]
    B --> C{transaction_state?}
    C -->|completed / settled| D[Payment successful]
    C -->|failed| E{Return code?}
    E -->|R01 Insufficient funds| F[Retry in a few days or contact customer]
    E -->|R02/R03/R04 Bad account| G[Request updated bank details]
    E -->|R08 Stop payment| H[Contact customer]
    E -->|R10 Unauthorized| I[Do not retry — investigate]
```

Python
```python
import time, requests, os

API_KEY = os.environ["PARADISE_API_KEY"]
BASE = "https://api.dev.paradisegateway.net"

def monitor_ach_transaction(txn_id, max_checks=10, interval=3600):
    """Poll an ACH transaction until it settles or fails."""
    for _ in range(max_checks):
        resp = requests.get(
            f"{BASE}/v1/transactions/{txn_id}",
            headers={"APIKEY": API_KEY},
        )
        txn = resp.json()
        state = txn["transaction_state"]

        if state in ("completed", "settled"):
            return {"status": "success", "transaction": txn}

        if state == "failed":
            return {
                "status": "returned",
                "message": txn.get("response_message", "ACH return"),
                "transaction": txn,
            }

        time.sleep(interval)

    return {"status": "timeout", "message": "Transaction still pending"}
```

JavaScript
```javascript
const API_KEY = process.env.PARADISE_API_KEY;
const BASE = "https://api.dev.paradisegateway.net";

async function monitorAchTransaction(txnId, maxChecks = 10, intervalMs = 3600000) {
  for (let i = 0; i < maxChecks; i++) {
    const resp = await fetch(`${BASE}/v1/transactions/${txnId}`, {
      headers: { "APIKEY": API_KEY },
    });
    const txn = await resp.json();

    if (["completed", "settled"].includes(txn.transaction_state)) {
      return { status: "success", transaction: txn };
    }
    if (txn.transaction_state === "failed") {
      return {
        status: "returned",
        message: txn.response_message || "ACH return",
        transaction: txn,
      };
    }
    await new Promise((r) => setTimeout(r, intervalMs));
  }
  return { status: "timeout", message: "Transaction still pending" };
}
```

## What to do after a return

| Return code | Action |
|  --- | --- |
| R01 (Insufficient funds) | Wait a few days and retry, or contact the customer for an alternative payment method |
| R02/R03/R04 (Bad account) | Request updated bank details; delete the invalid token |
| R08 (Stop payment) | Contact the customer to resolve the dispute |
| R10 (Unauthorized) | Do not retry — investigate the authorization. This may indicate fraud |


## Preventing returns

* **Verify bank accounts** — use micro-deposit verification before processing large debits
* **Get authorization** — for WEB entries, ensure you have the customer's online consent
* **Validate routing numbers** — check the routing number format before tokenizing
* **Communicate clearly** — tell customers when debits will occur and for how much


## Next steps

* [ACH for invoices](/docs/tutorials/payment-cookbook/ach-for-invoices) — collect payments via ACH
* [ACH payouts](/docs/tutorials/payment-cookbook/ach-payouts) — send funds to vendors
* [Handle payment failures](/docs/tutorials/payment-cookbook/handle-payment-failures) — broader failure patterns
* [About ACH payments](/docs/concepts/transactions/ach-accounts)