Account & Funds Management via API
If you haven't already, Sign Up for Sandbox Access to get your client ID and secret to work through this Build Guide!
What is Account & Funds Management?
Account & Funds Management encompasses the complete lifecycle of opening accounts for your customers, obtaining deposit instructions, monitoring account status changes, and tracking incoming funds. This guide will walk you through best practices for managing accounts and funds programmatically via the Rail API.
Rail supports multiple account types and currencies, allowing you to:
- Open deposit accounts in various fiat and crypto currencies
- Monitor account status changes through webhooks
- Generate deposit instructions for customers to send funds
- Track incoming deposits and transactions in real-time
Core API Flow Sequence
The standard API account management flow follows these key steps:
-
POST
/v1/accounts/deposits- Open a new account -
GET
/v1/accounts/deposits/{id}- Check account status -
POST
/v1/subscriptions- Subscribe to account webhooks -
POST
/v1/deposits- Get deposit instructions -
GET
/v1/accounts/deposits/{id}/transactions- Track incoming deposits
Build Guide
This guide should take no longer than 15 minutes and will allow you to open an account, set up monitoring, and track incoming funds in Sandbox.
By the end of this guide you will have:
- Authenticated your request
- Opened a new account for a customer
- Verified the account is ready
- Set up webhook subscriptions for monitoring
- Retrieved deposit instructions
- Tracked incoming deposits
Let's go!
Authenticating your request
Every request you make to a Rail API endpoint requires an AUTH_TOKEN. We secure our endpoints using standards based OAuth2 Client Credentials Grant and scopes.
To obtain the AUTH_TOKEN, you will need to authorize your account using your BASE_64_ENCODED_CLIENTID_AND_SECRET with the scopes you require:
curl --location --request POST 'https://auth.layer2financial.com/oauth2/ausbdqlx69rH6OjWd696/v1/token?grant_type=client_credentials&scope=accounts:read+accounts:write+deposits:read+deposits:write+subscriptions:read+subscriptions:write' \
--header 'Accept: application/json' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Cache-Control: no-cache' \
--header 'Authorization: Basic {BASE_64_ENCODED_CLIENTID_AND_SECRET}'This gives you a response object containing your AUTH_TOKEN:
{
"token_type": "Bearer",
"expires_in": 43200,
"access_token": {AUTH_TOKEN},
"scope": "accounts:read accounts:write deposits:read deposits:write subscriptions:read subscriptions:write"
}The scopes we'll need for this tutorial are:
-
accounts:readandaccounts:writeto open and query accounts -
deposits:readanddeposits:writeto get deposit instructions and track deposits -
subscriptions:readandsubscriptions:writeto set up webhooks for monitoring
The full list of scopes are available here.
Step 1: Open a New Account
Open a new account for a customer by calling the /v1/accounts/deposits endpoint.
You'll need:
-
The
customer_idfrom your onboarded customer (see Customer Onboarding ) -
An
account_idthat will be the unique identifier for this account (e.g.,CUSTOMERNAME_USD_001) -
The
product_id: The type of account to open. UseDEPOSIT_BASICfor standard deposit accounts -
The
asset_type_id: The currency/asset type for the account (e.g.,FIAT_TESTNET_USDfor USD in sandbox)
curl --location --request POST 'https://sandbox.layer2financial.com/api/v1/accounts/deposits' \
--header 'Authorization: Bearer {AUTH_TOKEN}' \
--header 'Content-Type: application/json' \
--data-raw '{
"customer_id": "INDIVIDUAL_CUSTOMER_001",
"account_to_open": {
"account_id": "CUSTOMER_USD_001",
"product_id": "DEPOSIT_BASIC",
"asset_type_id": "FIAT_TESTNET_USD"
}
}'The response contains the account_id you provided:
{
"data": {
"id": "CUSTOMER_USD_001"
}
}Important: Name your accounts appropriately for your business needs (e.g.,
CustomerName_USD_001,CustomerName_EUR_002) to make them easy to identify and manage.
Step 2: Check Account Status
Before you can request deposit instructions, the account must reach OPEN status. Check the account status by calling the /v1/accounts/deposits/{id} endpoint:
curl --location --request GET 'https://sandbox.layer2financial.com/api/v1/accounts/deposits/CUSTOMER_USD_001' \
--header 'Authorization: Bearer {AUTH_TOKEN}' \
--header 'Content-Type: application/json'The response shows the account status and balance information:
{
"data": {
"id": "CUSTOMER_USD_001",
"status": "OPEN",
"asset_type_id": "FIAT_TESTNET_USD",
"product_type": "DEPOSIT",
"product_id": "DEPOSIT_BASIC",
"customer_id": "INDIVIDUAL_CUSTOMER_001",
"current_balance": 0,
"available_balance": 0
}
}Account Status Meanings
Accounts progress through different statuses during their lifecycle:
- PENDING - Account is being set up and not yet ready for transactions
- OPEN - Account is fully ready and can receive deposit instructions
- FROZEN - Account has been temporarily locked for all actions (deposits, transfers, exchanges, withdrawals)
- EDD - Account requires enhanced due diligence. The account will operate as if frozen
- CLOSED - Account is permanently closed, no further actions can be performed
- DORMANT - No activity seen in the last 12 months. Account is still available for use
Critical Rule: Deposit instructions will only be available when the account status is
OPEN- not when it's inPENDINGor any other status. Always verify the account status before attempting to get deposit instructions.
When to Show Account as "Ready" to End Users
Scenario: You want to display an account card or enable account features in your UI only when the account is truly ready to receive funds.
The Most Reliable Approach: Don't rely solely on the account status being OPEN. Instead, actually request deposit instructions and confirm they are available. Here's why:
There may be scenarios where an account shows OPEN status, but the underlying banking provider has not yet completed their approval process. In these edge cases:
-
The account status will be
OPENin Rail's system - However, deposit instructions may not yet be available
- Attempting to get deposit instructions will return an error or empty response
Recommended Implementation:
# Step 1: Check account status
GET /v1/accounts/deposits/{account_id}
# Step 2: If status is OPEN, attempt to get deposit instructions
POST /v1/deposits
# Step 3: Only show account as "ready" if deposit instructions are successfully returnedThis two-step verification ensures that:
-
The account is marked as
OPENby Rail - The underlying provider has approved and deposit instructions are actually available
- Your customer can immediately use the account without errors
Example Flow:
// Pseudo-code for checking account readiness
async function isAccountReady(accountId) {
// Check account status
const account = await getAccount(accountId);
if (account.status !== 'OPEN') {
return false;
}
// Verify deposit instructions are available
try {
const deposit = await getDepositInstructions(accountId);
return deposit.deposit_instructions && deposit.deposit_instructions.length > 0;
} catch (error) {
// Instructions not yet available
return false;
}
}
// Only show account card when truly ready
if (await isAccountReady(accountId)) {
displayAccountCard();
}This approach provides the best user experience and prevents showing accounts as "ready" when they cannot actually receive funds yet.
Retrieving Multiple Accounts
You can also retrieve all accounts for a customer using the /v1/accounts/deposits endpoint with query parameters:
curl --location --request GET 'https://sandbox.layer2financial.com/api/v1/accounts/deposits?customer_id=INDIVIDUAL_CUSTOMER_001&status=OPEN' \
--header 'Authorization: Bearer {AUTH_TOKEN}' \
--header 'Content-Type: application/json'This returns a paginated list of accounts:
{
"data": {
"accounts": [
{
"id": "CUSTOMER_USD_001",
"status": "OPEN",
"asset_type_id": "FIAT_TESTNET_USD",
"product_type": "DEPOSIT",
"product_id": "DEPOSIT_BASIC",
"customer_id": "INDIVIDUAL_CUSTOMER_001",
"current_balance": 0,
"available_balance": 0
}
]
},
"links": {
"self": "/v1/accounts/deposits?customer_id=INDIVIDUAL_CUSTOMER_001&status=OPEN&page=0&page_size=20",
"first": "/v1/accounts/deposits?customer_id=INDIVIDUAL_CUSTOMER_001&status=OPEN&page=0&page_size=20",
"prev": null,
"next": null,
"last": "/v1/accounts/deposits?customer_id=INDIVIDUAL_CUSTOMER_001&status=OPEN&page=0&page_size=20"
}
}Step 3: Create Webhook Subscriptions
Instead of constantly polling for account status changes, set up webhook subscriptions to receive real-time notifications. This is the recommended approach for production systems.
Subscribe to Account Status Updates
Create a subscription for account status changes using the /v1/subscriptions endpoint:
curl --location --request POST 'https://sandbox.layer2financial.com/api/v1/subscriptions' \
--header 'Authorization: Bearer {AUTH_TOKEN}' \
--header 'Content-Type: application/json' \
--data-raw '{
"event_type": "ACCOUNT_OPEN",
"callback_url": "https://your-domain.com/webhooks/account-open"
}'The response includes a signature verification key for validating webhook payloads:
{
"data": {
"id": "sub_123456789",
"event_type": "ACCOUNT_OPEN",
"callback_url": "https://your-domain.com/webhooks/account-open",
"signature_verification_key": "whsec_abc123..."
}
}Subscribe to Deposit Instructions Updates
Subscribe to the ACCOUNT_DEPOSIT_INSTRUCTIONS_UPDATE webhook to know when deposit instructions become available or are updated:
curl --location --request POST 'https://sandbox.layer2financial.com/api/v1/subscriptions' \
--header 'Authorization: Bearer {AUTH_TOKEN}' \
--header 'Content-Type: application/json' \
--data-raw '{
"event_type": "ACCOUNT_DEPOSIT_INSTRUCTIONS_UPDATE",
"callback_url": "https://your-domain.com/webhooks/deposit-instructions"
}'Why This Matters: Deposit instructions on an account may be updated over time. Account numbers and banking details can change, so listening to this webhook ensures you always have current information.
Subscribe to Transaction Events
Set up subscriptions for tracking incoming deposits:
# Subscribe to pending transactions
curl --location --request POST 'https://sandbox.layer2financial.com/api/v1/subscriptions' \
--header 'Authorization: Bearer {AUTH_TOKEN}' \
--header 'Content-Type: application/json' \
--data-raw '{
"event_type": "TRANSACTION_PENDING",
"callback_url": "https://your-domain.com/webhooks/transaction-pending"
}'
# Subscribe to posted transactions
curl --location --request POST 'https://sandbox.layer2financial.com/api/v1/subscriptions' \
--header 'Authorization: Bearer {AUTH_TOKEN}' \
--header 'Content-Type: application/json' \
--data-raw '{
"event_type": "TRANSACTION_POSTED",
"callback_url": "https://your-domain.com/webhooks/transaction-posted"
}'Recommended Webhook Events for Account Management
| Event Type | When Triggered | Use Case |
|---|---|---|
ACCOUNT_OPEN |
Account status changes to OPEN | Know when account is ready for deposit instructions |
ACCOUNT_DEPOSIT_INSTRUCTIONS_UPDATE |
Deposit instructions become available or are updated | Get notified of deposit instruction changes |
ACCOUNT_FROZEN |
Account is frozen | Alert customer of account restrictions |
ACCOUNT_EDD |
Account requires enhanced due diligence | Take appropriate compliance actions |
TRANSACTION_PENDING |
Deposit detected but not yet posted | Notify customer of incoming funds |
TRANSACTION_POSTED |
Deposit approved and posted to account | Update customer balance and confirm receipt |
For more details on webhooks, see the Subscriptions Guide.
Step 4: Get Deposit Instructions
Once the account status is OPEN, request deposit instructions using the /v1/deposits endpoint.
Supported Payment Rails by Currency
Different currencies support different payment rails. For USD accounts, you can receive instructions for:
- ACH - Automated Clearing House (domestic US transfers)
- FEDWIRE - Federal Reserve Wire Network (domestic US transfers)
- SWIFT - Society for Worldwide Interbank Financial Telecommunication (international transfers)
For the complete list of supported payment rails for each currency, see the Assets Guide.
Request Deposit Instructions
Create a PUSH deposit to get the instructions customers need to send funds:
curl --location --request POST 'https://sandbox.layer2financial.com/api/v1/deposits' \
--header 'Authorization: Bearer {AUTH_TOKEN}' \
--header 'Content-Type: application/json' \
--data-raw '{
"deposit_type": "PUSH",
"deposit_destination": {
"destination_account_id": "CUSTOMER_USD_001"
},
"customer_name": "Jane Doe"
}'Understanding the Response
For FIAT_TESTNET_USD accounts, you'll receive ACH, FEDWIRE, and SWIFT instruction types (production accounts for FIAT_MAINNET_USD support all three rails):
{
"data": {
"id": "deposit_987654321",
"status": "REQUESTED",
"created_timestamp": "2023-11-20T14:32:18.123456Z",
"deposit_type": "PUSH",
"deposit_destination": {
"destination_account_id": "CUSTOMER_USD_001",
"asset_type_id": "FIAT_TESTNET_USD"
},
"customer_name": "Jane Doe",
"deposit_source": {
"deposit_instructions": [
{
"instruction_type": "ACH",
"account_holder_name": "Jane Doe",
"account_number": "9695859732",
"account_routing_number": "715960587",
"description": "Deposit to CUSTOMER_USD_001",
"asset_type_id": "FIAT_TESTNET_USD"
},
{
"instruction_type": "FEDWIRE",
"account_holder_name": "Jane Doe",
"account_number": "9695859732",
"account_routing_number": "715960587",
"account_holder_address": {
"address_line1": "123 Main Street",
"city": "Boston",
"state": "MA",
"postal_code": "02101",
"country_code": "US"
},
"institution_name": "Rail Banking Services",
"institution_address": {
"address_line1": "1 Financial Place",
"city": "Boston",
"state": "MA",
"postal_code": "02101",
"country_code": "US"
},
"memo": "ea947e78-50ba-40b0-8e6e-32345eeb2cab",
"asset_type_id": "FIAT_TESTNET_USD"
},
{
"instruction_type": "SWIFT",
"account_holder_name": "Jane Doe",
"account_number": "9695859732",
"swift_code": "RAILUSXX",
"account_holder_address": {
"address_line1": "123 Main Street",
"city": "Boston",
"state": "MA",
"postal_code": "02101",
"country_code": "US"
},
"institution_name": "Rail Banking Services",
"institution_address": {
"address_line1": "1 Financial Place",
"city": "Boston",
"state": "MA",
"postal_code": "02101",
"country_code": "US"
},
"reference": "CUSTOMER_USD_001",
"asset_type_id": "FIAT_TESTNET_USD"
}
]
}
}
}Deposit Instructions for Crypto Accounts
For crypto accounts, you'll receive blockchain addresses:
curl --location --request POST 'https://sandbox.layer2financial.com/api/v1/deposits' \
--header 'Authorization: Bearer {AUTH_TOKEN}' \
--header 'Content-Type: application/json' \
--data-raw '{
"deposit_type": "PUSH",
"deposit_destination": {
"destination_account_id": "CUSTOMER_USDC_001"
},
"customer_name": "Jane Doe"
}'Response for crypto account:
{
"data": {
"id": "deposit_crypto_123",
"status": "REQUESTED",
"created_timestamp": "2023-11-20T14:35:22.456789Z",
"deposit_type": "PUSH",
"deposit_destination": {
"destination_account_id": "CUSTOMER_USDC_001",
"asset_type_id": "ETHEREUM_GOERLI_USDC"
},
"customer_name": "Jane Doe",
"deposit_source": {
"deposit_instructions": [
{
"instruction_type": "CRYPTO",
"address": "0x9ED9961A87ba49511Cf522059df52e2a94eF188b",
"blockchain": "ethereum",
"network": "goerli",
"asset_type_id": "ETHEREUM_GOERLI_USDC"
}
]
}
}
}Important: The
/v1/depositsendpoint is the primary and most reliable method for getting deposit instructions. When the account status isOPEN, this endpoint will consistently return the appropriate instruction types for the account's asset type. For USD accounts, this includes ACH, FEDWIRE, and SWIFT instructions. See the Assets Guide for supported payment rails by currency.
Step 5: Track Incoming Deposits
Once deposit instructions are shared with customers and they send funds, you need to track when those funds arrive.
Understanding Deposits vs Transactions
-
Deposit
- The request for deposit instructions (the
idreturned from/v1/deposits) -
Transaction
- The actual movement of funds into the account (linked via
category_id)
When a customer sends funds using the deposit instructions, Rail creates a transaction linked to the deposit.
Important Note: A transaction appearing in the Rail system means the deposit has reached our platform. Rail does not have the ability to track the status of deposits before they hit our platform (e.g., we cannot see that a wire transfer has been initiated but not yet received). Transaction notifications only occur once funds have actually arrived at Rail's banking partners.
Webhook Sequence for Incoming Deposits
When a deposit is received, you'll receive webhooks in this sequence:
-
TRANSACTION_PENDING- Sent when Rail detects the incoming deposit -
TRANSACTION_POSTED- Sent once monitoring is completed and the transaction is approved
Both webhooks include the transaction details with the category_id matching your deposit id.
Example TRANSACTION_PENDING webhook payload:
{
"event_type": "TRANSACTION_PENDING",
"data": {
"transaction_id": "txn_abc123",
"account_id": "CUSTOMER_USD_001",
"amount": 1000.00,
"asset_type_id": "FIAT_TESTNET_USD",
"category": "DEPOSIT",
"category_id": "deposit_987654321",
"status": "PENDING",
"created_timestamp": "2023-11-20T15:00:00.000000Z"
}
}API Polling Method
Alternatively, you can poll for transactions using the account's transaction endpoint:
curl --location --request GET 'https://sandbox.layer2financial.com/api/v1/accounts/deposits/CUSTOMER_USD_001/transactions' \
--header 'Authorization: Bearer {AUTH_TOKEN}' \
--header 'Content-Type: application/json'Response with transaction details:
{
"data": {
"transactions": [
{
"id": "txn_abc123",
"account_id": "CUSTOMER_USD_001",
"status": "POSTED",
"amount": 1000.00,
"asset_type_id": "FIAT_TESTNET_USD",
"category": "DEPOSIT",
"category_id": "deposit_987654321",
"created_timestamp": "2023-11-20T15:00:00.000000Z",
"posted_timestamp": "2023-11-20T15:05:00.000000Z"
}
]
},
"links": {
"self": "/v1/accounts/deposits/CUSTOMER_USD_001/transactions?page=0&page_size=20",
"first": "/v1/accounts/deposits/CUSTOMER_USD_001/transactions?page=0&page_size=20",
"prev": null,
"next": null,
"last": "/v1/accounts/deposits/CUSTOMER_USD_001/transactions?page=0&page_size=20"
}
}Filter Transactions by Deposit
To find transactions for a specific deposit, filter by category and category_id:
curl --location --request GET 'https://sandbox.layer2financial.com/api/v1/accounts/deposits/CUSTOMER_USD_001/transactions?category=DEPOSIT&category_id=deposit_987654321' \
--header 'Authorization: Bearer {AUTH_TOKEN}' \
--header 'Content-Type: application/json'Check Updated Account Balance
After a transaction is posted, verify the account balance was updated:
curl --location --request GET 'https://sandbox.layer2financial.com/api/v1/accounts/deposits/CUSTOMER_USD_001' \
--header 'Authorization: Bearer {AUTH_TOKEN}' \
--header 'Content-Type: application/json'The response shows the updated balances:
{
"data": {
"id": "CUSTOMER_USD_001",
"status": "OPEN",
"asset_type_id": "FIAT_TESTNET_USD",
"product_type": "DEPOSIT",
"product_id": "DEPOSIT_BASIC",
"customer_id": "INDIVIDUAL_CUSTOMER_001",
"current_balance": 1000.00,
"available_balance": 1000.00
}
}API Account Management Best Practices
1. Always Verify Account Status Before Requesting Deposit Instructions
Deposit instructions will only be available when the account status is OPEN. Attempting to get instructions for accounts in PENDING or other statuses will fail.
Recommended Flow:
# 1. Check account status
GET /v1/accounts/deposits/{account_id}
# 2. Only proceed if status is "OPEN"
if status == "OPEN":
POST /v1/deposits2. Use Webhooks Instead of Polling (When Possible)
Set up webhook subscriptions for real-time notifications rather than continuously polling endpoints:
-
ACCOUNT_OPEN- Know immediately when account is ready -
ACCOUNT_DEPOSIT_INSTRUCTIONS_UPDATE- Get notified of instruction changes -
TRANSACTION_PENDINGandTRANSACTION_POSTED- Track incoming funds in real-time
Benefits:
- Reduces API load and costs
- Faster response times
- More reliable than polling intervals
- Scales better for high-volume operations
Note: If webhooks don't work for your setup (e.g., firewall restrictions, infrastructure limitations, or compliance requirements), polling can be a reliable replacement. Implement polling with appropriate intervals (e.g., every 30-60 seconds for account status, every 2-5 minutes for transactions) and proper exponential backoff for rate limiting.
3. Monitor Deposit Instructions for Changes
Account numbers and banking details can change over time. Always:
-
Subscribe to
ACCOUNT_DEPOSIT_INSTRUCTIONS_UPDATEwebhooks - Periodically refresh deposit instructions for long-lived accounts
- Update your UI/systems when instructions change
4. Never Use Reserve Accounts for Deposits
Reserve accounts are internal system accounts and won't work for customer deposits. Always:
- Create new accounts specifically for customer transactions
-
Use meaningful naming conventions (e.g.,
CustomerName_Currency_SequenceNumber) -
Verify the
product_typeisDEPOSIT, notCLIENT
5. Handle Transaction States Properly
Transactions progress through states:
-
PENDING- Detected but not yet approved (don't credit customer yet) -
POSTED- Approved and settled (safe to credit customer) -
CANCELLED- Transaction was cancelled (reverse any credits)
Always wait for TRANSACTION_POSTED before considering funds available to the customer.
6. Link Transactions to Deposits Using category_id
When querying transactions, use the deposit id as the category_id filter:
GET /v1/accounts/deposits/{account_id}/transactions?category=DEPOSIT&category_id={deposit_id}This ensures you're tracking the correct transactions for specific deposit requests.
7. Implement Proper Error Handling
Common scenarios to handle:
-
Account not yet
OPENwhen requesting deposit instructions - Network timeouts when polling for status
- Webhook delivery failures (implement retry logic)
- Duplicate transaction notifications (use idempotency)
Summary
Let's review the account and funds management process:
Core Workflow
-
Authenticate
- Get your
AUTH_TOKENusing OAuth2 -
Open Account
- POST to
/v1/accounts/depositswith customer details and asset type -
Verify Status
- GET
/v1/accounts/deposits/{id}until status isOPEN -
Set Up Webhooks
- POST to
/v1/subscriptionsfor account and transaction events -
Get Deposit Instructions
- POST to
/v1/depositswithdeposit_type: "PUSH" -
Track Deposits
- Listen for
TRANSACTION_PENDINGandTRANSACTION_POSTEDwebhooks, or poll transactions endpoint
Key Takeaways
-
✅ Always verify account status is
OPENbefore requesting deposit instructions - ✅ Use webhooks for real-time monitoring instead of polling
-
✅ Subscribe to
ACCOUNT_DEPOSIT_INSTRUCTIONS_UPDATEto catch instruction changes -
✅ Link transactions to deposits using
category_id -
✅ Only credit customers after
TRANSACTION_POSTEDstatus - ✅ Never attempt deposits on reserve accounts
- ✅ Implement proper error handling and retry logic
API Endpoints Used
| Endpoint | Purpose | Documentation |
|---|---|---|
/v1/accounts/deposits |
Open new accounts and retrieve account lists | Open Account, Get Accounts |
/v1/accounts/deposits/{id} |
Check individual account status and balance | Get Account |
/v1/subscriptions |
Set up webhook subscriptions | Create Subscription |
/v1/deposits |
Get deposit instructions | Create Deposit |
/v1/accounts/deposits/{id}/transactions |
Track incoming deposits and transactions | Get Transactions |
To dive deeper into what you can do on the Rail platform, head to our API documentation.
Contact Us - We'd love to hear your thoughts, and you can contact the team via slack, website or email support@layer2financial.com.