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)
-
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 |
-
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:
-
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.
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
-
Call
POST /ach/v1/payments
.The details of the push payment are defined by the call attributes, which can be seen here.
ImportantWe 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.
NoteMoney amounts in API calls and responses are written without a decimal point between the dollars and the cents.
CopySample Request POST /ach/v1/paymentsPOST /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"
} -
A successful API call returns a JSON response with the details of your originated payment.
CopySample 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.
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.
-
Call
POST /ach/v1/payments
. -
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 theAch.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, thepostingCode
tells you the rejection code, in this case RES (Account Restriction). If the payment needs to be reviewed manually, therejectionReason
gives you more information about the rejection.CopySample 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.
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.
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:
{
"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:
{
"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.