Pay via ACH
Pay external bank accounts using the ACH network
Pay via ACH sends funds from a Passport Account to an external bank account over the ACH (Automated Clearing House) network. Use it for vendor payments, payroll disbursements, and refunds. You pay a third party saved as a Contact, or a one-time recipient. For the full field list, jump to the Request reference.
Moving funds to your own bank account is a transfer of your own money, not a payout. See Transfer funds.
Common use cases
- Pay vendors, suppliers, or service providers
- Disburse payroll to employees
- Refund or pay a one-off recipient
Make your first payout
The fastest way to see it work: pay a saved vendor (Contact) with the minimal fields below. The full set of options is in the Request reference.
POST /v1/customer/id/{id}/transaction
{
"externalId": "ach-first-payout-001",
"method": "ACH",
"amount": "1200.00",
"purpose": "Vendor Payment",
"source": { "account": { "externalId": "passport-account-001" } },
"destination": {
"contact": {
"externalId": "vendor-contact-001",
"externalAccount": { "externalId": "vendor-bank-001" }
}
}
}You'll get back a transaction with status: SCHEDULED. Store the returned id and your externalId to track it and for follow-up actions.
UseexternalIdfor safe retriesFor Treasury, PCE treats the
externalIdin the request body as the idempotency key; there's no separate header. Send a uniqueexternalIdper payout, and retrying with the same value creates it only once.
Scenarios
Every ACH payout follows the transaction lifecycle. The only thing that changes per scenario is the destination, so pick the one that matches the problem you're solving.
sequenceDiagram
participant You as Your Application
participant PCE as PCE
participant ACH as ACH Network
participant Bank as Recipient's Bank
You->>PCE: POST /v1/customer/id/{id}/transaction (method: ACH)
PCE->>PCE: Validate balance + destination
PCE-->>You: 201 Created (status: SCHEDULED)
PCE->>ACH: Submit ACH credit — PROCESSING
ACH->>Bank: Route funds to beneficiary
Bank-->>ACH: Credit confirmation
ACH-->>PCE: Settlement complete
PCE-->>You: Webhook (status: COMPLETED)
Before you begin (all scenarios)
- The Passport Account you're paying from is active and has enough balance to cover the payout.
Each scenario below adds one requirement specific to its destination.
Scenario 1: Pay a vendor or contractor you pay regularly
(third-party recipient saved as a Contact)
Use this to pay a vendor, supplier, contractor, or employee you'll pay again. Save their bank details once as a Contact, then reference the contact on every payout.
You'll also need: the recipient saved as a Contact with a linked external bank account.
Request
POST /v1/customer/id/{id}/transaction
{
"externalId": "ach-contact-2dd0",
"method": "ACH",
"type": "REGULAR",
"amount": "1200.00",
"purpose": "Vendor Payment",
"source": { "account": { "externalId": "passport-account-001" } },
"destination": {
"contact": {
"externalId": "vendor-contact-001",
"externalAccount": { "externalId": "vendor-contact-bank-001" }
}
},
"processingDetail": { "processingMode": "SAME_DAY", "authType": "WRITTEN" }
}Response
{
"resourceName": "transaction",
"url": "/v1/customer/id/4225975/transaction/id/230491510",
"id": 230491510,
"externalId": "ach-contact-2dd0",
"status": "SCHEDULED",
"statusDate": "06/29/2026 14:20:55",
"amount": 1200,
"method": "ACH",
"purpose": "Vendor Payment",
"type": "REGULAR",
"transactionClass": "SEND",
"statusReason": "On User Request",
"source": {
"account": { "resourceName": "account", "id": 9915272, "url": "/v1/customer/id/4225975/account/id/9915272" }
},
"destination": {
"contact": { "resourceName": "contact", "id": 4019600, "url": "/v1/customer/id/4225975/contact/id/4019600" }
}
}Scenario 2: Make a one-time payout to a new recipient
(bank details passed inline, nothing saved)
Use this for a one-off payment when you don't want to save the recipient, such as a refund or a single disbursement. You pass the bank details directly in the request.
You'll also need: the recipient's holderName, routingNumber, and accountNumber, verified.
Request
POST /v1/customer/id/{id}/transaction
{
"externalId": "ach-onetime-91be",
"method": "ACH",
"type": "REGULAR",
"amount": "750.00",
"purpose": "Refund",
"source": { "account": { "externalId": "passport-account-001" } },
"destination": {
"externalAccount": {
"holderName": "Jane Doe",
"accountNumber": "987654321098",
"routingNumber": "021000089",
"type": "CHECKING",
"holderType": "CONSUMER"
}
},
"processingDetail": { "processingMode": "FORWARD", "authType": "WRITTEN" }
}Response
{
"resourceName": "transaction",
"url": "/v1/customer/id/4225975/transaction/id/230491521",
"id": 230491521,
"externalId": "ach-onetime-91be",
"status": "SCHEDULED",
"statusDate": "06/29/2026 16:04:30",
"amount": 750,
"method": "ACH",
"purpose": "Refund",
"type": "REGULAR",
"transactionClass": "SEND",
"statusReason": "On User Request",
"source": {
"account": { "resourceName": "account", "id": 9915272, "url": "/v1/customer/id/4225975/account/id/9915272" }
},
"destination": {
"externalAccount": { "resourceName": "externalAccount", "id": 4019508, "url": "/v1/customer/id/4225975/externalAccount/id/4019508" }
}
}Scenario 3: Refund a payout you already sent
(reverse a completed payout)
Use this to return funds from a payout that has already completed, for example reversing a vendor payment. You reference the original transaction, and the source and destination are derived from it, so you don't pass them again. Set type to REFUND.
You'll also need: the original payout to exist and be COMPLETED (you can't refund one that's still processing).
Request
POST /v1/customer/id/{id}/transaction
{
"externalId": "ach-refund-4c2e",
"method": "ACH",
"type": "REFUND",
"amount": "5.00",
"purpose": "Card Refund",
"reason": "ON_CUSTOMER_REQUEST",
"processingDetail": {
"parent": { "id": "3317" },
"achRefundCompanyName": "AMEX LTD",
"achRefundCompanyDescription": "Refund"
}
}Response
{
"resourceName": "transaction",
"url": "/v1/customer/id/4225975/transaction/id/230491533",
"id": 230491533,
"externalId": "ach-refund-4c2e",
"status": "SCHEDULED",
"statusDate": "06/29/2026 20:02:50",
"amount": 5,
"method": "ACH",
"type": "REFUND",
"purpose": "Card Refund",
"statusReason": "On User Request",
"processingDetail": {
"parent": { "id": 230491490 }
}
}Track the transaction
Subscribe to webhooks or GET the transaction to follow it to settlement. The statuses, the webhook payload, and the event types all live in Transaction lifecycle.
Request reference
The complete set of fields for the create-transaction request. Scenarios above use subsets of these.
Core parameters
| Parameter | Required | Description |
|---|---|---|
method | ✓ | Set to ACH |
externalId | Recommended | Your own reference ID, also the idempotency key. Retrying with the same value won't create a duplicate. Maximum 45 characters. |
type | Optional | Type of transaction. Possible values: REGULAR (a standard payout). Use REFUND only when refunding a completed payout, see Scenario 3. |
source.account.id (or externalId) | ✓ | The Passport Account to debit |
destination | ✓ | The bank account to credit. See Destination types for the supported shapes. |
amount | ✓ | Amount to send (in USD) |
purpose | ✓ | Purpose of the transaction. Maximum 128 characters. |
Destination types
The destination object takes one of two shapes, matching the two scenarios above.
Saved Contact (pay a third party you've saved, Scenario 1)
| Parameter | Required | Description |
|---|---|---|
destination.contact.id or externalId | ✓ | Unique identifier of the Contact |
destination.contact.externalAccount.id or externalId | ✓ | Unique identifier of the External Account linked to the Contact |
Inline bank details (one-time payout, nothing saved, Scenario 2)
| Parameter | Required | Description |
|---|---|---|
destination.externalAccount.holderName | ✓ | Name of the bank account holder as it appears on bank records |
destination.externalAccount.accountNumber | ✓ | The bank account number to be credited |
destination.externalAccount.routingNumber | ✓ | 9-digit routing number of the account |
destination.externalAccount.type | SAVING or CHECKING | |
destination.externalAccount.holderType | CONSUMER or CORPORATE |
Paying your own account?Referencing a saved External Account by
idsends funds to an account you own. That's a transfer, covered on Transfer funds, not here.
Processing detail
Optional as a whole, but certain fields may be required depending on your program's configuration (set by your Program Manager, the platform partner you onboarded through).
| Parameter | Required | Description |
|---|---|---|
processingDetail.processingMode | Recommended | FORWARD (default): settles next business day (end-of-day batch). SAME_DAY: settles same day if submitted before cutoff, otherwise next business day. |
processingDetail.companyName | Recommended | The payee name populated in the NACHA file. Defaults to your program's configuration if not provided. Maximum 16 characters. |
processingDetail.companyDescription | Recommended | Short description shown on the recipient's bank statement, such as PAYROLL or PURCHASE. Defaults to your program's configuration if not provided. Maximum 10 characters. Per NACHA rules (effective March 20, 2026), set PAYROLL for PPD credit transactions for wages or salaries, and PURCHASE for WEB debit transactions authorized for online consumer purchases. |
processingDetail.authType | Conditional | How authorization was obtained: WRITTEN (signed), PHONE (recorded call), or ONLINE (digital/web). |
processingDetail.addenda | Recommended | Reference data for reconciliation, such as an invoice number, customer reference ID, or order ID. Maximum 80 characters. |
processingDetail.location.id | Unique identifier of a location to link to the transaction, assigned by Passport. |
Refund fields
These apply only when type is REFUND (see Scenario 3). Source and destination are derived from the parent transaction, so you don't pass them.
| Parameter | Required | Description |
|---|---|---|
type | ✓ | Set to REFUND |
amount | ✓ | Amount to refund, in USD |
purpose | ✓ | Purpose of the refund |
processingDetail.parent.id | ✓ | The id of the original transaction being refunded |
processingDetail.achRefundCompanyName | Company name populated in the NACHA file for the refund | |
processingDetail.achRefundCompanyDescription | Company description populated in the NACHA file for the refund | |
reason | Reason for the refund, for example ON_CUSTOMER_REQUEST |
Validation
The prerequisites above are the validations, and PCE enforces them when you submit. A submission is checked synchronously; if a check fails, the request returns an error and no transaction is created. Submission checks:
- Required fields are present and correctly formatted.
- The source Passport Account is active and has enough balance to cover the payout.
- The destination resolves (a saved Contact, or valid one-time bank details).
Other conditions can only be judged once the payout is in flight, such as an inactive or blocked account, a compliance hold, or a bank return. These surface after creation as PENDING or FAILED; see Transaction lifecycle. For error codes, see Error Codes and Messages.
Statuses
Pay via ACH uses the shared transaction lifecycle. See it for every status, the reasons, and how to track a transaction.
Go live
The shared pre-production checklist (production credentials, webhook subscription and signature validation, idempotency, and sandbox testing, including failure and bank-return paths) is in Getting Started. Specific to paying via ACH, also confirm:
- Settlement timing (
FORWARDversusSAME_DAYcutoffs) is verified for your program.
Best practices
General practices (set a unique externalId, subscribe to webhooks, test in sandbox) are in Getting Started. Specific to paying via ACH:
| Practice | Description |
|---|---|
| Verify destination details | For one-time payments, ensure the account and routing numbers are accurate to avoid failures or misrouted funds. |
| Use the correct authorization type | Pass processingDetail.authType (WRITTEN, PHONE, ONLINE) to reflect how authorization was obtained. |
| Set meaningful NACHA descriptors | Populate processingDetail.companyName and companyDescription for clear identification on the recipient's bank statement. |
| Use addenda for reconciliation | Include invoice numbers or reference IDs via processingDetail.addenda to simplify reconciliation. |
Next steps
See also
- Save Payees as Contacts: save vendors for faster repeat payouts
- Transfer funds: move money to an account you own
- Pay via Wire (Domestic): same-day payouts
- Pay via Check and Pay via BOOK: other payout methods
- Error Codes and Messages: resolve a failed or returned payout
Updated about 15 hours ago