◈   ⌘ api · Intermediate

Binance API Withdraw Permission: Complete Setup Guide

Learn how to safely enable and use Binance API withdraw permission for automated trading, including security best practices and working code examples.

Uncle Solieditor · voc · 06.05.2026 ·views 72
◈   Contents
  1. → What Binance API Withdraw Permission Actually Does
  2. → Step-by-Step: Creating a Binance API Key with Withdraw Permission
  3. → Python Setup: Authentication and Withdraw API Calls
  4. → Tracking Withdrawal Status and History
  5. → Security Hardening: Protecting Withdraw-Enabled API Keys
  6. → Common Errors and How to Fix Them
  7. → Frequently Asked Questions
  8. → Conclusion

Enabling withdraw permission on a Binance API key is one of the most powerful — and most dangerous — things you can do in crypto automation. Get it right and you unlock fully automated fund management. Get it wrong and you hand an attacker the keys to your wallet. Most traders enable it without understanding exactly what they're authorizing, which is how accounts get drained. This guide walks through every step of setting up Binance API withdraw permission correctly, with real code and real security considerations.

What Binance API Withdraw Permission Actually Does

When you create an API key on Binance, you get three core permission toggles: Read, Trading, and Withdraw. Read lets the key fetch account balances and order history. Trading lets it place and cancel orders. Withdraw permission goes further — it authorizes the key to initiate actual fund transfers out of your Binance account to external wallet addresses.

This is fundamentally different from trading permission. A compromised trading key can wreck your positions, but a compromised withdraw key can empty your account permanently. Other exchanges handle this differently — on Bybit and OKX, withdrawal API access requires separate whitelisting steps and additional confirmation layers. Binance also offers IP whitelisting as a critical safeguard, which we'll cover in detail.

NEVER enable withdraw permission on an API key unless your use case specifically requires automated withdrawals. Trading bots, signal followers, and portfolio trackers have zero need for withdraw access. Only enable it for treasury automation, multi-exchange rebalancing bots, or custom withdrawal workflows.

Step-by-Step: Creating a Binance API Key with Withdraw Permission

Before touching any code, the API key setup in Binance's dashboard is where most mistakes happen. Follow this sequence exactly.

The withdrawal address whitelist is a separate account-level setting from API permissions. Even with a withdraw-enabled API key, Binance will only allow withdrawals to addresses you've pre-approved in your account's whitelist. This two-layer system means an attacker with your API key still can't send funds to an arbitrary address — they'd need your email and 2FA to add new addresses first.

Python Setup: Authentication and Withdraw API Calls

The Binance API uses HMAC-SHA256 signatures for authenticated endpoints. Withdraw endpoints are SIGNED, meaning every request must include a timestamp and a signature derived from your secret key. Here's how to set up authentication properly:

import hmac
import hashlib
import time
import requests
from urllib.parse import urlencode

API_KEY = 'your_api_key_here'
SECRET_KEY = 'your_secret_key_here'
BASE_URL = 'https://api.binance.com'

def sign_params(params: dict, secret: str) -> str:
    """Generate HMAC-SHA256 signature for Binance API request."""
    query_string = urlencode(params)
    signature = hmac.new(
        secret.encode('utf-8'),
        query_string.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()
    return signature

def get_headers() -> dict:
    return {'X-MBX-APIKEY': API_KEY}

def get_account_info() -> dict:
    """Fetch account balances — verify API key works before attempting withdrawals."""
    params = {'timestamp': int(time.time() * 1000)}
    params['signature'] = sign_params(params, SECRET_KEY)
    
    response = requests.get(
        f'{BASE_URL}/api/v3/account',
        headers=get_headers(),
        params=params
    )
    response.raise_for_status()
    return response.json()

# Test authentication before proceeding
try:
    account = get_account_info()
    print(f"Account type: {account['accountType']}")
    print(f"Can withdraw: {account['canWithdraw']}")
except requests.exceptions.HTTPError as e:
    print(f"Auth failed: {e.response.status_code} - {e.response.text}")

Always verify the `canWithdraw` field in the account response before attempting any withdrawal. Binance can restrict withdrawals at the account level independently of API permissions — for example, if KYC is incomplete or a security hold is active.

def submit_withdrawal(
    coin: str,
    address: str,
    amount: float,
    network: str,
    address_tag: str = None  # Required for XRP, EOS, etc.
) -> dict:
    """
    Submit a withdrawal via Binance API.
    Address must be pre-approved in Binance withdrawal whitelist.
    """
    params = {
        'coin': coin,
        'address': address,
        'amount': amount,
        'network': network,
        'timestamp': int(time.time() * 1000)
    }
    
    if address_tag:
        params['addressTag'] = address_tag
    
    params['signature'] = sign_params(params, SECRET_KEY)
    
    response = requests.post(
        f'{BASE_URL}/sapi/v1/capital/withdraw/apply',
        headers=get_headers(),
        params=params
    )
    
    # Handle specific Binance error codes
    if response.status_code != 200:
        error = response.json()
        error_code = error.get('code', 0)
        
        if error_code == -3005:
            raise ValueError("Insufficient balance for withdrawal")
        elif error_code == -3006:
            raise ValueError("Withdrawal amount below minimum")
        elif error_code == 3102:
            raise PermissionError("Address not in withdrawal whitelist")
        else:
            raise RuntimeError(f"Withdrawal failed: {error.get('msg', 'Unknown error')}")
    
    return response.json()  # Returns {'id': 'withdrawal_id_string'}

# Example: withdraw USDT on TRC20 network
try:
    result = submit_withdrawal(
        coin='USDT',
        address='TYour_TRC20_Address_Here',
        amount=100.0,
        network='TRX'
    )
    print(f"Withdrawal submitted. ID: {result['id']}")
except PermissionError as e:
    print(f"Whitelist error: {e}")
except ValueError as e:
    print(f"Amount error: {e}")
except RuntimeError as e:
    print(f"API error: {e}")

Tracking Withdrawal Status and History

Submitting a withdrawal is half the job. Production systems need to track withdrawal status through to completion, especially when handling cross-exchange rebalancing between Binance, OKX, or KuCoin. Network congestion, incorrect addresses, and chain-specific delays all require proper status polling.

import time

def get_withdrawal_history(coin: str = None, days_back: int = 7) -> list:
    """Fetch withdrawal history. Max lookback is 90 days."""
    end_time = int(time.time() * 1000)
    start_time = end_time - (days_back * 24 * 60 * 60 * 1000)
    
    params = {
        'startTime': start_time,
        'endTime': end_time,
        'timestamp': int(time.time() * 1000)
    }
    
    if coin:
        params['coin'] = coin
    
    params['signature'] = sign_params(params, SECRET_KEY)
    
    response = requests.get(
        f'{BASE_URL}/sapi/v1/capital/withdraw/history',
        headers=get_headers(),
        params=params
    )
    response.raise_for_status()
    return response.json()

def poll_withdrawal_status(withdrawal_id: str, max_attempts: int = 20) -> str:
    """
    Poll withdrawal status until confirmed or failed.
    Status codes: 0=Email sent, 1=Cancelled, 2=Awaiting approval,
                  3=Rejected, 4=Processing, 5=Failure, 6=Completed
    """
    STATUS_MAP = {
        0: 'email_sent', 1: 'cancelled', 2: 'awaiting_approval',
        3: 'rejected', 4: 'processing', 5: 'failed', 6: 'completed'
    }
    TERMINAL_STATES = {1, 3, 5, 6}
    
    for attempt in range(max_attempts):
        history = get_withdrawal_history(days_back=1)
        
        for record in history:
            if record.get('id') == withdrawal_id:
                status_code = record['status']
                status = STATUS_MAP.get(status_code, 'unknown')
                print(f"Attempt {attempt+1}: {status} | TxID: {record.get('txId', 'pending')}")
                
                if status_code in TERMINAL_STATES:
                    return status
                break
        
        time.sleep(30)  # Poll every 30 seconds
    
    return 'timeout'

# Usage
withdrawal_id = 'abc123xyz'
final_status = poll_withdrawal_status(withdrawal_id)
print(f"Final withdrawal status: {final_status}")

Security Hardening: Protecting Withdraw-Enabled API Keys

A withdraw-enabled API key is a high-value target. The security model needs to be layered — no single control is sufficient on its own. Platforms like Bybit and Bitget have similar security architectures, but Binance's implementation has some specific quirks worth knowing.

Binance API Security Controls for Withdraw Permission
ControlHow to EnableProtection Level
IP WhitelistAPI Management → Edit → Restrict access to trusted IPsCritical
Withdrawal Address WhitelistSecurity → Withdrawal Whitelist → EnableCritical
API Key ExpirySet expiration date when creating keyHigh
2FA on AccountSecurity → 2-Step VerificationHigh
Email ConfirmationEnabled by default for new withdrawal addressesMedium
Sub-account IsolationUse sub-accounts for trading vs withdrawal keysHigh

Sub-account isolation is underused and extremely effective. Create a dedicated Binance sub-account for automated withdrawals, keep only the funds needed for current operations in it, and never enable withdraw permission on your main account's API keys. This limits blast radius if a key is ever compromised — the attacker can only access what's in the sub-account.

When building automated trading systems that connect to signal platforms like VoiceOfChain for real-time trade signals, keep the signal-reception layer and the withdrawal-execution layer in completely separate processes with separate API keys. The signal bot needs trading permission only. A separate treasury bot handles fund movements. Never combine them.

Store API keys in environment variables or a secrets manager — never hardcode them. Rotate withdraw-enabled keys every 90 days minimum. Set up Binance account activity alerts via email so any unexpected withdrawal attempt triggers an immediate notification.

Common Errors and How to Fix Them

Working with the Binance withdrawal API throws a specific set of errors that aren't well-documented. Here are the ones you'll hit in production and what they actually mean:

Binance Withdrawal API Error Codes
Error CodeMessageFix
-1022Signature for this request is not validCheck timestamp sync — server clock must be within 1000ms of Binance time
-3005Insufficient balanceCheck available balance minus pending orders
-3006Amount less than minimumCheck coin-specific minimum withdrawal amounts via /sapi/v1/capital/config/getall
3102Address is not in withdrawal address bookAdd address to Binance withdrawal whitelist first
-1003Too many requestsImplement rate limiting — withdrawal endpoint allows 1 req/second
-2014API key format invalidRegenerate key — usually caused by whitespace in key string

The timestamp synchronization error (-1022) is the most common one for developers running in Docker containers or cloud environments. Binance requires your request timestamp to be within 1000ms of their server time. Always fetch the Binance server time at startup via `/api/v3/time` and use it to calculate a time offset for all subsequent requests.

Frequently Asked Questions

Is it safe to enable withdraw permission on a Binance API key?
It's safe if you implement proper controls: IP whitelisting, withdrawal address whitelist, and sub-account isolation. Never enable withdraw permission on a general-purpose trading key — create a dedicated key with narrow scope. Platforms like OKX and Bybit follow similar principles.
Can a Binance API key with withdraw permission drain my account?
Only to addresses pre-approved in your withdrawal whitelist. Binance requires new withdrawal addresses to be verified via email and 2FA before they become active. An attacker with only your API key cannot add new addresses — they'd need access to your email account as well.
What is the minimum withdrawal amount via Binance API?
Minimums vary by coin and network. Fetch current minimums using the `/sapi/v1/capital/config/getall` endpoint, which returns `withdrawMin` for each coin on each network. Don't hardcode these values — Binance updates them without notice.
Why does my Binance withdrawal API call return error -1022?
This is a timestamp synchronization error. Your server clock is out of sync with Binance's servers by more than 1000ms. Fetch Binance server time via `/api/v3/time` at startup and calculate a local offset, or use NTP to keep your server clock accurate.
Do I need withdraw permission for automated trading bots?
No. Standard trading bots only need Read and Trading permissions. Withdraw permission is only necessary when your automation needs to physically move funds between exchanges or to external wallets. Most bots — including those consuming signals from platforms like VoiceOfChain — only need trading access.
How do I withdraw to another exchange like KuCoin or Gate.io via API?
First deposit an address from KuCoin or Gate.io into your Binance withdrawal whitelist and wait for email confirmation. Then use the Binance withdrawal API with that address. Make sure to specify the correct network — sending ERC20 USDT to a TRC20 address will result in permanent loss.

Conclusion

Binance API withdraw permission is powerful infrastructure when used correctly — and a serious liability when it isn't. The pattern that works in production: use withdraw-enabled keys only in isolated sub-accounts, always enforce IP whitelisting, maintain a tight withdrawal address whitelist, and never mix withdrawal logic with trading logic in the same bot. The code examples here give you a working foundation, but adapt them to your specific use case rather than copy-pasting blindly. For the trading and signal side of your automation — where you're acting on real-time market data from platforms like VoiceOfChain — keep those API keys scoped to trading only. Reserve withdraw permission for the treasury layer, keep it small, keep it monitored, and rotate those keys regularly.

◈   more on this topic
◉ basics Mastering the ccxt library documentation for crypto traders ⌂ exchanges Mastering the Binance CCXT Library for Crypto Traders ⌬ bots Best Crypto Trading Bots 2025: Profitable AI-Powered Strategies