Skip to main content

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"
}
}
FieldYours to setNotes
chainOne of btc eth bsc polygon avax base tron sol xrp xlm
userRefYour customer identifier. Opaque to ChainOS, returned on every webhook
tagA free-form category — deposit, settlement, payroll. Filterable
labelHuman-readable, for the console only
derivationPathAbsolute. 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.

Funds arrive; one confirmation raises deposit_detected, which you show as pending; the chain's threshold raises deposit_confirmed, which is when you credit.

{
"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
}
}
Credit on deposit_confirmed, never on deposit_detected

A 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

ChainThresholdWhy
Bitcoin1, 2, 3A webhook at each. 3 is settlement-safe for most values
Ethereum12Standard PoS finality margin
BSC15Faster blocks, shorter reorg window
Polygon128A sidechain, not a rollup — depth is the security lever
TRON20DPoS, roughly one minute
SolanafinalizedDeterministic, about 32 slots
XRP1Deterministic finality at ledger close
Stellar1Deterministic 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_detected followed 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.