Skip to main content
Accept ACH payments and manage bank account verification.

Overview

Atlas supports ACH (Automated Clearing House) payments for US bank accounts. ACH is ideal for:
  • Recurring billing and subscriptions
  • Large transactions where card fees are prohibitive
  • B2B payments
  • Payouts and disbursements

Payment Flow

  1. Collect bank details - Use the Bank Account Element or API
  2. Verify the account - Instant verification or microdeposits
  3. Create a mandate - Customer authorizes debits
  4. Process payments - Debit the account

Bank Account Element

import { AtlasProvider, BankAccountElement } from '@atlas/react';

function BankForm() {
  return (
    <AtlasProvider publishableKey="pk_test_xxx">
      <BankAccountElement
        onSuccess={(bankAccount) => {
          console.log('Bank account:', bankAccount.id);
          console.log('Last 4:', bankAccount.last4);
        }}
        onError={(error) => console.error(error)}
      />
    </AtlasProvider>
  );
}

API Reference

Create Bank Account

curl -X POST https://api.atlas.co/functions/v1/bank-accounts \
  -H "Authorization: Bearer sk_test_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "account_holder_name": "John Doe",
    "account_holder_type": "individual",
    "routing_number": "110000000",
    "account_number": "000123456789",
    "account_type": "checking"
  }'
Response:
{
  "id": "ba_abc123",
  "object": "bank_account",
  "account_holder_name": "John Doe",
  "account_holder_type": "individual",
  "bank_name": "Test Bank",
  "last4": "6789",
  "routing_number": "110000000",
  "account_type": "checking",
  "status": "pending_verification",
  "created_at": "2025-01-21T12:00:00Z"
}

Verify with Microdeposits

For accounts that can’t be instantly verified:
curl -X POST https://api.atlas.co/functions/v1/bank-accounts/ba_abc123/verify \
  -H "Authorization: Bearer sk_test_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "amounts": [32, 45]
  }'

Create ACH Mandate

Authorize the customer to allow debits:
curl -X POST https://api.atlas.co/functions/v1/bank-mandates \
  -H "Authorization: Bearer sk_test_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "bank_account_id": "ba_abc123",
    "customer_id": "cus_xyz789",
    "mandate_type": "recurring",
    "ip_address": "192.168.1.1",
    "user_agent": "Mozilla/5.0..."
  }'

Process ACH Payment

curl -X POST https://api.atlas.co/functions/v1/bank-transfers \
  -H "Authorization: Bearer sk_test_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "bank_account_id": "ba_abc123",
    "mandate_id": "mandate_def456",
    "amount": 10000,
    "currency": "USD",
    "description": "Invoice #1234"
  }'

ACH Timeline

StageTiming
InitiatedImmediate
Submitted to networkSame business day (before 4pm ET)
Pending1-2 business days
Succeeded/Failed3-5 business days

Test Bank Accounts

RoutingAccountResult
110000000000123456789Succeeds
110000000000111111116Fails verification
110000000000111111113Account closed

Webhooks

Listen for ACH events:
  • bank_transfer.initiated
  • bank_transfer.pending
  • bank_transfer.succeeded
  • bank_transfer.failed
  • bank_transfer.returned
app.post('/webhooks/atlas', (req, res) => {
  const event = req.body;

  switch (event.type) {
    case 'bank_transfer.succeeded':
      await fulfillOrder(event.data.metadata.order_id);
      break;
    case 'bank_transfer.failed':
    case 'bank_transfer.returned':
      await handleFailedTransfer(event.data);
      break;
  }

  res.json({ received: true });
});

Return Codes

If an ACH transfer fails, check the return_code:
CodeDescription
R01Insufficient funds
R02Account closed
R03No account/unable to locate
R04Invalid account number
R07Authorization revoked
R08Payment stopped
R10Customer advises not authorized
R29Corporate customer advises not authorized