Funding & Deposits
Every card's Account can be funded via two rails: automatic multi-chain digital-asset straight-through processing (STP) and bank-rail (fiat) transfers. This page covers both in full, including how balances update at each stage, the trace-notification mechanism for bank deposits, and the three deprecated endpoints that previously handled a manual blockchain registration flow.
For the generic mechanics underlying both rails — confirmation/finality for digital assets, and the auth/clearing/settlement cycle for fiat — see Digital-Asset Rails & Custody and The Card Transaction Lifecycle.
How balances are updated
Both rails update the same two balance fields on the Card Account, following the same "pending → complete" pattern:
| Balance field | Updated when |
|---|---|
ledgerBalance | An inbound amount is confirmed/detected but not yet fully cleared or settled — an "on the way" balance including holds and pending amounts |
availableBalance | An inbound amount has fully cleared and is available for spending |
| Rail | ledgerBalance updates | availableBalance updates |
|---|---|---|
| Digital-asset (STP) | After ≥10 block confirmations | After chain-specific finality is reached and the asset has been off-ramped to the card's base fiat currency |
| Bank (fiat) | When the incoming transfer is detected / trace notification received | After the transfer clears through correspondent banking (~5 business days, depending on rail and jurisdiction) |
Rail 1: Digital-asset STP (automatic, zero-integration)
This is the primary funding rail for digital assets and requires no API calls on your part after card issuance.
How it works
At the moment a card is issued (POST /cards/virtual/{cardholderId} or POST /cards/assign/{cardholderId}), Axys automatically provisions a set of unique deposit addresses using an HD wallet architecture. These are returned immediately in the issuance response as cryptoAddresses, and are also available on GET /cards/{cardId} and GET /cards/{cardId}/details.
Deposit address architecture — one address per address format, not per chain
A common misconception is that "80+ chain compatibility" means 80+ separate deposit addresses. In practice, EVM-compatible chains share a single deposit address — the standard 42-character alphanumeric hexadecimal format (0x...) is identical across Ethereum, Arbitrum, Polygon, Base, BNB Smart Chain, and every other EVM-compatible network. A single address therefore accepts deposits across all of these chains simultaneously. Non-EVM chains (Bitcoin, Solana, Tron, etc.) use distinct address formats and each require their own address.
Default deposit addresses
Every card is provisioned by default with deposit addresses covering the following chains and formats:
| Chain(s) | Format | Address type |
|---|---|---|
| Ethereum, Arbitrum, Polygon (MATIC), Base, BNB Smart Chain — and all other EVM-compatible chains | 42-char hex (0x...) | Single shared EVM address |
| Bitcoin | Bech32 (bc1...) | BTC address |
| Solana | Base58 | SOL address |
"cryptoAddresses": [
{ "chain": "evm", "address": "0x742d35Cc6634C0532925a3b8D4C9B7e..." },
{ "chain": "bitcoin", "address": "bc1qxy2kgdygjrsqtzq2n0yrf249..." },
{ "chain": "solana", "address": "7EqQdEULxntMjTnxHhq1p9RwP..." }
]Additional and substitute deposit address types
Additional or alternative chain-specific deposit address types — for example, AVAX (Avalanche C-Chain uses the EVM format but may be listed separately), Tron, Cosmos, and others — are available but must be requisitioned per Program via Account Management.
Discovering available chains, assets, and indicative off-ramp prices
A dedicated endpoint is currently in development that will expose:
- The blockchain deposit addresses available for a given Program or card (reflecting both the default set and any additional chains requisitioned via Account Management)
- The digital assets supported for deposit and STP conversion on the Program
- Indicative off-ramp swap prices to the Program's base currency for certain assets — providing integrators and Cardholders with a directional indication of conversion rates before a deposit is made
Once available, this endpoint will be documented in the Reference section and linked from this page. In the meantime, the default chain coverage (EVM, BTC, SOL) and any Program-specific additions requisitioned via Account Management represent the full set of supported deposit addresses for a given Program. For current indicative off-ramp pricing, contact your Axys account team.
There may be cost implications for Programs requesting a large number of additional deposit address types. Discuss your chain-coverage requirements with your Axys account team during program design. Account Management is only available in the production environment — staging programs carry a standard default set of deposit address types.
Addresses exist immediately — even for unactivated physical cards. A physical card's deposit addresses are generated at issuance (
POST /cards/assign), before the cardholder has activated the card. Incoming digital-asset deposits to those addresses will be processed and reflected in the Card Account regardless of the card'sissuerCardStatus.
The STP deposit flow
sequenceDiagram
participant Depositor
participant Chain as Blockchain
participant Axys as Axys (MPC/TSS custody)
participant Account as Card Account
Note over Axys,Account: At issuance — zero configuration needed
Axys->>Account: Deposit addresses provisioned (HD wallet),<br/>returned in CardCreateResponse.cryptoAddresses
Note over Depositor,Account: At deposit time
Depositor->>Chain: Send digital asset to deposit address
Chain-->>Chain: Transaction broadcast & included in block
Chain-->>Chain: Confirmations accumulate...
Chain-->>Axys: ≥10 confirmations reached
Axys->>Account: ledgerBalance += deposit amount (pending)
Note over Account: Status: Pending — funds reserved,<br/>not yet available to spend
Chain-->>Chain: Chain-specific finality reached
Axys-->>Axys: Off-ramp: convert to card's base currency<br/>via deep-pool liquidity / market makers<br/>(dynamic spread applied — see FX & Fees)
Axys->>Account: availableBalance += converted amount
Note over Account: Status: Complete — funds available to spend
Asset coverage and conversion
- 80+ blockchains and 3,000+ digital assets are supported, covering EVM chains, UTXO chains, Solana, Tron, Cosmos ecosystem chains, and more.
- Stablecoins (USDC, USDT, PYUSD, etc.) convert rapidly with near-zero spread.
- Majors (BTC, ETH, BNB, SOL, etc.) convert via deep-pool liquidity relationships — typically fast, with a small spread.
- Exotics and high-volatility assets may take longer and carry a slightly wider spread to account for market-depth risk during conversion.
- All gas / network fees are embedded in the conversion rate — there are no separate gas fee line items.
For the full FX-spread model, see Embedded FX & Cross-Border Fees.
Monitoring a digital-asset deposit
Poll GET /cards/{cardId}/balance after sharing a deposit address with a depositor. The progression to watch for:
Step 1: availableBalance unchanged, ledgerBalance unchanged
(deposit not yet detected / confirmations < 10)
Step 2: ledgerBalance increases, availableBalance unchanged
(≥10 confirmations — deposit pending)
Step 3: ledgerBalance and availableBalance both increase (or converge)
(finality reached + off-ramp complete — deposit complete)
Rail 2: Bank-rail (fiat) deposits
Fiat deposits via bank transfer require registering the sender's bank account, followed by the actual transfer. A trace notification step is optional but recommended for faster processing.
Step 1: Register a bank account
POST /cardholders/{cardholderId}/register-bank-account
This registers the cardholder's bank account as an approved source for inbound fiat transfers.
| Field | Required | Constraints | Description |
|---|---|---|---|
senderAccountName | Yes | max 50 chars | The cardholder's bank account name |
senderAccountNumber | Yes | 7–34 chars | Bank account number, IBAN, or equivalent |
senderRoutingNumber | Yes | 6–12 chars | Routing number, BIC/SWIFT, sort code, or equivalent |
The response includes a depositId and statusText — and, critically, triggers an SMS OTP to the cardholder's registered mobile number.
The registration OTP: SMS reply-to-confirm
Bank account registration is deliberately not completable programmatically alone. After the API call, the cardholder receives an OTP via SMS from a short code, and must reply to that SMS to confirm their bank account. This "reply-to-confirm" methodology is specifically chosen because:
- It reduces MITM and interception risk — replying to an SMS (rather than entering an OTP into a form) is harder to spoof via phishing or man-in-the-middle attacks.
- It reduces SIM-swap risk — confirmation requires the physical SIM receiving the message, not just knowledge of the OTP value.
- It ensures the cardholder — not platform staff — is the one linking the account. Allowing bank account association to be completed programmatically without cardholder involvement would create a backdoor: internal personnel could potentially divert incoming funds to a different card by linking an arbitrary bank account. The reply-to-confirm model eliminates this as an attack vector.
A bank account registration cannot be completed via the API alone — the cardholder must actively reply to the SMS to confirm. Design your onboarding UX to instruct the cardholder to expect and reply to this message before any funded transfer is initiated.
Anonymous short code and named sender
By default, the SMS originates from an anonymous short code — this preserves the white-label identity of the Program, since an Axys-branded sender name would be visible to the cardholder.
Tenants can apply for a named sender (alphanumeric sender ID) to appear instead of the anonymous short code for a specific Program's outbound SMS — for example, displaying the Program's own brand name. This is configured via Account Management.
Account Management is only available in the production environment. Named sender ID applications and other Account Management configurations cannot be made in staging. Test flows with the anonymous short code in staging, and submit named-sender applications as part of your production go-live process.
Once registration is confirmed, you only need to register a bank account once per cardholder per account — subsequent deposits from the same account are matched automatically.
Step 2: Cardholder initiates transfer
The cardholder sends a bank transfer from their registered account to the Program's inbound bank account, referencing the cardId (or a program-specific reference provided to them) so the inbound transfer can be matched to the correct card.
Program inbound bank account credentials are only available via Account Management in the production environment. The account number, routing/sort code, BIC/SWIFT, and any required reference format that the cardholder needs to address their transfer to are exposed exclusively through Account Management — they are not returned by any endpoint in the program-level API. This also means that full end-to-end testing of the bank-rail deposit flow (including the cardholder-side transfer step) is only possible in production, not in staging. Retrieve your Program's inbound bank credentials from Account Management as part of your production go-live process, and share them with cardholders through your own application's funding-instructions UX.
Step 3 (optional): Post a trace notification
POST /cards/{cardId}/bank-deposit
The trace notification allows you to proactively tell Axys about an inbound transfer before it arrives, which can expedite straight-through processing by pre-matching the transfer to the card. All fields are optional:
| Field | Description |
|---|---|
transactionAmount | Expected amount (decimal-implied integer) |
transactionReference | The unique transaction number or reference from the bank's remittance advice or transaction receipt |
senderAccountName | The sender's bank account name |
senderAccountNumber | The sender's bank account number, IBAN, or equivalent |
senderRoutingNumber | The sender's routing number, BIC/SWIFT, sort code, or equivalent |
The response includes a depositId and statusText.
The bank-rail deposit flow
sequenceDiagram
participant You
participant Cardholder
participant Bank as Cardholder's Bank
participant Axys
participant Account as Card Account
You->>Axys: POST /cardholders/{cardholderId}/register-bank-account
Axys->>You: depositId, statusText (OTP issued for registration)
You->>Cardholder: Provide funding instructions (reference: cardId)
Cardholder->>Bank: Initiate bank transfer referencing cardId
opt Trace notification (recommended)
You->>Axys: POST /cards/{cardId}/bank-deposit<br/>(transactionReference, amount, sender details)
Axys->>You: depositId, statusText
Note over Axys: Transfer pre-matched — faster STP
end
Bank->>Axys: Transfer arrives (correspondent banking)
Axys->>Account: ledgerBalance += transfer amount
Note over Account: Status: Pending — cleared but not yet settled
Note over Bank,Account: Clearing window (~5 business days,<br/>depends on rail and jurisdiction)
Axys->>Account: availableBalance += amount
Note over Account: Status: Complete — fully settled
Wallet-level bank deposits
Bank deposits can also be directed to a Wallet rather than a Card:
POST /wallets/{walletId}/bank-deposit
The request and response shape are identical to the card-level endpoint. Funds deposited here land in the Wallet's fiat balance (see Accounts, Wallets & Deposit Addresses) and can then be transferred to Cards or other Wallets.
Suspense accounts and unallocated deposits
When an inbound deposit — on either rail — cannot be automatically matched and allocated to a specific card or wallet, the funds are held in a suspense account pending identification and manual allocation or return. Understanding the triggers for this, and designing your integration to prevent it, is both an operational and a compliance imperative.
What causes a deposit to become unallocated
| Scenario | Rail | Why STP fails |
|---|---|---|
Fiat transfer sent from a bank account that has not been registered via POST /cardholders/{cardholderId}/register-bank-account | Bank (most common) | No pre-established link between the sending account and a card — automatic matching cannot route the funds |
Fiat transfer sent for a card that is not issuerCardStatus = 1 (Active) — e.g. a card that has been put On Hold, is Not Activated, or has been closed | Bank or crypto | The destination card is not in a state that can receive funds |
| Fiat transfer using an incorrect or missing payment reference | Bank | The reference that would identify the destination card/wallet is absent or unreadable |
| Digital-asset deposit to a card deposit address where the card's status does not permit receipt | Crypto (uncommon) | The platform cannot complete the off-ramp and allocation cycle |
Compliance implications: AML, KYT, and Source of Funds
Funds sitting in a suspense account are, by definition, unidentified funds of unclear origin. This is not a neutral state from a compliance perspective:
- AML (Anti-Money Laundering): unallocated funds may trigger AML screening processes — a suspicious inbound transfer that can't be matched to a known, KYC'd cardholder is precisely the pattern AML frameworks exist to detect.
- KYT (Know Your Transaction): the absence of a verified sender-to-cardholder link means the transaction cannot be fully screened or attributed.
- Source of Funds (SoF): regulatory obligations may apply to the Tenant and the Program operator when unverified funds enter the system, including investigation to establish the source, intent, and identity of the sender.
Unallocated deposits create compliance risk for the Tenant and Program — treat this as a compliance incident, not just an operational inconvenience. There are no guaranteed processing times for suspense account resolution, and outcomes are uncertain. In some cases, funds may be returned to the originating source less any fees applied by banking or correspondent partners — meaning the original depositor may not recover the full amount sent. There is no guarantee that return is possible in all cases, particularly where the originating source is itself unverifiable.
Tenants and Program operators should put in place every reasonable mechanism to prevent this from occurring. Prevention is always preferable to resolution.
Prevention: required controls
The most effective mitigations are front-loaded — preventing an unallocated deposit is far preferable to resolving one after the fact:
| Control | Rail | Description |
|---|---|---|
| Register the bank account before funding | Bank | Complete POST /cardholders/{cardholderId}/register-bank-account and the SMS reply-to-confirm step before the cardholder initiates any transfer. Do not allow cardholders to fund from unregistered accounts. |
| Enforce the payment reference | Bank | Instruct cardholders clearly and precisely on the required payment reference format (including the cardId or Program-specific reference), and display it unambiguously in your funding-instructions UX. |
| Verify card status before sharing funding details | Both | Only surface funding instructions — deposit addresses or bank transfer details — for cards with issuerCardStatus = 1 (Active). Remove or suppress them for cards that are On Hold, Not Activated, or Closed. |
| KYT screening at the Program level | Both | Implement your own Know Your Transaction screening as a second line of defence — flag and investigate any inbound transfer that doesn't match an expected sender or amount. |
| Monitor for registration-to-funding gaps | Bank | If a cardholder's bank account was registered but no matching deposit arrives within an expected window, reach out proactively rather than waiting for a suspense event. |
If funds enter the suspense account
Raise a support ticket immediately with as much detail as possible:
POST /support/tickets
{
"programId": 42,
"cardholderId": 10532,
"subject": "Unallocated deposit — suspense account investigation",
"message": "Inbound transfer of approx. [amount] [currency] received [date] from [sender details]. Transfer reference: [reference]. Card ID: [cardId]. Requesting allocation investigation.",
"priority": 2
}
Include as much detail as possible: approximate amount, currency, sender bank details, transaction reference, expected destination card/wallet, and the date the transfer was initiated. A priority: 2 (High) ticket is appropriate given the compliance exposure. The more information provided, the faster the investigation can proceed.
See Raising & Managing Support Tickets.
Legacy endpoints (deprecated)
Three endpoints from the original manual blockchain-registration flow are deprecated and should not be used in new integrations:
| Deprecated endpoint | Why deprecated |
|---|---|
POST /cardholders/{cardholderId}/register-wallet-address | Superseded by automatic HD-wallet address generation per card |
POST /cards/{cardId}/deposit | Superseded by zero-integration STP |
POST /wallets/{walletId}/deposit | Superseded by zero-integration STP |
These endpoints remain functional for existing integrations. For context on the transition, see Versioning & Deprecations.
| Scenario | Recommended rail |
|---|---|
| Cardholder has crypto / stablecoins | Share cryptoAddresses from the card — STP does the rest |
| Cardholder wants to fund via bank transfer | Register bank account → cardholder sends transfer → optional trace notification |
| Program managing a corporate fund pool | Bank deposit to a Wallet → transfer to individual Cards |
| Fastest possible funding, zero friction | Digital-asset (stablecoin preferred — near-instant for majors) |
| Regulatory preference for fiat-only | Bank rail — but note the ~5 day clearing window before availableBalance updates |
