Sending ACH Payments

In this tutorial we explore originating an ACH push payment from a sub ledger account. We will also register relevant webhooks to receive status updates on our payment. Before starting the tutorial, please make sure you have the following:

  • API credentials
  • Partner ID
  • Master Account Number

Get an Access Token

Before calling the COS API you must first request an access token. Once the access token has been obtained it is recommended that it is cached for the lifetime of the token. If the API starts to return a 401 response code, it is likely that your token has expired and you need to obtain a new one. More details can be found here.

Register Webhooks

Webhooks allow us to receive notifications when certain events occur within COS. After we originate our ACH payment in the step below, we will want to stay informed when it has been accepted by the Federal Reserve. For that you will need to call the webhook registration endpoint and indicate you wish to receive ACH.Payment.Sent events:

POST /webhooks/v1/registrations
{
  "partnerId": "00000000-0000-0000-0000-000000000000",
  "eventName": "ACH.Payment.Sent",
  "registrationType": "Push", 
  "callbackUrl": "https://cos.yourcompanysite.com/ach-events"
}

Next, let's register for ACH.Return.Received to be notified in the event our payment gets returned by the receiving bank:

POST /webhooks/v1/registrations
{
  "partnerId": "00000000-0000-0000-0000-000000000000",
  "eventName": "ACH.Return.Received",
  "registrationType": "Push", 
  "callbackUrl": "https://cos.yourcompanysite.com/ach-events"
}

To learn more about webhook registrations click here. For a full listing of available ACH webhook events click here.

Open Sub Ledger

This tutorial assumes you have already obtained a master account which supports sub ledgers. Alternately, since all master accounts have an implicit sub ledger (more info here), this step can be skipped if you wish to originate a payment directly from your master account. The primary benefit of originating a payment from a sub ledger is to make reconciliation easier. For example, you could open a sub ledger for each member of your service and accurately track payment activity for them. The benefits are even greater if you wish to receive payments from other banks directly to these sub ledgers.

To learn more about master accounts and sub ledgers click here.

POST /core/v1/dda/subaccounts
{
  "masterAccountNumber": "2001231234",
  "title": "Acme Co",
  "beneficiary": {
    "referenceId": "ABC789",
    "entityName": "Acme Co",
    "streetAddress1": "400 Business Street",
    "streetAddress2": "Suite 123",
    "city": "New York",
    "state": "NY",
    "postalCode": "10025",
    "countryCode": "US",
    "phoneNumber": "2015551234",
    "emailAddress": "[email protected]",
    "notes": "Testing 123"
  }
}
{
  "accountNumber": "300012341234",
  "accountType": "Deposit",
  "status": "Active",
  "ledgerType": "Passthrough",
  "productType": "Deposit",
  "title": "Acme Co",
  "currentBalance": 0,
  "availableBalance": 0,
  "holdAmount": 0,
  "transactionCount": 0,
  "openedAt": "2020-03-10T04:29:29.182Z",
  "masterAccountNumber": "2001231234",
  "beneficiary": {
    "referenceId": "ABC789",
    "entityName": "Acme Co",
    "streetAddress1": "400 Business Street",
    "streetAddress2": "Suite 123",
    "city": "New York",
    "state": "NY",
    "postalCode": "10025",
    "countryCode": "US",
    "phoneNumber": "2015551234",
    "emailAddress": "[email protected]",
    "notes": "Testing 123"
  },
  "partnerId": "00000000-0000-0000-0000-000000000000",
  "lastMaintenanceAt": "2020-03-10T04:29:29.182Z",
  "productId": "00000000-0000-0000-0000-000000000000",
  "lastModifiedAt": "2020-03-10T04:29:29.182Z"
}

Originate Payment

Now let's send $100 (amount below is in cents) to Bob Smith who has an account at another bank. We will pass the sub ledger number from the previous step in the account number field. This indicates the account that will be funding the payment.

When we send money from COS to another bank, this is called a Push payment. If we were to take money from an account held at another bank, that would be a Pull payment. For the purposes of this tutorial we will be sending money, so the paymentType field should be set to Push.

The receiver fields relate to the account at the other bank. The routingNumber is used by the Federal Reserve to route the payment instruction to the appropriate bank. The accountNumber and name fields will be used by the bank to locate the account you wish to send money to. Lastly, the identification field is a reference value that may or may not appear on the account statement description. For example, this could be a customer reference code or invoice number.

The serviceType field supports either SameDay or Standard values, and indicates when the payment is effective. Note that SameDay is only supported on domestic payments $100,000 or less.

POST /ach/v1/payments
{
  "accountNumber": "300012341234",
  "receiver": {
    "routingNumber": "021000021",
    "accountNumber": "456789000",
    "accountType": "Checking",
    "name": "Bob Smith",
    "identification": "XYZ123",
  },
  "secCode": "WEB",
  "description": "Payment",
  "transactionType": "Push",
  "amount": 10000,
  "serviceType": "SameDay",
}

It is highly recommended an idempotency key is included in your request header. This will provide duplicate protection in the event of a failure. Read more about idempotency keys here.

Payment Confirmation

After originating a payment, it will typically stay in a Pending or Hold status for minutes to hours. It may take up to a few days to transition in the case of weekends and bank holidays. A status of Hold indicates the payment is being reviewed by our Ops team and has not yet been approved for release to the Federal Reserve. A status of Pending lets you know that the payment is good to go, but has not yet been batched for release. Typically the batching process occurs a few times per day.

Given this, it is not recommended to poll for status updates. Instead the recommended method is to register for the ACH.Payment.Sent webhook event, as we did in the steps above. The ACH.Payment.Sent webhook is fired once the status transitions to Processing, which indicates the payment has been successfully accepted by the Federal Reserve. The status will transition to Complete once the payment posts to the funding account. In the case of push payments, this happens almost immediately after Processing since we are sending money. However for pull payments, this transition could take up to a day and is driven by the effective date of the payment.

Handling Returns

Sometimes ACH payments get returned by the receiving bank. There are a variety of reasons why this can happen, as well as varying time frames. For example, the receiver account number specified cannot be found by the receiving bank. Most returns occur within 2 business days. For a full listing of return reasons click here.

When your payment is returned, a new payment record is created with a paymentType set to Return. The new payment record is correlated to the origination record by the original.paymentId field. It is important to understand that once a payment's status transitions to Complete, there will be no further updates to that payment record, even if the payment is returned.

Lastly, we are notified of the returned payment via the ACH.Return.Received webhook we registered in the steps above. At which point, you would query the payment resource specified in the event body to obtain the original payment ID and return reason code found in the reasonCode field.


Did this page help you?