Wallet and payouts
From a buyer paying to money in the seller's bank.
First: can this seller be paid at all?#
Check before you build a withdrawal button, not after.
curl https://sandbox-api.biklo.com/v1/wallet/account -H "Authorization: Bearer $KEY"
can_withdraw: false means payout onboarding is unfinished, and you cannot fix that
from here — Stripe collects identity documents in a browser flow. next_step tells
you what to tell your user. See
Setting up your sandbox payout account.
The same verdict is on GET /v1/me under payouts, from your very first call, so you
find out on day one rather than at the first withdrawal.
Money is always minor units plus a currency#
There is no single balance. A seller trading cross-border holds several at once, so
GET /v1/wallet/balance returns a list:
{
"data": [
{
"currency": "RON",
"pending": 123400,
"available": 0,
"blocked": 0,
"paid_out": 0,
"refunded": 0,
"debt": 0,
"under_review": 0
}
],
"can_withdraw": true
}
Every figure is an integer of the smallest unit — 123400 is 1,234.00 RON. Never a
float, never a bare number without the currency beside it.
What the buckets mean:
| Bucket | Meaning |
|---|---|
pending |
Order money still travelling: accepted, shipping, or inside the buyer's return window |
available |
Cleared. This is what a withdrawal can move |
blocked |
Cleared, but held because payout onboarding is incomplete |
paid_out |
Already sent to the bank |
refunded |
Went back to the buyer |
settled_to_fees |
Terminal: the company's absorbed fees consumed the whole order, so nothing was transferred and nothing refunded |
debt |
Negative: a refund landed after the seller had been paid |
under_review |
We could not classify it automatically and a person is looking |
If blocked is non-zero the balance response carries an onboarding block explaining
why — that money is real and the seller cannot touch it, so it should never be a
mystery.
Where the money is, order by order#
GET /v1/wallet/lots is one row per order. Each carries balance_bucket, so you can
reconcile a lot against the balance totals without learning our internal statuses.
Reconcile against net_transferred_amount, not amount. When the company absorbs
fees, the surcharge is withheld at transfer, so the money that actually reached the
seller is smaller than the lot. Sizing anything on the gross figure is how a payout
gets requested for money that was never there.
GET /v1/wallet/ledger is the append-only trail: every movement, in order, with the
lot and order it belongs to. This is what you reconcile against, not the balance —
balances are a current view, the ledger is the history.
Withdrawing#
curl -X POST https://sandbox-api.biklo.com/v1/wallet/withdrawals \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"amount": 50000, "currency": "RON"}'
Send both or neither. An amount without a currency is ambiguous the moment a seller holds two, so it is refused rather than guessed. With neither, everything available is withdrawn per currency.
The response is honest about what happened:
status |
Meaning |
|---|---|
payout_created |
A payout is on its way to the bank |
funds_settling |
Money was released and is settling at Stripe. Not a failure |
released_settling |
Lots were released to the connected account this call |
payout_failed |
Stripe refused. Check stripe_errors |
funds_settling is the one to get right in your UI. Nothing is wrong; the money is
simply not at the bank yet.
The currency trap#
payout_failed is almost always a bank account that cannot receive the balance
currency. Stripe pays a balance out to an account in that currency. A RON balance
with a EUR-only IBAN fails, and it fails quietly.
GET /v1/wallet/account lists the bank accounts Stripe holds against the balances they
can serve, so you can detect this before attempting a withdrawal.
Hearing about it#
Register the payout.updated webhook rather than polling. It fires on the transitions
that answer "has the money moved":
event |
Meaning |
|---|---|
released |
Moved to the seller's connected account |
payout_requested |
A payout was created |
paid_out |
It arrived |
blocked_pending_onboarding |
Cleared but held — the seller must finish onboarding |
debt |
Negative balance recorded |
blocked_pending_onboarding is the one worth wiring to a notification: it is the only
signal that a human has to go and do something in a browser.
Testing the whole path#
Stripe test-mode payouts are real payouts — created, then pending until Stripe
settles them, which takes days. That is correct and useless in a demo, so:
curl -X POST https://sandbox-api.biklo.com/v1/sandbox/withdrawals/advance \
-H "Authorization: Bearer $KEY"
This skips the waiting and nothing else. Every payout in the sandbox is a real test-mode payout against a real test-mode connected account; only the settlement clock is short-circuited.
A complete run:
POST /v1/sandbox/orders— an order to earn fromPOST /v1/sandbox/orders/{id}/advancewithdeliveredPOST /v1/sandbox/orders/{id}/advancewithsettled— closes the return windowGET /v1/wallet/balance— the money is nowavailablePOST /v1/wallet/withdrawalsPOST /v1/sandbox/withdrawals/advance— skip the settlement waitGET /v1/wallet/withdrawals—status: paid
