View as Markdown

llms.txt

No such blueprint

This instance publishes no blueprint at that address. The catalog lists every one it does publish.

Back to the blueprints

Blueprints Full integrations

Accept an ACH payment

Debit a bank account over the API, understand what an ACH approval promises and what it doesn't, and be ready for the return that can arrive days later.

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.

4 steps, 2 API callsACHPaymentsTransactions

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.

Send the debit

1. Send the sale with check data

API call

POST /api/transactions

Send the same sale request you would send for a card, with a checkData block in place of the card block. The check data is what selects the ACH rail; there is no separate endpoint for it. The SEC code says under which NACHA authorization class you are debiting the account, and PPD is the ordinary choice for a personal account you hold a signed authorization for.

Reference for this operation

Values this step gives you

  • {{transactionId}} The id of the debit, from the response body's id property.
  • {{merchantId}} The merchant the debit belongs to, from the response body's merchantId property.
cURL
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.00, "total": 25.00 }
    }
  }'
.NET
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.00m, total = 25.00m }
    }
});

response.EnsureSuccessStatusCode();

var debit = await response.Content.ReadFromJsonAsync<JsonElement>();
var transactionId = debit.GetProperty("id").GetString();
var merchantId = debit.GetProperty("merchantId").GetString();

What this step answers with

Abridged to the properties this step depends on. A real response carries more.

HTTP 200
{
  "id": "9f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "merchantId": "3a7b1c9d-2e4f-4a6b-8c8d-9e0f1a2b3c4d",
  "transactionType": "Sale",
  "resultCode": "Ok",
  "authorizedAmount": 25.00,
  "creationTime": "2026-02-04T18:22:41.517Z",
  "responseData": {
    "resultCode": "Ok",
    "resultMessage": "ACH Sale Approved",
    "secCode": "Ppd"
  }
}

2. Read the outcome off the response

On your side

The sandbox answers with the result code "Ok" and the message "ACH Sale Approved" in the response body. Read that as acceptance into the ACH network, not as a card-style authorization: no issuer checked a balance and no funds are held. The debit now clears through the network on its own schedule, and it can still come back as a return days later. Treat an accepted debit as money in flight rather than money received.

Reference for this operation

Values this step gives you

    What this step answers with

    Abridged to the properties this step depends on. A real response carries more.

    HTTP 200
    {
      "id": "{{transactionId}}",
      "merchantId": "3a7b1c9d-2e4f-4a6b-8c8d-9e0f1a2b3c4d",
      "transactionType": "Sale",
      "resultCode": "Ok",
      "authorizedAmount": 25.00,
      "creationTime": "2026-02-04T18:22:41.517Z",
      "responseData": {
        "resultCode": "Ok",
        "resultMessage": "ACH Sale Approved",
        "secCode": "Ppd"
      }
    }

    Confirm what was accepted

    3. Read the transaction back

    API call

    GET /api/transactions/{{transactionId}}

    Read the debit you just created. The create response and the stored transaction are the same record, and this record is the one a later return lands on. Reconcile against it rather than against the create response alone, so a request that times out on your side still has somewhere to recover the outcome from.

    Reference for this operation

    Values this step gives you

      cURL
      curl "{{BASE_URL}}/api/transactions/{{transactionId}}" \
        -H "api-key: {{API_KEY}}"
      .NET
      var transaction = await http.GetFromJsonAsync<JsonElement>(
          $"/api/transactions/{transactionId}");
      
      var result = transaction.GetProperty("responseData")
          .GetProperty("resultMessage").GetString();

      What this step answers with

      Abridged to the properties this step depends on. A real response carries more.

      HTTP 200
      {
        "id": "{{transactionId}}",
        "merchantId": "3a7b1c9d-2e4f-4a6b-8c8d-9e0f1a2b3c4d",
        "transactionType": "Sale",
        "resultCode": "Ok",
        "authorizedAmount": 25.00,
        "creationTime": "2026-02-04T18:22:41.517Z",
        "responseData": {
          "resultCode": "Ok",
          "resultMessage": "ACH Sale Approved",
          "secCode": "Ppd"
        }
      }

      Be ready for the return

      4. Handle the return that arrives later

      On your side

      On the live rail a return arrives days after the debit was accepted, long after this flow has finished. Build your integration so a transaction can leave an accepted state and enter a returned one: the record you read back above is the one the NACHA return code lands on. The return scenario below collapses that wait to a single sandbox call so you can prove the path now, and the webhook blueprint is how your system hears about a return without polling for it.

      Reference for this operation

      Values this step gives you

        Reconnecting to the server

        Could not reconnect

        This session has ended

        Attempt 1

        Your work on this page is still here. Retrying keeps it; reloading starts the page again.

        The server no longer holds this page's state, so it has to be loaded again.