Receiving ACH debits

A received ACH debit pulls money from your Internal Account. For example, a vendor may debit your Internal Account to collect payment for a bill. Modern Treasury represents a received ACH debit as an Incoming Payment Detail.

This differs from originating an ACH debit, which pulls funds from an External Account into your Internal Account.

Before you start

  • Ask your customer success manager to turn on received ACH debits for your organization.
  • Onboard an Individual or Business Legal Entity, and wait for its status to become active.

1. Provision an Internal Account

Follow Provisioning Accounts to create a USD Internal Account for the active Legal Entity. When you create the account, set debitable: true so the account can receive ACH debits. This field defaults to false, and you cannot change it after you create the account.

Subscribe to internal_account webhooks. Wait for the internal_account.activated event before you use the account.

2. Choose which Company Ids are allowed to debit the account

On the ACH network, the originator of an ACH payment is identified by an ACH Company ID. This ID is unique per originating party and is usually 10 digits. If you anticipate receiving debits, you can ask your counterparty what their ACH Company ID is, and restrict ACH debits to that ID, or set of IDs, only.

Use the allowed_ach_debit_company_ids field to set the account's allowlist of ACH Company IDs. For an account with debitable: true, this table shows which debits each value of the field allows:

ValueMeaningDebit from Company ID 1111111111Debit from Company ID 2222222222
null*No restriction (any Company ID can debit your account)AllowedAllowed
[]All debits blocked (no Company IDs can debit your account)BlockedBlocked
["1111111111"]Only the Company IDs in the list can debit your accountAllowedBlocked

*Default value

You can set up your allowlist when you create the account, or update the account later. Each update is a full replacement of the existing list. To edit one Company ID at a time, you can view the Allowlist in the Modern Treasury Dashboard, in the Account Details page.

3. Receive the debit

Retrieve the Internal Account's routing number and account number, and share them securely with each company that you allow to debit the account. If the account has an allowlist, confirm that it includes the company's ACH Company ID before the company sends the debit.

Keep enough money in the Internal Account to cover each debit. A debit from an allowed company can still fail for other reasons, such as when the account does not have enough money.

When a debit arrives, Modern Treasury checks its ACH Company ID against the account's allowlist. If the allowlist blocks the company, Modern Treasury automatically returns the debit.

4. Track the Incoming Payment Detail

For a received debit, the Incoming Payment Detail has direction: "debit".

Monitor the Incoming Payment Details on the Internal Account and check their status:

StatusWhat it means
pendingModern Treasury received the debit, and the debit has not posted yet.
completedThe debit posted, and the money left the account.
failedThe debit failed. Read failure_reason to learn why.

Monitor status in Incoming Payment Detail webhooks. Some Incoming Payment Details already have status: "failed" when you receive the incoming_payment_detail.created event.

When the allowlist blocks a debit, its Incoming Payment Detail has status: "failed" and failure_reason: "ACH debits from this company are not allowed".

You can also retrieve the Incoming Payment Detail through the API with its id. See Tracking Received Payments to learn more about Incoming Payment Details and webhooks.

Test in Sandbox

Use a sandbox API key for every request below. You need your Organization ID, your connection ID, and the ID of an active Legal Entity. The ACH Company IDs in these examples are ordinary values. They do not trigger special results.

1. Create an Internal Account that can receive debits

This example allows only ACH Company ID 1111111111 to debit the account:

curl --request POST \
  --user ORGANIZATION_ID:SANDBOX_API_KEY \
  --url https://app.moderntreasury.com/api/internal_accounts \
  --header 'Content-Type: application/json' \
  --data '{
    "name": "Bill payments",
    "connection_id": "CONNECTION_ID",
    "legal_entity_id": "LEGAL_ENTITY_ID",
    "currency": "USD",
    "debitable": true,
    "allowed_ach_debit_company_ids": ["1111111111"]
  }'

Save the id from the response as INTERNAL_ACCOUNT_ID, and wait for the internal_account.activated event.

2. Add money to the account

Add money before you simulate debits to ensure there are sufficient funds. Simulate an incoming ACH credit of $1,000:

curl --request POST \
  --user ORGANIZATION_ID:SANDBOX_API_KEY \
  --url https://app.moderntreasury.com/api/simulations/incoming_payment_details/create_async \
  --header 'Content-Type: application/json' \
  --data '{
    "internal_account_id": "INTERNAL_ACCOUNT_ID",
    "type": "ach",
    "direction": "credit",
    "amount": 100000
  }'

Monitor the Incoming Payment Detail and wait for its status to become completed before you simulate debits. See Tracking Received Payments for monitoring options.

3. Simulate received debits

Simulate a $10 debit from ACH Company ID 1111111111:

curl --request POST \
  --user ORGANIZATION_ID:SANDBOX_API_KEY \
  --url https://app.moderntreasury.com/api/simulations/incoming_payment_details/create_async \
  --header 'Content-Type: application/json' \
  --data '{
    "internal_account_id": "INTERNAL_ACCOUNT_ID",
    "type": "ach",
    "direction": "debit",
    "amount": 1000,
    "data": {
      "originator_identification": "1111111111"
    }
  }'

The simulation endpoint responds with HTTP status 202 and a pending Incoming Payment Detail. Monitor its status for completed or failed.

The request above uses Company ID 1111111111, which is on the account's allowlist. Its Incoming Payment Detail should complete.

Next test a blocked debit:

Change originator_identification to "2222222222" inside data. This Company ID is not on the allowlist.

The Incoming Payment Detail should fail with failure_reason: "ACH debits from this company are not allowed".

4. Change the allowlist and test again

After the simulations finish, update the account and repeat the debit requests for each setting:

  • Set allowed_ach_debit_company_ids to []: all simulated debits should fail.
  • Set allowed_ach_debit_company_ids to null: all simulated debits should complete.

Wait for each set of simulations to finish before you change the allowlist again.