# ACH Direct Debit for Unified Payments API Before processing ACH payments, you must verify you customer's bank account. ## Validate your customer account To process ACH payments, you must validate your customer's bank account. For this validation, you must contract directly with one of NACHA's third-party validation service vendors listed on the [Account Validation Resource Center](https://www.nacha.org/content/account-validation-resource-center). For ease of integration, we recommend using [Plaid Link](https://plaid.com/docs/link/) due to our built-in support for their Balance and Auth APIs. Plaid Link provides you with instant account validation and eliminates the need to transmit sensitive bank routing and account numbers. To use Plaid in your account validation process: 1. [Create a Plaid account](https://dashboard.plaid.com/signup). 2. Create a sandbox API key on the [Keys](https://dashboard.plaid.com/team/keys) page and make a note of your Plaid client ID. 3. Select Checkout.com on the [Plaid Integrations](https://dashboard.plaid.com/team/integrations) page. 4. Call the `/link/token/create` [endpoint](https://plaid.com/docs/api/link/#linktokencreate) to create a Link token. 5. [Initialize Link](https://plaid.com/docs/api/link/#linktokencreate), open a consent window, and obtain a public token. 6. Call the `/item/public_token/exchange` [endpoint](https://plaid.com/docs/api/items/#itempublic_tokenexchange) to obtain an access token. 7. Call the `/processor/token/create` [endpoint](https://plaid.com/docs/api/processors/#processortokencreate) to obtain a Checkout.com processor token. You send this token when you [request a payment](https://www.checkout.com/docs/payments/add-payment-methods/ach-direct-debit/unified-payments-api.md#Request_an_initial_payment). If you are using other validation service, you must perform account validation on your own before processing the ACH payment. --- ## Payment flow and timing Once the NACHA Account Validation step is complete, you can [submit a payment request](#Request_an_initial_payment). When we receive the request: 1. We send a `payment_pending` webhook before processing the request. 2. We run syntax validation and compliance checks on the payment data. Plaid implementations also include a balance check. - If all checks are successful, we set the payment status to `pending` and send a `payment_capture_pending` webhook. - If unsuccessful, we set the payment status to `declined` and send a `payment_declined` webhook, in which case the payment process is complete. 3. ACH payments submitted before midnight Eastern Time will progress to `captured` status on the following business day before 10:20 AM Eastern Time. Payments will not progress to `captured` on weekends or banking holidays. When a payment progresses to `captured` status, we'll send a `payment_captured` webhook. Note that the ACH network does not provide final confirmation of payment completion. The only indicator of a successful payment is the absence of a return. To minimize exposure to a return, some businesses choose to retain assets for seven to ten calendar days following an ACH payment. Captured payments are settled according to your standard settlement schedule and appear in your financial [reconciliation reports](https://www.checkout.com/docs/business-operations/retrieve-reports/reconcile-with-checkout-com.md). To expedite ACH network payments, you can request same-day settlement. For more information, see [Same Day ACH](#Same_Day_ACH). If the recipient bank returns a successfully processed payment, we set the payment status to `returned` and send a `payment_returned` webhook. Typically, an ACH network or bank return is reported within a few business days; however, the account holder can return a payment for up to 60 days. --- ## Same Day ACH Checkout.com supports Same Day ACH, an accelerated ACH settlement at an increased cost. A standard ACH payment typically takes two business days to settle, but in a same-day payment, ACH settles you within one business day. When setting up ACH payments for your account, configure your settlement service to default to either standard settlement or Same Day ACH. You can override this default setting in specific payment requests by setting `processing.service_type` to `standard` or `sameday`. Same Day ACH offers no accelerated timing benefits between 4pm and 11:59:59pm ET. Payments submitted during this time window always default to standard settlement regardless of your configuration or request settings. --- ## Request an initial payment To comply with regulatory requirements and reduce the likelihood of bank declines or fraud, you must provide the following values within the `account_holder` object. If the `account_holder.type` is: - `individual` – Provide the `account_holder.first_name` and `account_holder.last_name` fields. - `corporate` – Provide the `account_holder.company_name` field. > **Information** > > Typically, the bank account holder's name is on file within your database. Otherwise, Plaid implementations can obtain the name through the [`accounts.owners.names`](https://plaid.com/docs/api/products/identity/#identity-get-response-accounts-owners-names) object in the response from Plaid's [/identity/get](https://plaid.com/docs/api/products/identity/#identityget) endpoint. > **Information** > > Your base URL's `{prefix}` value is unique to your account and environment. To learn how to retrieve your base URLs for the sandbox and production environments, see [API endpoints](https://www.checkout.com/docs/developer-resources/api/api-endpoints.md). **Production** `POST` `https://{prefix}.api.checkout.com/payments` **Sandbox** `POST` `https://{prefix}.api.sandbox.checkout.com/payments` ### Request examples **Plaid** ```json { "source": { "type": "plaid", "token": "", "account_holder": { "type": "individual", "first_name": "John", "last_name": "Doe" } }, "amount": "6540", "currency": "USD", "processing_channel_id": "pc_ovo75iz4hdyudnx6tu74mum3fq" } ``` **ACH** ```json { "source": { "type": "ach", "account_type": "savings", "country": "US", "account_number": "1234567890", "bank_code": "021000021", "account_holder": { "type": "individual", "first_name": "John", "last_name": "Doe" } }, "amount": "6540", "currency": "USD", "processing_channel_id": "pc_ovo75iz4hdyudnx6tu74mum3fq" } ``` During the initial payment, ensure that you provide the correct `account_type` value, which can be one of: - `savings` - `checking` - `cash` Providing the incorrect account type can later trigger a notification of change, which requires you to update the stored bank account details to avoid future disruptions. See the [notification of change (NOC)](#Notification_of_change) section for more information. ### Response examples If you receive a `202 Accepted` response with a status field set to `Pending`, your request was successful. **Plaid** ```json { "id": "pay_itm5maobcrne5fbbafuu2mw3le", "status": "Pending", "_links": { "self": { "href": "https://{prefix}.api.sandbox.checkout.com/payments/pay_itm5maobcrne5fbbafuu2mw3le" } } } ``` **ACH** ```json { "id": "pay_ki4evicwaxyerc3ogrkdtetg6i", "status": "Pending", "source": { "id": "src_kb2gep7wak7ublhgf4jzab2gz4", "type": "ach" }, "_links": { "self": { "href": "https://{prefix}.api.sandbox.checkout.com/payments/pay_ki4evicwaxyerc3ogrkdtetg6i" } } } ``` --- ## Request a recurring payment To request a recurring payment, call the [Request a payment](https://api-reference.checkout.com/tag/Payments/#operation/requestAPaymentOrPayout) endpoint, and set the `payment_type` field to `recurring`: > **Information** > > Your base URL's `{prefix}` value is unique to your account and environment. To learn how to retrieve your base URLs for the sandbox and production environments, see [API endpoints](https://www.checkout.com/docs/developer-resources/api/api-endpoints.md). **Production** `POST` `https://{prefix}.api.checkout.com/payments` **Sandbox** `POST` `https://{prefix}.api.sandbox.checkout.com/payments` ### Request examples **Plaid** Recurring payments with Plaid occasionally require the user to re-authenticate with [Plaid Link's update mode](https://plaid.com/docs/link/update-mode/). ```json { "source": { "type": "plaid", "token": "", "account_holder": { "type": "individual", "first_name": "John", "last_name": "Doe" } }, "payment_type": "recurring", "amount": "6540", "currency": "USD", "processing_channel_id": "pc_ovo75iz4hdyudnx6tu74mum3fq" } ``` **ACH** ```json { "source": { "type": "id", "id": "src_yn62j25j3kvuthctvnnq76n4mi" }, "payment_type": "recurring", "amount": 2000, "currency": "USD" } ``` ### Response examples If you receive a `202 Accepted` response with a status field set to `Pending`, your request was successful. **Plaid** ```json { "id": "pay_itm5maobcrne5fbbafuu2mw3le", "status": "Pending", "_links": { "self": { "href": "https://{prefix}.api.sandbox.checkout.com/payments/pay_itm5maobcrne5fbbafuu2mw3le" } } } ``` **ACH** ```json { "id": "pay_gpogdbx73ppejkf7fbdodo5lra", "status": "Pending", "source": { "id": "src_yn62j25j3kvuthctvnnq76n4mi", "type": "ach" }, "_links": { "self": { "href": "https://{prefix}.api.sandbox.checkout.com/payments/pay_gpogdbx73ppejkf7fbdodo5lra" } } } ``` --- ## Get details about a payment For the full API specification, see the [API reference](https://api-reference.checkout.com/tag/Payments/#operation/getPaymentDetails). > **Information** > > Your base URL's `{prefix}` value is unique to your account and environment. To learn how to retrieve your base URLs for the sandbox and production environments, see [API endpoints](https://www.checkout.com/docs/developer-resources/api/api-endpoints.md). **Production** `GET` `https://{prefix}.api.checkout.com/payments/{payment_id}` **Sandbox** `GET` `https://{prefix}.api.sandbox.checkout.com/payments/{payment_id}` ### Response examples **Plaid** ```json { "id": "pay_itm5maobcrne5fbbafuu2mw3le", "status": "Pending", "reference": "reference_example", "_links": { "self": { "href": "https://{prefix}.api.sandbox.checkout.com/payments/pay_itm5maobcrne5fbbafuu2mw3le" } } } ``` **ACH** ```json { "id": "pay_ki4evicwaxyerc3ogrkdtetg6i", "status": "Pending", "reference": "reference_example", "source": { "id": "src_kb2gep7wak7ublhgf4jzab2gz4", "type": "ach" }, "_links": { "self": { "href": "https://{prefix}.api.sandbox.checkout.com/payments/pay_ki4evicwaxyerc3ogrkdtetg6i" } } } ``` --- ## Refund a payment You can use our [Refund API](https://www.checkout.com/docs/four/payments/manage-payments/refund-a-payment) to fully or partially refund a payment. Once refunded, the payment status updates to `refunded`, and you receive a `payment_refunded` webhook notification. ### Refunded payment flow After the payment status changes to `captured`, you can submit a request to process a full or partial refund. After receiving it, we send a `payment_refund_pending` webhook but do not change the payment status. If the processor token is revoked before the refund occurs, you may need to re-authenticate the user in [Plaid Link's update mode](https://plaid.com/docs/link/update-mode/). The flow and timing for refunds are [the same as for payments](#Payment_flow_and_timing). #### Successful refund After a refund request is successfully sent to the ACH network (within one to three business days), we set the payment status to `refunded` or `partially_refunded` and send a `payment_refunded` webhook. If you issue a refund, you should notify your customer as soon as possible to prevent them from simultaneously canceling the payment with their bank. If your customer does get credited twice, you must contact them directly to resolve the situation. #### Unsuccessful refund If the refund is declined by the ACH network or bank (typically within one to three business days), the payment status is set to `payment_refund_declined`. --- ## Notification of change The ACH network uses Notification of Change (NOC) messages to enable financial institutions to communicate updates and corrections to bank account details. For example, changes in account or routing numbers, or incorrect account types. The Receiving Depository Financial Institution (RDFI) sends an NOC to the Originating Depository Financial Institution (ODFI) when an ACH entry is outdated or incorrect. After receiving an NOC, Checkout.com sends you a `bank_account_updated` webhook request with the necessary information for you to update your payment instruments. Updating your payment details based on the NOC ensures seamless and uninterrupted transactions. ### NOC Codes ACH uses NOC codes to describe the specific nature of the change. Our integration supports the following NOC codes: | ACH NOC code | Description | | --- | --- | | `C01` | Incorrect bank account number | | `C02` | Incorrect transit/routing number | | `C03` | Incorrect transit/routing number and bank account number | | `C04` | Bank account name change | | `C05` | Incorrect account type | | `C06` | Incorrect bank account number and transit code | | `C07` | Incorrect transit/routing number, bank account number, and account type | For instructions on testing NOC codes, see the [Test ACH payments](#Test_ACH_payments) section. --- ## Returns An ACH return indicates that your bank, also known as the Receiving Depository Financial Institution (RDFI), could not collect the funds from your customer's account. A return can happen for various reasons, for example: - The customer has insufficient funds in their bank account. - The customer’s bank account is closed or frozen. - The customer claims that they do not recognize the purchase and did not authorize it. See [ACH returns explained](https://www.checkout.com/blog/ach-returns-explained) for more information. You can avoid returns caused by outdated banking information by listening to the `bank_account_updated` webhook. When a return happens, you'll receive a `payment_returned` webhook notification. ### Return codes ACH return codes identify the reason an ACH payment could not be collected from a customer's account. These codes provide additional information about the cause of the issue. | ACH code | Description | API response code | | --- | --- | --- | | `R01` | Insufficient funds | `20051` | | `R03` | No account or unable to locate account | `20083` | | `R04` | Invalid account number | `20083` | | `R19` | Amount field error | `30020` | | `R02` | Account closed | `30046` | | `R18` | Improper effective entry date | `200T2` | | `R07` | Authorization revoked by customer | `20R07` | | `R08` | Payment stopped or stop payment on item | `20R08` | | `R09` | Uncollected funds | `20R09` | | `R10` | Customer advises not authorized | `20R10` | | `R11` | Customer advises entry not in accordance with the terms of the authorization | `20R11` | | `R17` | File record edit criteria | `20R17` | | `R21` | Invalid company identification | `20R21` | | `R22` | Invalid individual ID number | `20R21` | | `R24` | Duplicate entry | `20R24` | | `R25` | Addenda error | `20R25` | | `R26` | Mandatory field error | `20R26` | | `R27` | Trace number error | `20R27` | | `R29` | Corporate customer advises not authorized | `20R29` | | `R31` | Permissible return entry | `20R31` | | `R32` | RDFI non-settlement | `20R32` | | `R33` | Return of XCK entry | `20R33` | | `R35` | Return of improper debit entry | `20R35` | | `R36` | Return of improper credit entry | `20R36` | | `R37` | Source document presented for payment | `20R37` | | `R38` | Stop payment on source document | `20R38` | | `R05` | Unauthorized debit to consumer account | `30R05` | | `R06` | Returned per ODFI's request | `30R06` | | `R12` | Branch sold to another DFI | `30R12` | | `R13` | Invalid ACH routing number | `30R13` | | `R14` | Representment payee deceased or unable to continue in that capacity | `30R14` | | `R15` | Beneficiary of account holder deceased | `30R15` | | `R16` | Account frozen | `30R16` | | `R20` | Non-transaction account | `30R20` | | `R23` | Credit entry refused by receiver | `30R23` | | `R28` | Routing number or check digit error | `30R28` | | `R30` | RDFI not participant in check truncation program | `30R30` | | `R34` | Limited participation DFI | `30R34` | --- ## Webhooks When using ACH, you may receive the following webhooks. For more details about these webhooks, see [Event types](https://www.checkout.com/docs/developer-resources/event-notifications/event-types.md). | Webhook | Description | | --- | --- | | `payment_pending` | Occurs when the payment request is successfully initiated. | | `payment_capture_pending` | Occurs when the payment request is successfully initiated and capture is pending. | | `payment_declined` | Occurs when the payment request is rejected.

We send this webhook after unsuccessful real-time checks on the payment, including data validation, compliance, and balance availability (Plaid Link only). Typically, around 100 milliseconds after `payment_pending`. | | `payment_captured` | Occurs when the payment is successfully captured. Typically, one to two business days after `payment_capture_pending`. | | `payment_returned` | Occurs when the payment is returned due to an ACH network decline, bank decline, or customer dispute. ACH network and bank declines typically occur within a few business days, but customers can dispute for up to 60 days. | | `payment_refund_pending` | Occurs when the refund request is successfully initiated. | | `payment_refunded` | Occurs when the refund is sent to the ACH network. Typically, one to three business days after `payment_refund_pending`. | | `payment_refund_declined` | Occurs when a refund is declined. Typically, one to three business days after `payment_refund_pending`. | | `bank_account_updated` | Occurs when the customer's bank account details change and must be updated for future payments.

This webhook sends two objects:

- `original_bank_account`
- `replacement_bank_account`

Use the new bank account details returned in the `replacement_bank_account` object to update your payment instruments.

[See example](https://www.checkout.com/docs/developer-resources/event-notifications/event-types/bank_account_updated.md) | --- ## Test ACH payments You can send test requests using the values from the following table, depending on which outcome you want to simulate: | API response code | ACH code | Payment status | Webhook | Values | | --- | --- | --- | --- | --- | | `10000` | N/A | `Captured` | `payment_captured` | Corporate and personal accounts:

- `bank_code: 021000021`
- `account_number: 1234567890` | | `20000` | N/A | `Declined` | `payment_declined` | Corporate accounts:

- `bank_code: 091000022`
- `company_name: Green Co`

Personal accounts:

- `bank_code: 091000022`
- `first_name: Green`
- `last_name: Co` | | `20051` | `R01` | `Returned` | `payment_returned` | Corporate accounts:

- `bank_code: 011075150`
- `company_name: Widget Inc`

Personal accounts:

- `bank_code: 011075150`
- `first_name: Widget`
- `last_name: Inc` | | `30046` | `R02` | `Returned` | `payment_returned` | Corporate and personal accounts:

- `account_number: 9999999999` | | `30R05` | `R05` | `Returned` | `payment_returned` | Corporate and personal accounts:

- `bank_code: 122105155` | | `20R07` | `R07` | `Returned` | `payment_returned` | Corporate and personal accounts:

- `bank_code: 082000549` | | `20R10` | `R10` | `Returned` | `payment_returned` | Corporate and personal accounts:

- `bank_code: 121122676` | | `20R29` | `R29` | `Returned` | `payment_returned` | Corporate and personal accounts:

- `bank_code: 122235821` | | `10000` | `C01` | `Captured` | `bank_account_updated` | Corporate accounts:

- `account_number: 8888888888`
- `company_name: Checkout Inc`

Personal accounts:

- `account_number: 8888888888`
- `first_name: Checkout`
- `last_name: Inc` | | `10000` | `C02` | `Captured` | `bank_account_updated` | Corporate accounts:

- `bank_code: 011075150`
- `company_name: Green Co`

Personal accounts:

- `bank_code: 011075150`
- `first_name: Green`
- `last_name: Co` | | `10000` | `C03` | `Captured` | `bank_account_updated` | Corporate accounts:

- `bank_code: 011075150`
- `company_name: Yellow Co`

Personal accounts:

- `bank_code: 011075150`
- `first_name: Yellow`
- `last_name: Co` | | `10000` | `C04` | `Captured` | `bank_account_updated` | Corporate accounts:

- `bank_code: 011075150`
- `company_name: John Doe`

Personal accounts:

- `bank_code: 011075150`
- `first_name`: John
- `last_name`: Doe | | `10000` | `C05` | `Captured` | `bank_account_updated` | Corporate accounts:

- `bank_code: 011075150`
- `company_name: Gray Co`

Personal accounts:

- `bank_code: 011075150`
- `first_name: Gray`
- `last_name: Co` | | `10000` | `C06` | `Captured` | `bank_account_updated` | Corporate accounts:

- `bank_code: 011075150`
- `company_name: Amber Co`

Personal accounts:

- `bank_code: 011075150`
- `first_name: Amber`
- `last_name: Co` | | `10000` | `C07` | `Captured` | `bank_account_updated` | Corporate accounts:

- `bank_code: 011075150`
- `company_name: Orange Co`

Personal accounts:

- `bank_code: 011075150`
- `first_name: Orange`
- `last_name: Co` |