Skip to main content
In production, an ACH entry moves through its lifecycle on its own, driven by Fed processing windows and the receiving bank. In sandbox, nothing is submitted to the Fed, so nothing moves on its own. Every entry, outgoing or incoming, starts in scheduled and stays there until you advance it. Each advance names the status you want next and moves the entry exactly one step. It returns the full updated entry and fires the webhook production fires for that status. Before testing, review ACH Statuses so you know which statuses to expect at each step.

Simulation endpoints

These endpoints exist only in sandbox. In production they return 404. To return an incoming entry, dishonor a return, contest a dishonor, or cancel an entry, use the standard endpoints, as in production. See Returns, reversals, and exceptions.

Advancing an entry

POST /v2/simulate/ach/{ach_id}/advance takes a simulation object with one required field, next_status. One entry and one transition per call. You can’t skip a status.
Lead checks only that the transition is allowed from the entry’s current status for its direction. It doesn’t re-run production validations. A successful call returns 200 with the full entry.

Allowed transitions

The table lists every transition you can request. The three pending_ statuses are entered through the return, dishonor, and contest actions, not through /advance. You then advance the entry out of them.

Errors

Every error uses the format described on Errors. A rejected advance leaves the entry unchanged and sends no webhook.
/advance takes no Idempotency-Key. Repeating a call that names the current status gets object_status_unchanged. Repeating one that names a now-allowed status is a new transition.

Test an outgoing entry

Follow these steps:
  1. Create the entry with POST /v2/ach. The response is the entry in scheduled. Synchronous create checks run exactly as in production: a request against an inactive account number fails with 422 and creates nothing.
  2. Advance to processing. No webhook.
  3. Advance to submitted. ach.submitted fires.
  4. Advance to posted. ach.posted fires.
Advancing from processing straight to posted returns the status_transition_not_permitted error from the Errors table on this page.

Test an incoming entry

POST /v2/simulate/ach/incoming_ach creates an entry as if another bank originated it. The request takes account_number_id, sec_code, transaction_type, amount, currency_code, statement_descriptor, sender_name, sender_company_id, and sender_routing_number. The response is the full entry in scheduled, and ach.scheduled fires. Advance it to posted to complete it.

Test returns

You can test returns three ways:
  • You return an incoming entry. Call POST /v2/ach/{ach_id}/return with a return_code. The entry moves to pending_return. Advance it to returned.
  • The counterparty returns your outgoing entry. Call POST /v2/simulate/ach/{ach_id}/incoming_return with return_code and, optionally, details. The entry moves to pending_return, and the text you sent comes back as returns[].details. Advance it to returned.
  • Dishonor and contest. Use POST /v2/ach/{ach_id}/return with a dishonor or contest code on the original entry, then advance out of the pending status it enters.

What is and isn’t real

Keep these differences from production in mind:
  • No funds move, and Fed processing windows don’t apply. You control timing entirely through /advance.
  • An entry can’t be steered into under_review, and a return, dishonor, or contest can’t be rejected, in sandbox.
  • Statuses, the returns[] block, and webhooks match production. A webhook fires only where production fires one, and never twice for one transition.
  • v1 sandbox behavior is unchanged. Entries created on /v1/ach keep advancing the v1 way.