Skip to main content
Your users can fund their balance on your platform with stablecoins and cash out to their own wallet. Every user gets a permanent deposit address per chain. AssetPay watches those addresses, credits your merchant balance when a deposit clears, and tells you which user it was for. A cashout runs the other way: it is paid out of your merchant balance to an address the user chose.
Crypto for end users is available to verified merchants only. Unverified merchants receive ONCHAIN_MERCHANT_NOT_VERIFIED.

Supported Networks

Only the tokens listed in the address response are credited. On Solana each token has its own account, so ask for the address with the token the user is about to send and wait until tokens lists it. Show your users the token list from the response, never a hard-coded one.
All amounts are in USD cents. USDT and USDC are credited 1:1.

Deposits

1

Ask for the user's address

Call GET /client/crypto/deposit-address?chain=ETH with the client token, or GET /secure/crypto/deposit-address with your API key and a steamId. The first call creates the address. It never changes, so you can cache it and reuse it for every deposit on Ethereum, BNB Smart Chain and Tron.
2

Show it to the user

Display address, the network, the accepted tokens, minDepositCents and the deposit fee. While status is paused the address is valid but deposits are credited only once the chain resumes.
3

The user sends the tokens

AssetPay sees the transfer within seconds (on Tron after about a minute, once its block is final) and records a deposit with status pending.
4

The deposit clears

After minConfirmations blocks the deposit becomes completed, your merchant balance is credited, and you receive a crypto_deposit webhook with the steamId. Credit your user when that webhook arrives.

Solana addresses are opened on request

A Solana account costs rent while it is open, so it is opened when the user asks for it and closed again once it has been emptied or has sat unused.
  • Pass token=USDT or token=USDC (default USDC) for the token the user is about to send. USDT and USDC use separate accounts behind the same address, and each one is opened on its own.
  • The first call answers status: "pending" with address: null. Poll the same endpoint with the same token every few seconds until it is active and tokens lists that token. This usually takes under a minute.
  • Call the endpoint again before every later Solana deposit. If the account was closed in the meantime, the call reopens it.
  • ONCHAIN_ADDRESS_CAPACITY (503) means too many of your users have an open Solana account right now. Retry later.

Minimum deposit

The live value is minDepositCents in the address response. A deposit under the minimum is recorded with status below_minimum and waits up to 72 hours for more to the same address. Once the total reaches the minimum, every waiting deposit is credited (completed); if it never does, each one ends failed and is not credited. Tell your users the minimum before they send.

Deposit fee

Deposits on Tron pay a flat $2.50, taken from the deposit, unless the deposit is $10,000 or more. The other chains have no deposit fee. Your merchant balance is credited the amount minus the fee, and the crypto_deposit webhook carries amount (as sent), fee and netAmount (what was credited). The address response states the terms for its chain: depositFeeCents is the fee and depositFeeFreeFromCents the amount from which none is charged (null when the chain has no fee). Show both to your users and read them from the response rather than hard-coding them. Below-minimum deposits that reach the minimum together pay one fee between them. Those credited along with a deposit that cleared on its own pay none.

Deposit statuses

Cashouts

A cashout debits your merchant balance. Debit your own user before you let it go through. There are two ways to start one: Both work the same way from there: AssetPay asks your server to approve every cashout before anything is sent, the same way it asks for a skin withdrawal. A cashout cannot be taken back once it is on chain, so even a request from your own server is confirmed through your callback URL.

The approval call

Every cashout starts as awaiting_approval. The amount is already held from your merchant balance, and nothing is sent until your server answers. AssetPay sends a signed POST to your callback URL, the same URL and the same signature as the skin withdrawal approval:
Tell it apart from a skin approval by the body: a cashout carries withdrawal, a skin trade carries trade. Inside your handler: verify the signature, check that steamId has at least amountCents on your platform, debit them, then answer 200 with { "action": "approve" }. If you approve and the payout later ends as failed, rejected or cancelled, give the user their balance back when that webhook arrives.
A bare 200 does not approve a cashout. This differs from the skin withdrawal approval on purpose: a handler that answers 200 to every event must never send money on chain by accident. Answer with { "action": "approve" } explicitly.
You need an active callback URL and an API secret. Without them every cashout request is refused up front with ONCHAIN_CLIENT_WITHDRAW_NO_CALLBACK (409). A callback URL that was switched off after repeated delivery failures counts as missing: save it again in the dashboard.

Fees and amounts

amountCents is what leaves your merchant balance. A flat network fee per chain, feeCents, comes out of that amount, and the user receives receiveCents:
The current fee per chain: The fee is the same for every amount. It can change, so read feeCents from the response rather than hard-coding it. The amount the user receives must be at least $1.00, otherwise the request fails with ONCHAIN_WITHDRAW_BELOW_MIN.

Safe retries

requestId is required. Sending the same requestId for the same user returns the first withdrawal and never creates a second one, so a timed out request can be repeated safely. Use a fresh requestId for every new cashout.

Daily limits

Two rolling 24 hour limits apply: one per end user and one for all of your users together. When one is reached the request fails with ONCHAIN_CLIENT_WITHDRAW_LIMIT (403):
scope is user or merchant, and remainingCents is what can still be withdrawn now. Cashouts that were refused, failed or cancelled do not count.

Cashout statuses

Webhooks

Both flows report to your regular callback URL with the regular signature. The body has one top-level key that tells you what it is: The objects are the same ones the list endpoints return, and the event is the object’s status. awaiting_approval has no webhook of its own: the approval call is that announcement. Webhooks can arrive more than once. Key your handling on id plus status.

Errors