Overview
Withdrawals allow users to transfer cryptocurrency from their custodial internal wallets to external blockchain addresses. All withdrawals require the destination address to be whitelisted for security and compliance.
Withdrawal Flow
Withdrawing from Internal Wallets
Execute a withdrawal from an internal custodial wallet to a whitelisted address:
Standard Withdrawal
XRP with Memo
Response
curl -X POST "https://api.example.com/v1/wallet/internal/withdraw" \
-H "x-client-key: YOUR_PUBLIC_KEY" \
-H "Authorization: Bearer USER_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"amount": "100.50",
"recipientAddrss": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
"recipientMemo": null,
"sourceAddress": "0x0a4b21fa733e9aeaddbf070302a85c559de13c4d",
"sourceMemo": null,
"currency": "usdc"
}'
Request Parameters
Field Required Description amountYes Amount to withdraw (as string for precision) recipientAddrssYes Destination address (must be whitelisted) recipientMemoNo Destination memo/tag (required for XRP, Stellar, etc.) sourceAddressYes Source wallet address (from GET /v1/wallet/internal) sourceMemoNo Source wallet memo (if applicable) currencyYes Currency to withdraw
Note the intentional typo in the API: recipientAddrss (with double ‘s’). This is the actual field name in the current API version.
The recipientAddrss (destination address) must be whitelisted before withdrawal. Attempting to withdraw to a non-whitelisted address will fail with a validation error.
Getting Source Address Details
Before withdrawing, retrieve the source wallet address from the internal wallets list:
curl -X GET "https://api.example.com/v1/wallet/internal" \
-H "x-client-key: YOUR_PUBLIC_KEY" \
-H "Authorization: Bearer USER_ACCESS_TOKEN"
Use the address field as sourceAddress and addressMemo (if not null) as sourceMemo in withdrawal requests.
Network-Specific Considerations
EVM Networks (Ethereum, Linea)
Standard ERC-20 token withdrawal
No memo required
Gas fees paid from wallet balance
Typical confirmation: 5-15 minutes (Ethereum), 1-2 minutes (Linea)
{
"amount" : "100" ,
"recipientAddrss" : "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb" ,
"recipientMemo" : null ,
"sourceAddress" : "0x0a4b21fa733e9aeaddbf070302a85c559de13c4d" ,
"sourceMemo" : null ,
"currency" : "usdc"
}
Gas fees are automatically deducted from the withdrawal amount or wallet balance. Users receive the full requested amount at the destination. Example:
Requested withdrawal: 100 USDC
Network fee: ~0.50 ( L i n e a ) o r 0.50 (Linea) or ~ 0.50 ( L in e a ) or 2-5 (Ethereum)
User receives: 100 USDC (platform covers fees)
Solana
Requirements
Transaction Fees
SPL token withdrawal
No memo required
Transaction fees paid by platform
Typical confirmation: 5-30 seconds
{
"amount" : "50" ,
"recipientAddrss" : "DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK" ,
"recipientMemo" : null ,
"sourceAddress" : "DfKNsYfrCEHb7ScJkuMTtPTeDiyjmBBm9NMHnbR7uFHz" ,
"sourceMemo" : null ,
"currency" : "usdc"
}
Solana transaction fees are typically 0.000005 SOL (~$0.0001), making it the most cost-effective network for withdrawals.
XRP Ledger
Requirements
Destination Tags
Destination tag (memo) required for exchange addresses
Native XRP transfer
Typical confirmation: 3-5 seconds
Minimum withdrawal: 20 XRP (reserve requirement)
{
"amount" : "50" ,
"recipientAddrss" : "rNxp4h8apvRis6mJf9Sh8C6iRxfrDWN7AA" ,
"recipientMemo" : "66" ,
"sourceAddress" : "rPEPPER7kfTD9w2To4CQk6UCfuHM9c6GDY" ,
"sourceMemo" : "78" ,
"currency" : "xrp"
}
Always include destination tag when provided:
Exchanges (Binance, Coinbase, etc.) require destination tags
Personal wallets may not require tags
Missing tags can result in lost funds or delayed processing
Verify the destination tag with the recipient before withdrawal. Incorrect tags may send funds to the wrong account.
Transaction Tracking
After successful withdrawal, the API returns success: true. Transaction details appear in wallet history:
curl -X GET "https://api.example.com/v1/wallet/history?walletId=098aeb90&walletType=INTERNAL&walletCurrency=usdc&page=0" \
-H "x-client-key: YOUR_PUBLIC_KEY" \
-H "Authorization: Bearer USER_ACCESS_TOKEN"
The transaction history shows withdrawal events but does not include on-chain transaction hashes. To get the transaction hash for blockchain explorer tracking, implement additional transaction logging in your application.
Implementation Examples
Complete Withdrawal Flow
async function withdrawToWhitelist ( userToken , currency , amount , recipientIndex = 0 ) {
// 1. Get internal wallets
const walletsResponse = await fetch (
'https://api.example.com/v1/wallet/internal' ,
{
headers: {
'x-client-key' : 'YOUR_PUBLIC_KEY' ,
'Authorization' : `Bearer ${ userToken } `
}
}
);
const wallets = await walletsResponse . json ();
// 2. Find source wallet
const sourceWallet = wallets . find ( w => w . currency === currency );
if ( ! sourceWallet ) {
throw new Error ( `No ${ currency } wallet found` );
}
// 3. Check balance
if ( parseFloat ( sourceWallet . balance ) < parseFloat ( amount )) {
throw new Error ( 'Insufficient balance' );
}
// 4. Get whitelisted addresses
const whitelistResponse = await fetch (
`https://api.example.com/v1/wallet/whitelist?currency= ${ currency } ` ,
{
headers: {
'x-client-key' : 'YOUR_PUBLIC_KEY' ,
'Authorization' : `Bearer ${ userToken } `
}
}
);
const whitelisted = await whitelistResponse . json ();
if ( whitelisted . length === 0 ) {
throw new Error ( 'No whitelisted addresses. Please add one first.' );
}
const recipient = whitelisted [ recipientIndex ];
// 5. Execute withdrawal
const withdrawResponse = await fetch (
'https://api.example.com/v1/wallet/internal/withdraw' ,
{
method: 'POST' ,
headers: {
'x-client-key' : 'YOUR_PUBLIC_KEY' ,
'Authorization' : `Bearer ${ userToken } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
amount: amount . toString (),
recipientAddrss: recipient . walletAddress ,
recipientMemo: recipient . walletMemo ,
sourceAddress: sourceWallet . address ,
sourceMemo: sourceWallet . addressMemo ,
currency: currency
})
}
);
if ( ! withdrawResponse . ok ) {
const error = await withdrawResponse . json ();
throw new Error ( error . message );
}
return await withdrawResponse . json ();
}
// Usage
try {
const result = await withdrawToWhitelist ( userToken , 'usdc' , '100.50' );
console . log ( 'Withdrawal successful:' , result );
} catch ( error ) {
console . error ( 'Withdrawal failed:' , error . message );
}
Withdrawal UI Component
import { useState , useEffect } from 'react' ;
function WithdrawalForm ({ userToken , currency }) {
const [ wallets , setWallets ] = useState ([]);
const [ whitelisted , setWhitelisted ] = useState ([]);
const [ amount , setAmount ] = useState ( '' );
const [ selectedAddress , setSelectedAddress ] = useState ( '' );
const [ loading , setLoading ] = useState ( false );
const [ error , setError ] = useState ( '' );
useEffect (() => {
fetchData ();
}, [ currency ]);
const fetchData = async () => {
const [ walletsRes , whitelistRes ] = await Promise . all ([
fetch ( 'https://api.example.com/v1/wallet/internal' , {
headers: {
'x-client-key' : 'YOUR_PUBLIC_KEY' ,
'Authorization' : `Bearer ${ userToken } `
}
}),
fetch ( `https://api.example.com/v1/wallet/whitelist?currency= ${ currency } ` , {
headers: {
'x-client-key' : 'YOUR_PUBLIC_KEY' ,
'Authorization' : `Bearer ${ userToken } `
}
})
]);
const walletsData = await walletsRes . json ();
const whitelistData = await whitelistRes . json ();
setWallets ( walletsData . filter ( w => w . currency === currency ));
setWhitelisted ( whitelistData );
};
const handleWithdraw = async ( e ) => {
e . preventDefault ();
setLoading ( true );
setError ( '' );
try {
const sourceWallet = wallets [ 0 ];
const recipient = whitelisted . find ( w => w . id === selectedAddress );
if ( parseFloat ( amount ) > parseFloat ( sourceWallet . balance )) {
throw new Error ( 'Insufficient balance' );
}
const response = await fetch (
'https://api.example.com/v1/wallet/internal/withdraw' ,
{
method: 'POST' ,
headers: {
'x-client-key' : 'YOUR_PUBLIC_KEY' ,
'Authorization' : `Bearer ${ userToken } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
amount: amount ,
recipientAddrss: recipient . walletAddress ,
recipientMemo: recipient . walletMemo ,
sourceAddress: sourceWallet . address ,
sourceMemo: sourceWallet . addressMemo ,
currency: currency
})
}
);
if ( ! response . ok ) {
const errorData = await response . json ();
throw new Error ( errorData . message );
}
alert ( 'Withdrawal successful!' );
setAmount ( '' );
setSelectedAddress ( '' );
await fetchData ();
} catch ( err ) {
setError ( err . message );
} finally {
setLoading ( false );
}
};
const sourceWallet = wallets [ 0 ];
return (
< div className = "withdrawal-form" >
< h3 > Withdraw { currency . toUpperCase () } </ h3 >
{ sourceWallet && (
< div className = "balance-display" >
< label > Available Balance </ label >
< span className = "balance" >
{ sourceWallet . balance } { currency . toUpperCase () }
</ span >
</ div >
) }
{ whitelisted . length === 0 ? (
< div className = "warning" >
No whitelisted addresses. Please add one before withdrawing.
</ div >
) : (
< form onSubmit = { handleWithdraw } >
< div className = "form-field" >
< label > Amount </ label >
< input
type = "number"
step = "0.01"
value = { amount }
onChange = { ( e ) => setAmount ( e . target . value ) }
placeholder = "0.00"
required
/>
</ div >
< div className = "form-field" >
< label > Destination Address </ label >
< select
value = { selectedAddress }
onChange = { ( e ) => setSelectedAddress ( e . target . value ) }
required
>
< option value = "" > Select address... </ option >
{ whitelisted . map ( addr => (
< option key = { addr . id } value = { addr . id } >
{ addr . name } - { addr . walletAddress . slice ( 0 , 10 ) } ...
{ addr . walletMemo && ` (Memo: ${ addr . walletMemo } )` }
</ option >
)) }
</ select >
</ div >
{ error && < div className = "error" > { error } </ div > }
< button type = "submit" disabled = { loading || ! amount || ! selectedAddress } >
{ loading ? 'Processing...' : 'Withdraw' }
</ button >
</ form >
) }
</ div >
);
}
Error Handling
Insufficient Balance
{
"error" : "Insufficient balance" ,
"code" : "WALLET_INSUFFICIENT_BALANCE" ,
"message" : "Wallet balance is insufficient for this withdrawal"
}
Solution: Check wallet balance before withdrawal. Display available balance to user.
Address Not Whitelisted
{
"error" : "Address not whitelisted" ,
"code" : "WALLET_ADDRESS_NOT_WHITELISTED" ,
"message" : "Recipient address must be whitelisted before withdrawal"
}
Solution: Add the address to whitelist first using POST /v1/wallet/whitelist.
Invalid Source Address
{
"error" : "Invalid source address" ,
"code" : "WALLET_INVALID_SOURCE" ,
"message" : "Source wallet not found or does not belong to this user"
}
Solution: Verify the source address and memo match values from GET /v1/wallet/internal.
Missing Required Memo
{
"error" : "Memo required" ,
"code" : "WALLET_MEMO_REQUIRED" ,
"message" : "This network requires a recipient memo/destination tag"
}
Solution: Provide recipientMemo for XRP and similar networks that require destination tags.
Amount Below Minimum
{
"error" : "Amount too low" ,
"code" : "WALLET_AMOUNT_TOO_LOW" ,
"message" : "Withdrawal amount below minimum threshold"
}
Solution: Check network-specific minimum withdrawal amounts (e.g., XRP has a 20 XRP reserve requirement).
Best Practices
Validate Before Submission Check wallet balance, address whitelist status, and required fields before submitting withdrawal requests to provide better user feedback.
Show Transaction Status Display clear status indicators during withdrawal processing. Blockchain confirmations can take seconds to minutes depending on the network.
Confirm with Users Always show a confirmation dialog before executing withdrawals, displaying the amount, destination address, and expected fees.
Handle Memos Carefully For XRP and similar networks, prominently display the destination tag requirement and validate that it’s included in the withdrawal request.
Display Network Fees Inform users about approximate network fees for different blockchains to help them choose cost-effective withdrawal options.
Implement Retry Logic Network issues can cause temporary failures. Implement exponential backoff retry logic for transient errors.
Test with Small Amounts Encourage users to test new whitelisted addresses with small withdrawal amounts before large transfers.
Security Considerations
Irreversible Transactions : Blockchain transactions cannot be reversed. Double-check all withdrawal details before submission.
Whitelist Verification : Only withdraw to addresses you control and have verified. The whitelist prevents unauthorized destinations but cannot prevent user error.
Rate Limiting : Implement rate limiting on withdrawal requests to prevent abuse and protect user accounts from rapid fund drainage attacks.
Two-Factor Authentication : Consider requiring 2FA or email confirmation for withdrawal requests, especially for large amounts.
US Environment
For US-based users, route withdrawal requests to the US environment:
Next Steps