How to originate an ACH payment

In this tutorial, you'll learn how to

Originate a push payment

Monitor status of the payment

Handle notifications of change to the outbound payment (rejected, returned, NOC)

Note
  • If you are new to ACH we recommend you read the ACH overview before starting this tutorial

  • If you are new to APIs and how the work we recommend you read the API overview page before starting this tutorial.

The tutorial uses these API endpoints:

API

Description

POST /ach/v1/payments

Originate the payment

Important
  • Do not poll the APIs for status updates and reconciliation purposes.

  • Incorporate webhooks into the payment reconciliation process.

Before you begin

Make sure you have:

  • API credentials

  • Partner ID

  • Account Number

  • Registered the following webhook events

    Webhook

    Description

    Ach.Payment.Sent

    Outbound payment has been transmitted to the Federal Reserve

    Ach.Return.Received

    A previously originated payment has been returned by the receiving bank

    Ach.Noc.Received

    A notification of change has been sent from the receiving bank regarding a previous origination

    Ach.Payment.Rejected

    A notification that the ACH payment was rejected

Originate the ACH payment

There are two types of ACH payments:

  • Push payment
    When you transfer money to an account in another bank.

  • Pull payment
    When you request funds from an account in another bank.

Note

Use an ACH prenotification (prenote) to verify and validate account details. A prenote simply means originating a payment of $0.00. If you don't receive an error response, the details are correct.

This tutorial covers push payments. A push payment can result in:

  • The recipient receives the payment

  • CR or ACH rejects the payment

  • The recipient FI returns the payment

  • The recipient receives the payment but ACH sends a Notification of Change.

Let's send $100 to Bob Smith who has an account at another bank. Since we are transferring the money to Bob's account the transactionType attribute must be Push.

To originate an ACH payment

  1. Call POST /ach/v1/payments.

    The details of the push payment are defined by the call attributes, which can be seen here.

    Important

    We strongly recommend that you include an idempotency key in your request header to provide duplicate protection should the payment fail. Read more about idempotency keys here.

    Note

    Money amounts in API calls and responses are written without a decimal point between the dollars and the cents.

    Copy
    Sample Request POST /ach/v1/payments
    POST /ach/v1/payments
    {
      "accountNumber": "2714035231",
      "receiver": {
        "routingNumber": "021000021",
        "accountNumber": "456789000",
        "accountType": "Checking",
        "name": "Bob Smith",
        "identification": "XYZ123",
      },
      "secCode": "WEB",
      "description": "Payment",
      "transactionType": "Push",
      "amount": 10000,
      "serviceType": "SameDay",
      "clientIdentifier": "21fe77da-e2f8-4475-9397-81a293d63b8x"
    }
  2. A successful API call returns a JSON response with the details of your originated payment.

    Copy
    Sample Reponse POST /ach/v1/payments
    {
        "id": "b96b935a-4713-4aae-973b-aeee00f1a749",
        "accountNumber": "2714035231",
        "referenceId": "A2236S8S20FH",
        "paymentType": "Origination",
        "direction": "Outbound",
        "status": "Created",
        "source": "Api",
        "postingType": "Individual",
        "postingCode": "OK",
        "posting": "Pending",
        "originator": {
            "routingNumber": "021214891",
            "accountNumber": "2714035231",
            "accountType": "Checking",
            "name": "Cross River Bank",
            "identification": "021214891"
        },
        "receiver": {
            "routingNumber": "021000021",
            "accountNumber": "456789000",
            "accountType": "Checking",
            "name": "Bob Smith",
            "identification": "XYZ123"
        },
        "secCode": "WEB",
        "description": "Payment",
        "transactionType": "Push",
        "amount": 10000,
        "serviceType": "SameDay",
        "effectiveDate": "220811",
        "traceNumber": "021214898943562",
        "wasReturned": false,
        "wasCorrected": false,
        "holdDays": 0,
        "original": {
            "paymentId": "b96b935a-4713-4aae-973b-aeee00f1a749"
        },
        "createdAt": "2022-08-11T10:39:49.9996701-04:00",
        "partnerId": "1e5d3f04-ae24-4af6-9e30-aecf012b99dd",
        "productId": "cc62e17f-5912-483e-9e42-aed30112fbb6",
        "lastModifiedAt": "2022-08-11T10:39:49.9996701-04:00",
        "clientIdentifier": "21fe77da-e2f8-4475-9397-81a293d63b8x"
    }

    The status attribute in the response indicates that your payment was created. It does not indicate that your payment was successful.

Payment confirmation

After originating the payment, its status changes to Pending or Hold for up to several hours.

  • A Pending status lets you know that the payment request is created but has not been batched for release to the Federal Reserve. The batching process occurs several times a day.

  • A Hold status indicates that the payment request is in review and has not yet been approved for release to the Federal Reserve.

Next, the payment moves to Batched status. If the payment is originated close to a weekend or bank holiday it may take a few days for the status to transition to Batched.

When the Federal Reserve accepts the payment the status changes to processing, which triggers the Ach.Payment.Sent event.

The payment ID provided in the response body of the payment origination request (id) appears in the details object of the Ach.Payment.Sent event. In this case: b96b935a-4713-4aae-973b-aeee00f1a749.

Copy

Sample Ach.Payment.Sent Event

{
  "id": "7c135bfa-234f-48d2-9d70-adc70135d15f",
  "eventName": "Ach.Payment.Sent",
  "status": "Pending",
  "partnerId": "30dee145-b6a2-4058-8dc3-ac4000dee91f",
  "createdAt": "2021-10-20T14:48:01.12-04:00",
  "resources": [
    "ach/v1/payments/b96b935a-4713-4aae-973b-aeee00f1a749"
  ],
  "details": [
    {
      "paymentId": "b96b935a-4713-4aae-973b-aeee00f1a749",
      "coreTransactionId": null,
      "memoPostId": "85b509f9-61f0-4af3-b64c-b04900de43da",
      "clientBatchId": null,
      "clientBatchSequence": null,
      "accountNumber": "2714035231",
      "postingCode": null,
      "clientIdentifier": null,
      "purpose": "ENTERED BY #60C3C9C8FD17BA0070A7FB4F#"
    }
    ]
}

Payment status webhook events

Rejected payment

There may be situations where a payment request is rejected. A payment request rejected after initially receiving a success response from POST /ach/v1/payments could be due to:

  • Technical Rejection. The transaction was unable to post from the account being used for your request, and is covered in this tutorial.

    Example
    • You originate a push payment for an amount greater than the balance of the originator account.

    • You originate a payment from an account with an active restriction.

  • Manual Rejection. The payment was rejected by the Cross River Operations or BSA/AML Teams. Contact Cross River directly for information on why the payment was rejected.

To simulate a technical rejection, originate a push payment for an amount that exceeds the originating account balance. Information on ACH simulation is found here.

Again, let's send $100 to Bob Smith who has an account at another bank. This time, the amount you are sending exceeds the amount in the originator account.

  1. Call POST /ach/v1/payments.

  2. Define the call attributes as above in To originate an ACH payment.

    Copy
    {
      "accountNumber": "2674958042",
      "receiver": {
        "routingNumber": "021000021",
        "accountNumber": "456789000",
        "accountType": "Checking",
        "name": "Bob Smith",
        "identification": "XYZ123",
      },
      "secCode": "WEB",
      "description": "Payment",
      "transactionType": "Push",
      "amount": 10000,
      "serviceType": "SameDay",
      "clientIdentifier": "bd3e0315-5f29-4b1c-a004-1fcdd0b8bee1"
    }

    The following JSON response is returned:

    Copy
    {
        "id": "f868a125-22b8-4c7e-a8dd-aeee00f76ce7",
        "accountNumber": "2674958042",
        "referenceId": "A223O84JDV79",
        "paymentType": "Origination",
        "direction": "Outbound",
        "status": "Created",
        "source": "Api",
        "postingType": "Individual",
        "postingCode": "OK",
        "posting": "Pending",
        "originator": {
            "routingNumber": "021214891",
            "accountNumber": "2674958042",
            "accountType": "Checking",
            "name": "Cross River Bank",
            "identification": "021214891"
        },
        "receiver": {
            "routingNumber": "021000021",
            "accountNumber": "456789000",
            "accountType": "Checking",
            "name": "Bob Smith",
            "identification": "XYZ123"
        },
        "secCode": "WEB",
        "description": "Payment",
        "transactionType": "Push",
        "amount": 10000,
        "serviceType": "SameDay",
        "effectiveDate": "220811",
        "traceNumber": "021214896697194",
        "wasReturned": false,
        "wasCorrected": false,
        "holdDays": 0,
        "original": {
            "paymentId": "f868a125-22b8-4c7e-a8dd-aeee00f76ce7"
        },
        "createdAt": "2022-08-11T11:00:50.8991331-04:00",
        "partnerId": "1e5d3f04-ae24-4af6-9e30-aecf012b99dd",
        "productId": "d5f3ce06-8550-413e-a2e1-aed1016d6eea",
        "lastModifiedAt": "2022-08-11T11:00:50.8991331-04:00",
        "clientIdentifier": "bd3e0315-5f29-4b1c-a004-1fcdd0b8bee1"
    }

    The Ach.Payment.Rejected event is triggered.

    The details object in the Ach.Payment.Rejected event contains the payment ID from the response body (id). In this case: f868a125-22b8-4c7e-a8dd-aeee00f76ce7.

    In the details object of the event, the postingCode tells you the rejection code, in this case RES (Account Restriction). If the payment needs to be reviewed manually, the rejectionReason gives you more information about the rejection.

    Copy
    Sample Ach.Payment.Rejected event
    {
      "id": "496659ff-6034-480a-84af-aeee00f78ade",
      "eventName": "Ach.Payment.Rejected",
      "status": "Pending",
      "partnerId": "1e5d3f04-ae24-4af6-9e30-aecf012b99dd",
      "createdAt": "2022-08-11T11:01:16.483-04:00",
      "resources": [
        "ach/v1/payments/f868a125-22b8-4c7e-a8dd-aeee00f76ce7"
      ],
      "details": [
      {
          "paymentId": "f868a125-22b8-4c7e-a8dd-aeee00f76ce7",
          "coreTransactionId": null,
          "memoPostId": "517f24c5-3cf1-4168-b721-b04900e36f00",
          "clientBatchId": null,
          "clientBatchSequence": null,
          "accountNumber": "2674958042",
          "postingCode": "RES",
          "clientIdentifier": null,
          "purpose": "ENTERED BY #60C3C9C8FD17BA0070A7FB4F#",
          "rejectionReason": "Other"
        }
      ]
    }

Returned payment

Sometimes an ACH payment is returned by the receiving bank. See Return Codes for a complete list of why a payment might be returned.

Example

The receiving bank can't find the specified receiver account number.

Most returns occur within 2 business days, but may take longer.

A payment return creates a new payment record with the paymentType set as Return. The new payment record is connected to the original payment record with the previous.paymentId field.

Important

Once payment status transitions to Complete, the payment record is no longer updated, even if the payment is returned.

A payment returned from a receiving bank triggers the ACH.Return.Received webhook event.

Here's an example of an ACH return webhook event:

Copy
Sample Ach.Return.Received event
{
  "id": "d3ad6473-6690-44f1-a4a6-b04900d95419",
  "eventName": "Ach.Return.Received",
  "status": "Pending",
  "partnerId": "1e5d3f04-ae24-4af6-9e30-aecf012b99dd",
  "createdAt": "2023-07-24T09:11:16.133-04:00",
  "resources": [
    "ach/v1/payments/fb05013b-eaba-439e-8c6b-b04900d8f805"
  ],
  "details": [
    {
      "paymentId": "fb05013b-eaba-439e-8c6b-b04900d8f805",
      "coreTransactionId": "28633d37-f441-496a-ab07-b04900d93e75",
      "originalPaymentId": "1a1a9919-33b5-4933-9b9f-b034011462df",
      "accountNumber": "2151546989",
      "traceNumber": "021000021754553",
      "reasonCode": "R01",
      "reasonData": ""
    }
  ]
}

The event shows reasonCode R01 (Insufficient Funds). The originalPaymentId field shows the ID of the original outbound payment.

Notification of Change (NOC)

There are times when Cross River receives an ACH notification of change (NOC) related to an outbound payment. When you create an API call, a NOC is referred to as a Correction in the paymentType attribute. Unlike an ACH return, a NOC indicates that the payment you previously originated:

  • Posted to the receiver account

  • Contained an error (such as an incorrect routing number)
    See ACH Correction Codes for a complete list

Register for the Ach.Noc.Received webhook event to be notified of an inbound NOC.

Here's an example of a NOC webhook event:

Copy
Sample Ach.Noc.Received event
{
  "id": "43d767a8-6b3a-4b5e-9fe6-b05401215333",
  "eventName": "Ach.Noc.Received",
  "status": "Pending",
  "partnerId": "1e5d3f04-ae24-4af6-9e30-aecf012b99dd",
  "createdAt": "2023-08-04T13:33:24.01-04:00",
  "resources": [
    "ach/v1/payments/197021dd-4b47-47b3-a75e-b0540120f5f2"
  ],
  "details": [
    {
      "paymentId": "197021dd-4b47-47b3-a75e-b0540120f5f2",
      "coreTransactionId": "578f36cd-f8e8-47ba-afa8-b05401213c5f",
      "originalPaymentId": "2cc0a0dd-6d7e-4ad9-9d3e-b0540105ba19",
      "accountNumber": "2696592019",
      "traceNumber": "021200333650075",
      "reasonCode": "C02",
      "reasonData": "026009593"
    }
  ]
}

The reasonCode attribute in the response is set to C02. This indicates an Incorrect Routing Number. The reasonData attribute is the correct receiver routing number.

If you receive a NOC notification, update your internal records with the information you receive in the reasonData attribute.