Deposits and withdrawals
How a customer's coins reach your Binance account and leave it again — the shared deposit address, the transaction hash that is the only link to a person, and every way a payout can fail.
With this provider running, your platform holds no coins. Your Binance
account holds them, and the SPOT wallet rows in your database are a ledger
describing who is owed what out of that one pot.
That single fact explains everything below: why deposit addresses are shared, why the transaction hash matters more than it should, and why a withdrawal can be approved on your platform and still not happen.
How a deposit works
-
The customer picks a currency and a network. The list of networks comes from the currency import — see Currencies, markets and charts. A currency that was never imported has no networks and cannot be deposited.
-
The platform fetches a deposit address from Binance. It tries
fetchDepositAddressesByNetwork, thenfetchDepositAddresses, thenfetchDepositAddresswith the network as a parameter, taking whichever answers with a usable address. The result is validated before it is shown — an empty or malformed response surfaces as a clear failure rather than a blank box. -
The customer sends coins to that address from wherever they hold them.
-
The customer pastes the transaction hash into the deposit screen and submits.
-
The platform polls Binance for the matching deposit. A WebSocket subscription on
/api/finance/deposit/spotcallsfetchDepositsfor that currency every 15 seconds for 30 minutes, looking for a deposit whosetxidequals the hash the customer submitted. -
On a match with status
ok, the wallet is credited. Binance's own deposit fee is subtracted from the amount first.
Every customer depositing the same currency on the same network sees the same Binance address — yours. Nothing on-chain distinguishes one customer's deposit from another's. The transaction hash the customer types is the entire mapping between an incoming transfer and an account.
A customer who deposits and never submits a hash has funded your Binance account with no ledger entry anywhere. The money is not lost, but finding it is a manual reconciliation against Binance's deposit history, and nothing in the platform will prompt you to do it.
The details that bite
Binance internal transfers do not have a normal hash. A deposit sent from
another Binance account arrives with a txid like Off-chain transfer 12345
rather than a chain hash — in English, Russian, Portuguese or Spanish depending
on the sender's locale. The matcher strips that wrapper and compares the
identifier inside, so those deposits do resolve. Customers still have to paste
the whole string as Binance shows it.
The exchange's deposit fee is not your revenue. Binance sometimes keeps a
cut of an incoming transfer. That amount is subtracted before crediting, and
recorded as a fee with no platform profit entry, because the platform never
received it. If the fee equals or exceeds the deposit, the transaction is marked
FAILED with an explanation instead of retrying forever.
A currency mismatch deletes the request. If the matched deposit is a different asset than the wallet the customer opened the request against, the deposit is refused and the pending transaction is removed. The coins remain in your Binance account, unattributed.
Deposit expiry is optional and strict. With the depositExpiration setting
enabled, a matched deposit is rejected as TIMEOUT if it landed more than 15
minutes either side of the request being created, or more than 45 minutes ago.
Off by default; turn it on only if you understand that a slow chain will
strand legitimate deposits.
The 30-minute poll is not the only chance. A wallet cron job re-checks pending spot deposits against Binance's deposit history, searching from an hour before the request was created. A customer who closes the browser is not abandoned, but they are on the cron's schedule rather than a live one.
How a withdrawal works
A withdrawal is two separate movements: an immediate ledger debit on your platform, and a later transfer out of your Binance account. They can disagree, and the product is built around that possibility.
-
The customer requests a withdrawal. The platform validates the amount against the minimum, maximum, precision and fee imported for that currency and network, checks KYC and — if enabled — a withdrawal 2FA token.
-
The wallet is debited inside a locked transaction. The row is locked for update, the balance is checked against
balanceonly (funds held in orders are never spendable), and the debit commits. -
Approval. With auto-approval off — the default, and the safe one — the request waits for an admin on the withdrawals screen. With it on, the platform proceeds immediately, marking the transaction
PROCESSINGwith no reference id so a crash between the debit and the exchange call is recoverable. -
A balance pre-flight runs. The platform reads the free balance of that asset on your Binance account and refuses the payout if it is short, with a message naming both numbers.
-
withdrawis called on Binance with the destination address, the memo or tag if the network needs one, and the network name. -
The result is mapped to a status.
completedorokbecomesCOMPLETED,canceledbecomesCANCELLED,failedbecomesFAILED, anything else staysPENDING. -
A failure refunds the customer. If Binance returned no withdrawal id, or the status came back failed or cancelled, the debited amount is credited back — the customer was debited before the exchange was ever asked, so leaving it would simply destroy the funds.
The pre-flight reads the free spot balance. Funds in Earn, in Funding, in a sub-account, or locked behind an open order on your own Binance account are invisible to it and unusable for payouts. Unlike the KuCoin path, the Binance path performs no internal transfer before withdrawing — what is in spot is what can leave.
Why a payout fails at the exchange
| What you see | What it actually is |
|---|---|
| "Insufficient exchange balance. Available: X, Required: Y" | Your Binance spot balance is short. The customer is not debited twice; fix the float and retry |
Binance error -4026 |
Binance itself reported insufficient funds. Surfaced to the customer as a generic shortfall message |
| The withdrawal fails immediately and the customer is credited back | Most often the API key lacks the withdrawal permission, or the key is not IP-restricted — Binance only permits withdrawals from restricted keys |
| The withdrawal fails for one network but not another | The network name imported from Binance does not match what Binance expects for that asset. Re-run the currency import |
| Every withdrawal to a customer address fails | Your Binance account restricts withdrawals to whitelisted destination addresses. That is incompatible with self-service payouts |
A withdrawal reconciler cron sweeps PROCESSING rows, looks them up against
Binance's withdrawal history and either confirms or refunds them, so a process
that died mid-payout does not leave a customer permanently short. It caps how
many rows it scans per run and backs off on rows already under review, so a
large backlog clears over several runs rather than one.
Fees on the way out
Two fees are involved and they are configured in different places.
- The percentage fee is the currency's own fee plus the
spotWithdrawFeeplatform setting. It is added on top of the requested amount and deducted from the customer's wallet. It stays with you. Super-admin withdrawals skip it. - The network fee is the fixed per-network withdrawal fee imported from
Binance. The
withdrawChainFeesetting decides who absorbs it: enabled, the customer receives the full requested amount and your Binance account pays the chain fee; disabled, the fee is subtracted from what is sent.
Both numbers come from the currency import, so a stale import means stale minimums and stale fees. Re-import after Binance changes a network's economics, not before a customer complains.
What you own, operationally
Nothing reconciles the ledger against the account. The admin balance screen reads your real Binance balances, and the customer ledger lives in your database, but no screen subtracts one from the other — the platform has no price feed in that payload and will not add BTC to USDT to produce a confident wrong number.
Checking that the pot covers the claims is a manual, per-asset job, and the first time it matters is the first withdrawal you cannot pay.