Simulate an ACH return
Send an ACH sale at an amount the sandbox returns, and read the NACHA return code off the response.
Signed in, you can run this blueprint against your own sandbox merchant one call at a time, with the values from each call threaded into the next. Sign in to run it.
2 steps, 1 API callACHPaymentsTransactions
Samples use {{API_KEY}} for your API key and {{BASE_URL}} for this instance's API address. Anything else in double braces is a value an earlier step gave you.
Reproduce it in the sandbox
1. Send the ACH sale at the returning amount
API call
POST /api/transactions
A transaction carrying check data runs on the ACH rail, where the cents of the amount select the return. The account and routing numbers below are test values that reach nothing.
Values this step gives you
curl -X POST "{{BASE_URL}}/api/transactions" \
-H "api-key: {{API_KEY}}" \
-H "Content-Type: application/json" \
-d '{
"transactionType": "Sale",
"checkData": {
"nameOnCheck": "Jane Doe",
"routingNumber": "021000021",
"accountNumber": "1234567890",
"accountType": "Checking",
"secCode": "Ppd"
},
"invoiceData": {
"amounts": { "base": 25.01, "total": 25.01 }
}
}'using var http = new HttpClient { BaseAddress = new Uri("{{BASE_URL}}") };
http.DefaultRequestHeaders.Add("api-key", "{{API_KEY}}");
var response = await http.PostAsJsonAsync("/api/transactions", new
{
transactionType = "Sale",
checkData = new
{
nameOnCheck = "Jane Doe",
routingNumber = "021000021",
accountNumber = "1234567890",
accountType = "Checking",
secCode = "Ppd"
},
invoiceData = new
{
amounts = new { @base = 25.01m, total = 25.01m }
}
});
var result = await response.Content.ReadFromJsonAsync<JsonElement>();
var returnCode = result.GetProperty("responseData").GetProperty("nachaReturnCode").GetString();2. Read the outcome off the response
On your side
The sandbox answers with the result code "Decline" and the message "ACH Returned (R01)" in the response body. The platform files that result under the "Declined" outcome. The response carries the NACHA return code R01 (Insufficient Funds). On the live rail a return arrives days after the debit was accepted, so your integration has to be able to leave and re-enter an accepted-then-returned state. The sandbox collapses that wait to a single call. What this particular call doesn't produce is a notification: the sale was refused, so no settlement status ever moves. The ordinary decline notification fires; the ACH status-changed and returned events don't. Read the NACHA code off the response here. Then, to prove your endpoint handles a return, send a sale at an approving amount and move it with POST /api/transactions/{id}/sandbox/ach-status, which publishes both ACH events on demand. Chain a settle and then a return on one transaction and the return is flagged late, which is the case worth proving.
Values this step gives you
What this step answers with
Abridged to the properties this step depends on. A real response carries more.
{
"id": "9f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
"merchantId": "3a7b1c9d-2e4f-4a6b-8c8d-9e0f1a2b3c4d",
"resultCode": "Decline",
"responseData": {
"resultCode": "Decline",
"resultMessage": "ACH Returned (R01)",
"nachaReturnCode": "R01",
"nachaReturnReason": "Insufficient Funds"
}
}