ACH Bank Details

Get ACH bank account details for receiving USD transfers.

Before You Start

Read the following guides before proceeding:

GuideWhy
Getting StartedPlatform overview and setup
Api BasicsRequired headers and request configuration
AuthenticationHow to obtain access tokens
OnboardingUser and wallet registration
KYCKYC verification requirements
RecipientsRecipient management

Overview

Users residing in supported countries can receive USD transfers via ACH. Unlike SEPA accounts which are provisioned automatically, ACH accounts require manual activation. Incoming ACH transfers are automatically converted to the user's unified balance (WUSD).


Checking ACH Availability

Before activating an ACH account, verify that the user has access by checking their capabilities.

GET /api/v2/user

The response includes the AchAccount capability:

{
  "capabilities": [
    {
      "type": "AchAccount",
      "status": "ActivationNotStarted"
    }
  ]
}

Capability Status

StatusDescription
ActiveACH account is provisioned and ready to use
ActivationNotStartedAvailable but requires activation via API
InProgressActivation requested, waiting for provisioning
NotFulfilledUser needs to complete additional verification first
NotAvailableNot available for user's country of residence

See Bank Account Availability for supported countries.


Activate ACH Account

When capability status is ActivationNotStarted, call this endpoint to provision the account.

POST /api/v1/bank/accounts

Request Body

FieldTypeRequiredDescription
account_typestringYesMust be Ach

Code Examples

const response = await fetch(`${baseUrl}/api/v1/bank/accounts`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'X-User-Address': userEoaAddress,
    'X-Chain-Id': chainId,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    account_type: 'Ach'
  })
});

const result = await response.json();
console.log('Details ID:', result.details_id);
response = requests.post(
    f"{base_url}/api/v1/bank/accounts",
    headers={
        "Authorization": f"Bearer {access_token}",
        "X-User-Address": user_eoa_address,
        "X-Chain-Id": chain_id,
        "Content-Type": "application/json"
    },
    json={
        "account_type": "Ach"
    }
)

result = response.json()
print("Details ID:", result["details_id"])
body := map[string]string{
    "account_type": "Ach",
}

jsonBody, _ := json.Marshal(body)
req, _ := http.NewRequest("POST", baseURL+"/api/v1/bank/accounts", bytes.NewBuffer(jsonBody))
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("X-User-Address", userEoaAddress)
req.Header.Set("X-Chain-Id", chainId)
req.Header.Set("Content-Type", "application/json")

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()

var result BankAccountActivateResponse
json.NewDecoder(resp.Body).Decode(&result)

Response

{
  "details_id": "8d5a65eb59d94afea64374d45591fe9f"
}

After activation, the capability status changes to InProgress. You will receive a webhook when the account is ready.


Get Bank Accounts

Retrieve the user's bank account details including ACH information.

GET /api/v1/bank/accounts

Query Parameters

ParameterTypeDescription
page_numberintegerPage number, 0-indexed (default: 0)
page_sizeintegerItems per page (default: 25)

Code Examples

const response = await fetch(`${baseUrl}/api/v1/bank/accounts`, {
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'X-User-Address': userEoaAddress,
    'X-Chain-Id': chainId
  }
});
const { accounts } = await response.json();

const achAccount = accounts.find(a => a.account_type === 'Ach');
if (achAccount) {
  console.log('Account Number:', achAccount.details.account_number);
  console.log('Routing Number:', achAccount.details.routing_number);
}
response = requests.get(
    f"{base_url}/api/v1/bank/accounts",
    headers={
        "Authorization": f"Bearer {access_token}",
        "X-User-Address": user_eoa_address,
        "X-Chain-Id": chain_id
    }
)
accounts = response.json()["accounts"]

ach_account = next((a for a in accounts if a["account_type"] == "Ach"), None)
if ach_account:
    print("Account Number:", ach_account["details"]["account_number"])
    print("Routing Number:", ach_account["details"]["routing_number"])
req, _ := http.NewRequest("GET", baseURL+"/api/v1/bank/accounts", nil)
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("X-User-Address", userEoaAddress)
req.Header.Set("X-Chain-Id", chainId)

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()

var result struct {
    Accounts []BankAccountResponse `json:"accounts"`
}
json.NewDecoder(resp.Body).Decode(&result)

for _, account := range result.Accounts {
    if account.AccountType == "Ach" {
        fmt.Println("Account Number:", account.Details.AccountNumber)
        fmt.Println("Routing Number:", account.Details.RoutingNumber)
    }
}

Response

{
  "accounts": [
    {
      "id": "1334726cbd7641c09b4124e3e52f53fe:8d5a65eb59d94afea64374d45591fe9f",
      "currency": "USD",
      "status": "Active",
      "account_holder": "Alex Grey",
      "account_type": "Ach",
      "details": {
        "account_number": "8310931284",
        "routing_number": "026073150"
      }
    }
  ]
}

Response Fields

FieldDescription
idComposite identifier in format {account_id}:{details_id}. The account_id corresponds to the account id from webhooks, and details_id corresponds to the details id from webhooks.
currencyAccount currency (USD for ACH)
statusAccount status: Active, Pending, Blocked, Closed
account_holderName on the account
account_typeAch for ACH accounts
details.account_numberBank account number for receiving transfers
details.routing_numberABA routing number

Account Status

StatusDescription
PendingAccount details being provisioned
ActiveReady to receive transfers
BlockedTemporarily suspended
ClosedPermanently closed

Receiving ACH Transfers

To receive an ACH transfer:

  1. Activate the ACH account if not already active
  2. Retrieve the user's ACH account details using the endpoint above
  3. Provide the account number and routing number to the sender
  4. Monitor incoming transfers via the Activities webhook

Important Notes

  • Incoming USD is automatically converted to WUSD in the user's unified balance
  • ACH transfers typically settle within 1-3 business days

Webhooks

Bank Account Updates

Wirex sends webhook notifications when bank account details are created or updated.

Endpoint: POST {your_webhook_base_url}/webhook/accounts/fiat

Account Created

{
  "change_type": "Created",
  "id": "1334726cbd7641c09b4124e3e52f53fe",
  "account_type": "Fiat",
  "currency": "USD",
  "status": "Active",
  "balance": {
    "amount": 0,
    "available_amount": 0
  },
  "details": [],
  "owner_type": "Personal",
  "created_at": "2024-01-15T10:00:00Z"
}

Details Changed (ACH Details Added)

{
  "change_type": "DetailsChanged",
  "id": "1334726cbd7641c09b4124e3e52f53fe",
  "account_type": "Fiat",
  "currency": "USD",
  "status": "Active",
  "balance": {
    "amount": 0,
    "available_amount": 0
  },
  "details": [
    {
      "id": "8d5a65eb59d94afea64374d45591fe9f",
      "type": "Ach",
      "status": "Pending",
      "transport_currency": "USD"
    }
  ],
  "owner_type": "Personal"
}

Incoming ACH Transfer

When an ACH transfer is received, you receive an activity webhook.

Endpoint: POST {your_webhook_base_url}/v2/webhooks/activities

{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "user_address": "0x7e1Cc1685D68D486b22D3880030C24434E3b90a9",
  "type": "AchPush",
  "status": "Completed",
  "direction": "Inbound",
  "source": {
    "type": "AchBankAccount",
    "bank_account": {
      "account_number": "",
      "routing_number": "026073150",
      "owner_name": "Alex Grey",
      "is_business": false
    }
  },
  "destination": {
    "type": "Wallet",
    "wallet": {
      "address": "0xAAFF0821A09A1Aac28B72dD3Ff410A7ea5FEb874"
    }
  },
  "source_amount": {
    "amount": 500.00,
    "currency": "USD"
  },
  "destination_amount": {
    "amount": 500.00,
    "token_symbol": "WUSD",
    "token_address": "0x0774164DC20524Bb239b39D1DC42573C3E4C6976"
  },
  "operations": [
    {
      "hash": "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234",
      "operation_amount": {
        "amount": 500.00,
        "token_symbol": "WUSD",
        "token_address": "0x0774164DC20524Bb239b39D1DC42573C3E4C6976"
      },
      "transaction_amount": {
        "amount": 500.00,
        "currency": "USD"
      },
      "rate": {
        "ticker": "USD/WUSD",
        "rate": 1
      }
    }
  ],
  "created_at": "2024-01-01T08:00:00.000Z",
  "activity_steps": [
    {
      "type": "Initiated",
      "status": "Completed",
      "created_at": "2024-01-01T08:00:00.000Z",
      "completed_at": "2024-01-01T08:00:00.000Z"
    },
    {
      "type": "BankIn",
      "status": "Completed",
      "created_at": "2024-01-01T08:00:00.000Z",
      "completed_at": "2024-01-01T08:05:00.000Z"
    },
    {
      "type": "CryptoIn",
      "status": "Completed",
      "created_at": "2024-01-01T08:05:00.000Z",
      "completed_at": "2024-01-01T08:05:15.000Z"
    }
  ]
}

Error Handling

Validation Errors

Error ReasonDescription
ErrorNotFoundUser not found or not verified
ErrorInvalidStatusUser account not active
ErrorInvalidFieldInvalid account type or capability not available

Error Response Format

{
  "error_reason": "ErrorInvalidField",
  "error_description": "Account capability does not need activation or is not available",
  "error_category": {
    "category": "CategoryValidationFailure",
    "http_status_code": 400
  },
  "error_details": [
    {
      "key": "field",
      "details": "account_type"
    }
  ]
}