Receiving deposits
The inbound path, end to end.
Issue an address
One address per customer per chain, issued once and kept. ChainOS does not rotate deposit addresses — a fintech's customer keeps the account number they were given, and a rotating address produces deposits to addresses your customer saved months ago.
curl -X POST $EDGE/addresses \
-H "X-API-Key: $CHAINOS_EDGE_KEY" \
-H "Idempotency-Key: addr-cust_88213-eth" \
-H "Content-Type: application/json" \
-d '{
"chain": "eth",
"userRef": "cust_88213",
"tag": "deposit",
"label": "Main NGN wallet"
}'
{
"success": true,
"data": {
"id": "adr_01J8XKE",
"chain": "eth",
"address": "0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD",
"derivationPath": "m/44'/60'/0'/0/4211",
"environment": "live",
"userRef": "cust_88213",
"tag": "deposit",
"label": "Main NGN wallet",
"status": "active"
}
}
| Field | Yours to set | Notes |
|---|---|---|
chain | ✅ | One of btc eth bsc polygon avax base tron sol xrp xlm |
userRef | ✅ | Your customer identifier. Opaque to ChainOS, returned on every webhook |
tag | ✅ | A free-form category — deposit, settlement, payroll. Filterable |
label | ✅ | Human-readable, for the console only |
derivationPath | — | Absolute. Store it; it is how you recover the key without ChainOS |
The Idempotency-Key matters here specifically: a retried address creation without one issues a
second address to the same customer, and now you are monitoring two and your customer has one.
See Idempotency.
ETH, BSC, Polygon, Avalanche and Base share one address. They share the derivation path m/44'/60'/0', so
an address issued for eth receives on all five. Issue it once per customer, not five times, and
note that a deposit arriving on BSC to an address you issued for eth is still that customer's
deposit — the webhook's chain field tells you which network it arrived on.
Solana and Stellar need a live Edge. Their addresses come from pools the Edge derives, because
Ed25519 has no public-key-only derivation. If a pool is exhausted and no Edge is connected,
allocation fails with 423 EDGE_OFFLINE rather than issuing an address nobody holds a key for. The
two pools are independent of each other. See Solana and
Stellar.
A Stellar address does not exist until its first deposit creates it. The response carries
reserveDrops for xlm — 10,000,000 stroops, 1 XLM — and that is the minimum a first deposit
must carry, because the sender has to issue create_account rather than a plain payment. Nearly
every wallet and exchange does this automatically; a bare payment to an unfunded address fails and
the funds stay with the sender. Every deposit after the first is an ordinary payment of any size.
A USDC deposit additionally needs a trustline that only the destination account can establish —
see Stellar.
The two events
A deposit produces two webhooks, and they answer different questions.

{
"event": "deposit_confirmed",
"id": "evt_01J8XK9",
"environment": "live",
"confirmations": 12,
"threshold": 12,
"timestamp": "2026-08-19T10:32:00Z",
"data": {
"chain": "eth",
"txid": "0xf9e1a2b3…",
"outputIndex": 0,
"address": "0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD",
"counterparty": "0x8a1c…",
"amount": "1000000",
"amountFormatted": "1.00",
"decimals": 6,
"tokenSymbol": "USDT",
"tokenContract": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
"blockNumber": 21456789,
"userRef": "cust_88213",
"tag": "deposit",
"destinationTag": null
}
}
deposit_confirmed, never on deposit_detectedA detected deposit can disappear. It is in a block that a reorg can orphan, or in the mempool of a chain that has not yet included it. The threshold exists precisely to bound that risk.
Showing a pending balance to your customer on deposit_detected is good product design.
Crediting a withdrawable balance on it is how you lose money.
Confirmation thresholds
| Chain | Threshold | Why |
|---|---|---|
| Bitcoin | 1, 2, 3 | A webhook at each. 3 is settlement-safe for most values |
| Ethereum | 12 | Standard PoS finality margin |
| BSC | 15 | Faster blocks, shorter reorg window |
| Polygon | 128 | A sidechain, not a rollup — depth is the security lever |
| TRON | 20 | DPoS, roughly one minute |
| Solana | finalized | Deterministic, about 32 slots |
| XRP | 1 | Deterministic finality at ledger close |
| Stellar | 1 | Deterministic finality at ledger close — a closed ledger does not reorganise |
Bitcoin is the one with a ladder: deposit_confirmed fires three times, at 1, 2 and 3
confirmations, with confirmations and threshold in the payload telling you which. Choose the
depth you credit at based on value — many fintechs credit small amounts at 1 and hold larger ones
to 3.
Each threshold fires exactly once, tracked in a bitmask, so a reorg-and-replay cannot re-fire a threshold that already fired.
Thresholds are not configurable. They come from each chain's reorg characteristics rather than from preference. You can subscribe to fewer events; you cannot lower a threshold.
Reorgs
If a block containing a detected deposit is orphaned, ChainOS reverses its view: the transaction
returns to pending and the confirmation count resets. If the transaction is re-included, the
count climbs again and any threshold not previously fired fires when reached.
What this means for you:
- You will not receive a "deposit reversed" event before
deposit_confirmed, because a transaction below its threshold is not settled and was never presented as such. - After
deposit_confirmed, a reversal is a genuine incident, not a normal case. At 12 Ethereum confirmations or 128 on Polygon it means something extraordinary happened on the chain. It is not silently swallowed — see Incident response. - Do not treat
deposit_detectedfollowed by silence as a failed deposit. It may be a slow chain, a low-fee transaction, or a reorg in progress. Reconcile rather than guess; see Reconciliation.
Tokens
Token deposits arrive as the same events with the token fields populated:
{ "amount": "1000000", "decimals": 6,
"tokenSymbol": "USDT",
"tokenContract": "0xdAC17F958D2ee523a2206206994597C13D831ec7" }
Branch on tokenContract being present, not on tokenSymbol. Symbols are not unique: several
contracts call themselves USDT, and only one of them is the one you accept. The contract address
is the identity.
Polygon carries two USDCs — native and bridged USDC.e. Only the native one is monitored,
so a deposit to the bridged contract is never credited. If you accept "USDC" on Polygon, give your
customers the contract rather than the word. See Polygon.
Reading a balance
Webhooks are the mechanism for reacting. Balances are for reconciling and displaying.
# Native
curl -s "$EDGE/balances/eth/0x3fC9…7FAD" -H "X-API-Key: $CHAINOS_EDGE_KEY"
# A specific token
curl -s "$EDGE/balances/eth/0x3fC9…7FAD/tokens/0xdAC17F95…1ec7" \
-H "X-API-Key: $CHAINOS_EDGE_KEY"
# Many at once — one call rather than five hundred
curl -s -X POST $EDGE/balances/batch \
-H "X-API-Key: $CHAINOS_EDGE_KEY" \
-H "Content-Type: application/json" \
-d '{ "chain": "eth", "addresses": ["0x3fC9…7FAD", "0x8a1c…"] }'
{ "confirmed": "1000000", "available": "1000000", "reserved": "0", "decimals": 6 }
available is confirmed minus anything the chain immobilises. On XRP that is the base reserve,
on Solana the rent-exempt minimum, on Stellar the account's own minimum balance. On the other five
they are equal. Read available when
deciding whether a send can proceed — it is what makes the pre-flight correct without
per-chain branching.
A chain whose RPC is unreachable returns 503 CHAIN_UNAVAILABLE, never a zero. A confident zero
is indistinguishable from an emptied wallet.
Archiving
An address you no longer need can be archived:
curl -X POST $EDGE/addresses/adr_01J8XKE/archive -H "X-API-Key: $CHAINOS_EDGE_KEY"
That removes it from monitoring and from the active-address count used for billing, from the next
daily snapshot. Funds already there are unaffected and still recoverable from your mnemonic and
the stored derivationPath — but deposits arriving after archiving are not detected and
produce no webhook. Only archive an address you are confident your customer will not use again.
Next
- Webhooks — signature verification, which is where integrations break.
- Idempotency — the obligation at-least-once delivery puts on you.
- Reconciliation — proving your ledger matches the chain.
- Your chain's page, for the quirk that will otherwise surprise you.