ACH Verify Transaction
Validate bank account information before processing payments
ACH Verification
An ACH Verify transaction validates a customer's bank account information without transferring funds. This is useful for confirming bank account details before processing payments or setting up recurring billing.
When to Use ACH Verify
| Use Case | Recommendation |
|---|---|
| Before first payment | Verify to catch invalid account details early |
| Setting up recurring payments | Verify and tokenize before the first scheduled payment |
| Updating payment methods | Verify new bank account before replacing existing |
| High-value transactions | Verify before processing large ACH debits |
| Braintree processor | Required - Must verify before sale transactions |
Important: When using Braintree as your processor, you must run a verify transaction before processing ACH sales. This is due to Braintree's bank account vaulting requirements.
Processor Support
Not all processors support ACH verification. Check the table below:
| Processor | Verify Supported | Verification Method | Notes |
|---|---|---|---|
| Braintree | ✅ Yes | Bank account vaulting | Required before sale |
| Payrix | ✅ Yes | Zero-dollar transaction | Test in test mode first |
| Adyen | ✅ Yes | Zero-dollar authorization | — |
| Adyen for Platforms | ✅ Yes | Zero-dollar authorization | — |
| Finix | ✅ Yes | Zero-dollar authorization | — |
| Authorize.Net | ✅ Yes | Zero-dollar authorization | — |
| AffiniPay | ✅ Yes | $1.00 authorization | Amount is not captured |
Note on Verification Depth: Zero-dollar verifications typically confirm that the routing number is valid and the account number format is correct. They may not verify account ownership, available balance, or that the account is open. For enhanced verification, some processors offer third-party verification services (like GIACT for Payrix).
Endpoint
POST /v1/transactions
Request Format
Using a Token (Recommended)
If you have an existing ACH token, use it directly:
{
"type": "verify",
"merchantId": "mid_abc123",
"payment": {
"token": "tkn_xyz789"
}
}Token Field Inheritance: If the token was created with
firstName,lastName,accountBusinessName, orachMandate, those values are automatically used for the transaction. You don't need to include them in the request.Overriding Token Fields: If you include
firstName,lastName,accountBusinessName, orachMandatein the request, those values will be used for the transaction and the token will be updated with the new values.
Using Raw Bank Account Details
Alternatively, you can pass bank account details directly:
{
"type": "verify",
"merchantId": "mid_abc123",
"payment": {
"bankAccount": {
"type": "personalChecking",
"account": "123456789",
"routing": "987654321",
"bankCountry": "USA"
}
},
"firstName": "John",
"lastName": "Doe"
}Verify with Tokenization
To create a multi-use token for future transactions, add ?tokenize=true:
POST /v1/transactions?tokenize=true{
"type": "verify",
"merchantId": "mid_abc123",
"payment": {
"bankAccount": {
"type": "personalChecking",
"account": "123456789",
"routing": "987654321",
"bankCountry": "USA"
}
},
"firstName": "John",
"lastName": "Doe"
}Verify for Braintree (ACH Mandate Required)
When using Braintree, you must include the achMandate field:
{
"type": "verify",
"merchantId": "mid_abc123",
"payment": {
"token": "tkn_xyz789"
},
"achMandate": "I authorize [Your Company] to verify my bank account and process future debits as agreed."
}Note: If your token was created with an
achMandate, you don't need to include it again unless you want to update the authorization text.
Request Fields
Required Fields
| Field | Type | Description |
|---|---|---|
type | string | Must be "verify" |
merchantId | string | The merchant ID |
payment.token | string | Existing ACH token (recommended) |
payment.bankAccount | object | Raw bank account details (alternative to token) |
Bank Account Fields (when not using token)
| Field | Type | Description |
|---|---|---|
bankAccount.type | string | Account type (see below) |
bankAccount.account | string | Account number (4-17 digits) |
bankAccount.routing | string | Routing number (9 digits) |
bankAccount.bankCountry | string | Must be "USA" |
Conditional Fields
| Field | Type | Condition | Description |
|---|---|---|---|
firstName | string | Personal accounts (if not on token) | Customer's first name |
lastName | string | Personal accounts (if not on token) | Customer's last name |
accountBusinessName | string | Corporate accounts (if not on token) | Business name |
achMandate | string | Braintree processor (if not on token) | Authorization text |
Note: When using a token, these conditional fields are only required if they weren't provided when the token was created. If included, they will override the token's stored values.
Account Types
| Value | Description |
|---|---|
personalChecking | Individual checking account |
personalSavings | Individual savings account |
corporateChecking | Business checking account |
corporateSavings | Business savings account |
Response Format
Successful Verification
{
"id": "txn_01abc123def456",
"type": "verify",
"currency": "USD",
"merchantId": "mid_abc123",
"authorization": {
"status": "A",
"processorCode": "00",
"processorMessage": "Bank account verified",
"approvedAmount": 0
},
"payment": {
"type": "personalChecking",
"last4": "6789",
"bin": "987654321"
},
"processor": {
"id": "pfmCon_xyz789",
"name": "Payrix",
"routingSource": "plan"
},
"firstName": "John",
"lastName": "Doe",
"createdOn": "2024-01-15T10:30:00Z"
}Verification with Token
When using ?tokenize=true:
{
"id": "txn_01abc123def456",
"type": "verify",
"currency": "USD",
"merchantId": "mid_abc123",
"authorization": {
"status": "A",
"processorCode": "00",
"processorMessage": "Bank account verified",
"approvedAmount": 0
},
"payment": {
"token": "tkn_xyz789",
"type": "personalChecking",
"last4": "6789",
"bin": "987654321"
},
"processor": {
"id": "pfmCon_xyz789",
"name": "Payrix",
"routingSource": "plan"
},
"firstName": "John",
"lastName": "Doe",
"createdOn": "2024-01-15T10:30:00Z"
}Failed Verification
{
"id": "txn_01abc123def456",
"type": "verify",
"currency": "USD",
"merchantId": "mid_abc123",
"authorization": {
"status": "D",
"processorCode": "R03",
"processorMessage": "No account/unable to locate account",
"approvedAmount": 0
},
"payment": {
"type": "personalChecking",
"last4": "6789",
"bin": "987654321"
},
"processor": {
"id": "pfmCon_xyz789",
"name": "Payrix",
"routingSource": "plan"
},
"firstName": "John",
"lastName": "Doe",
"createdOn": "2024-01-15T10:30:00Z"
}Braintree Verify-Before-Sale Flow
When using Braintree, follow this required flow:
Step 1: Verify bank account
↓
Step 2: Store the token from verify response
↓
Step 3: Use token for sale transactions
Step 1: Verify and Tokenize
POST /v1/transactions?tokenize=true
{
"type": "verify",
"merchantId": "mid_abc123",
"payment": {
"bankAccount": {
"type": "personalChecking",
"account": "123456789",
"routing": "987654321",
"bankCountry": "USA"
}
},
"firstName": "John",
"lastName": "Doe",
"achMandate": "I authorize Example Corp to debit my bank account."
}Step 2: Store the Token
Save the payment.token value from the response (e.g., tkn_xyz789) in your database associated with the customer.
Step 3: Process Sale with Token
{
"type": "sale",
"merchantId": "mid_abc123",
"amount": 5000,
"currency": "USD",
"payment": {
"token": "tkn_xyz789"
}
}Common Verification Failures
| Processor Code | Meaning | Recommended Action |
|---|---|---|
R01 | Insufficient funds | N/A for verify (no funds debited) |
R02 | Account closed | Ask customer for different account |
R03 | No account/unable to locate | Verify account number with customer |
R04 | Invalid account number | Customer entered incorrect account |
R07 | Authorization revoked | Customer revoked ACH authorization |
R10 | Customer advises not authorized | Customer disputes the authorization |
R16 | Account frozen | Ask customer for different account |
R20 | Non-transaction account | Account doesn't allow debits |
Best Practices
1. Always Verify Before Recurring Payments
async function setupRecurringPayment(customerId, bankAccount, customer) {
// Step 1: Verify the bank account
const verifyResult = await preczn.transactions.create({
type: 'verify',
merchantId: MERCHANT_ID,
payment: { bankAccount },
firstName: customer.firstName,
lastName: customer.lastName
}, { tokenize: true });
if (verifyResult.authorization.status !== 'A') {
throw new Error('Bank account verification failed');
}
// Step 2: Store the token for recurring use
await savePaymentMethod(customerId, verifyResult.payment.token);
return verifyResult.payment.token;
}2. Handle Verification Failures Gracefully
function handleVerificationResult(result) {
switch (result.authorization.status) {
case 'Approved':
return { success: true, token: result.payment.token };
case 'Declined':
return {
success: false,
message: 'We could not verify this bank account. Please check your account and routing numbers.',
code: result.authorization.processorCode
};
default:
return {
success: false,
message: 'There was an error verifying your bank account. Please try again.',
retry: true
};
}
}3. Verify Before High-Value Transactions
For transactions over a certain threshold, always verify first:
async function processLargePayment(amount, paymentMethod) {
const VERIFY_THRESHOLD = 100000; // $1,000.00
if (amount >= VERIFY_THRESHOLD && !paymentMethod.verified) {
const verifyResult = await verifyBankAccount(paymentMethod);
if (verifyResult.authorization.status !== 'A') {
throw new Error('Verification required for large transactions');
}
}
return processPayment(amount, paymentMethod);
}Error Messages
These are Preczn API errors returned when a request fails validation or processing. The error and message fields correspond to the API response format documented in the ACH Error Reference.
Bank Account Validation Errors
| Error | Message | Cause | Solution |
|---|---|---|---|
| Bad Request | Routing number is required | Missing routing field | Include the routing field in bankAccount |
| Bad Request | Routing number must be exactly 9 digits | Routing number length incorrect | Verify the routing number is exactly 9 digits |
| Bad Request | Routing number is invalid 'USA' bank routing number | Fails ABA checksum validation | Verify the routing number with customer's bank |
| Bad Request | Account number is required | Missing account field | Include the account field in bankAccount |
| Bad Request | Account number must be between 4 and 17 digits | Account number length invalid | Verify account number is 4-17 digits |
| Bad Request | Account type is required | Missing type field | Include the type field in bankAccount |
| Bad Request | Account type must be one of: PersonalChecking, PersonalSavings, CorporateChecking, CorporateSavings | Invalid account type | Use a valid account type value |
| Bad Request | Bank country is required | Missing bankCountry field | Include bankCountry: "USA" |
| Bad Request | Bank country must be one of: USA | Unsupported country | ACH only supports US bank accounts |
Customer Information Errors
| Error | Message | Cause | Solution |
|---|---|---|---|
| Bad Request | Personal ACH accounts require firstName and lastName | Personal account missing name fields | Include both firstName and lastName fields |
| Bad Request | Corporate ACH accounts require accountBusinessName or firstName and lastName | Corporate account missing identification | Include accountBusinessName or both firstName and lastName |
| Bad Request | ACH mandate is required for Braintree ACH transactions | Braintree requires authorization text | Include achMandate field |
Token Errors
| Error | Message | Cause | Solution |
|---|---|---|---|
| Bad Request | payment.token must be a valid token ID | Invalid or expired token | Verify the token ID is correct and exists |
Configuration Errors
| Error | Message | Cause | Solution |
|---|---|---|---|
| Bad Request | ACH transaction not supported for this Plan | Merchant's plan lacks ACH processor | Contact support to enable ACH |
For a complete list of errors, see the ACH Error Reference.
Updated 2 days ago
