คุณเคยเจอข้อผิดพลาด {"code":-2015,"msg":"Invalid API-key, IP, or permissions for action"} หลังจากเขียนโค้ดเชื่อมต่อ Binance API เพื่อดึงข้อมูลพอร์ตโฟลิโอหรือไม่? ผมเคยใช้เวลาหาสาเหตุเกือบ 3 ชั่วโมงเพราะลืมเติม timestamp ลงใน payload ที่ใช้สร้างลายเซ็น จนได้เรียนรู้ว่าระบบลายเซ็นของ Binance นั้นเข้มงวดกว่าที่คิดมาก

ในบทความนี้ผมจะอธิบายกลไกการทำงานของ HMAC-SHA256 signature ที่ Binance ใช้ พร้อมโค้ดตัวอย่างที่รันได้จริงใน Python และ JavaScript และวิธีแก้ปัญหาข้อผิดพลาดที่พบบ่อยที่สุด 5 กรณี

Binance API Signature คืออะไร และทำไมต้องมี?

Binance ใช้ HMAC-SHA256 signature เพื่อยืนยันว่าคำขอที่ส่งมาจากคุณจริง ไม่ใช่ผู้ไม่หวังดีปลอมแปลง กระบวนการทำงานมี 4 ขั้นตอนหลัก:

โครงสร้าง Query String สำหรับสร้าง Signature

ก่อนสร้างลายเซ็น ต้องสร้าง query string ที่มีพารามิเตอร์เรียงตามตัวอักษร (alphabetical order) ดังนี้:

# ตัวอย่าง query string สำหรับดึงยอดคงเหลือ

พารามิเตอร์ที่ต้องมี:

timestamp = ปัจจุบันในหน่วย ms

recvWindow = ระยะเวลารอรับ response (แนะนำ 5000ms)

import time import urllib.parse

พารามิเตอร์ที่ต้องส่ง

params = { 'timestamp': int(time.time() * 1000), # บรรทัดนี้สำคัญมาก! 'recvWindow': 5000, }

สร้าง query string โดยเรียงตามตัวอักษร

ผลลัพธ์: recvWindow=5000×tamp=1703123456789

query_string = '&'.join([f"{key}={value}" for key, value in sorted(params.items())]) print(f"Query String: {query_string}")

Output: recvWindow=5000×tamp=1703123456789

สร้าง HMAC-SHA256 Signature ด้วย Python

ขั้นตอนการสร้าง signature จริงใช้ HMAC-SHA256 เข้ารหัส query string ด้วย secret key:

import hmac
import hashlib
import requests
import time

====== ตั้งค่า API Credentials ======

API_KEY = "your_binance_api_key_here" SECRET_KEY = "your_binance_secret_key_here" BASE_URL = "https://api.binance.com" def create_signature(query_string: str) -> str: """สร้าง HMAC-SHA256 signature""" signature = hmac.new( SECRET_KEY.encode('utf-8'), # Secret key เป็น bytes query_string.encode('utf-8'), # Query string เป็น bytes hashlib.sha256 # ใช้ SHA-256 hash ).hexdigest() return signature def get_account_info(): """ดึงข้อมูลบัญชี Binance""" # 1. สร้าง query string timestamp = int(time.time() * 1000) params = f"timestamp={timestamp}&recvWindow=5000" # 2. สร้าง signature signature = create_signature(params) # 3. ส่ง requestพร้อม headers headers = { "X-MBX-APIKEY": API_KEY, "Content-Type": "application/x-www-form-urlencoded" } full_url = f"{BASE_URL}/api/v3/account?{params}&signature={signature}" try: response = requests.get(full_url, headers=headers, timeout=10) data = response.json() if response.status_code == 200: print(f"✅ ดึงข้อมูลสำเร็จ: {len(data.get('balances', []))} assets") return data else: print(f"❌ ข้อผิดพลาด: {data}") return None except requests.exceptions.Timeout: print("❌ Connection timeout - ลองเพิ่ม recvWindow") return None

ทดสอบเรียกใช้

result = get_account_info()

สร้าง HMAC-SHA256 Signature ด้วย JavaScript (Node.js)

สำหรับ frontend หรือ backend ที่ใช้ Node.js สามารถใช้ crypto module มาตรฐานได้เลย:

const crypto = require('crypto');

class BinanceAPI {
    constructor(apiKey, secretKey) {
        this.apiKey = apiKey;
        this.secretKey = secretKey;
        this.baseURL = 'https://api.binance.com';
    }

    // สร้าง query string จาก object
    static createQueryString(params) {
        return Object.keys(params)
            .sort()  // ต้องเรียงตามตัวอักษรเสมอ!
            .map(key => ${key}=${params[key]})
            .join('&');
    }

    // สร้าง HMAC-SHA256 signature
    createSignature(queryString) {
        return crypto
            .createHmac('sha256', this.secretKey)
            .update(queryString)
            .digest('hex');
    }

    // ดึงยอดคงเหลือ
    async getAccountInfo() {
        const timestamp = Date.now();
        const params = {
            timestamp: timestamp,
            recvWindow: 5000
        };

        const queryString = BinanceAPI.createQueryString(params);
        const signature = this.createSignature(queryString);

        const url = ${this.baseURL}/api/v3/account?${queryString}&signature=${signature};

        const response = await fetch(url, {
            method: 'GET',
            headers: {
                'X-MBX-APIKEY': this.apiKey,
                'Content-Type': 'application/x-www-form-urlencoded'
            }
        });

        const data = await response.json();
        
        if (!response.ok) {
            throw new Error(API Error ${data.code}: ${data.msg});
        }

        return data;
    }
}

// วิธีใช้งาน
const binance = new BinanceAPI(
    'YOUR_API_KEY',
    'YOUR_SECRET_KEY'
);

(async () => {
    try {
        const account = await binance.getAccountInfo();
        console.log('✅ ยอด USDT:', account.balances
            .find(b => b.asset === 'USDT')?.free || '0');
    } catch (error) {
        console.error('❌', error.message);
    }
})();

การตรวจสอบ Signature ฝั่ง Server (Python)

หากคุณต้องการสร้าง webhook หรือ server ที่รับ request จาก Binance ต้องตรวจสอบ signature ด้วย:

from fastapi import FastAPI, Request, Header, HTTPException
import hmac
import hashlib
import time

app = FastAPI()
WEBHOOK_SECRET = "your_webhook_secret_key"

def verify_signature(payload: str, signature: str, timestamp: str) -> bool:
    """
    ตรวจสอบ signature จาก Binance
    ป้องกัน replay attack โดยตรวจสอบ timestamp
    """
    # 1. ตรวจสอบว่า timestamp ไม่เก่าเกินไป (5 นาที)
    current_time = int(time.time() * 1000)
    request_time = int(timestamp)
    
    if abs(current_time - request_time) > 300000:  # 5 นาที = 300000ms
        raise HTTPException(status_code=401, detail="Request timestamp expired")
    
    # 2. สร้าง expected signature
    message = f"{timestamp}{payload}".encode('utf-8')
    expected = hmac.new(
        WEBHOOK_SECRET.encode('utf-8'),
        message,
        hashlib.sha256
    ).hexdigest()
    
    # 3. เปรียบเทียบแบบ timing-safe (ป้องกัน timing attack)
    return hmac.compare_digest(expected, signature)

@app.post("/webhook")
async def binance_webhook(
    request: Request,
    x_binance_signature: str = Header(None),
    x_binance_timestamp: str = Header(None)
):
    payload = await request.body()
    payload_str = payload.decode('utf-8')
    
    if not verify_signature(payload_str, x_binance_signature, x_binance_timestamp):
        raise HTTPException(status_code=401, detail="Invalid signature")
    
    # ประมวลผล webhook payload ที่นี่
    return {"status": "success"}

ข้อผิดพลาดที่พบบ่อยและวิธีแก้ไข

กรณีที่ 1: 401 Unauthorized - Invalid Signature

สาเหตุ: Query string ไม่ตรงกับตอนสร้าง signature หรือเรียงลำดับพารามิเตอร์ผิด

# ❌ ผิด: เรียงลำดับไม่ถูกต้อง
params_bad = f"timestamp={ts}&recvWindow=5000"

ได้: "timestamp=123&recvWindow=5000"

✅ ถูกต้อง: เรียงตามตัวอักษร

params_good = f"recvWindow=5000×tamp={ts}"

ได้: "recvWindow=5000×tamp=123"

วิธีตรวจสอบ: พิมพ์ query string ออกมาดูก่อน sign

print(f"Query: {params_good}")

ถ้าเรียงถูก ควรเป็น: recvWindow=5000×tamp=1703123456789

กรณีที่ 2: -1021 Timestamp mismatch

สาเหตุ: เวลาของ server กับ client ไม่ตรงกันเกิน 1 วินาที

# ❌ ผิด: ดึง timestamp แล้ว sign แต่ delay ก่อนส่ง
ts1 = int(time.time() * 1000)

... โค้ดอื่นทำงาน 3 วินาที ...

signature = create_signature(params) # timestamp ล้าสมัยแล้ว

✅ ถูกต้อง: ดึง timestamp ใกล้เวลาส่ง request มากที่สุด

def get_account_info(): timestamp = int(time.time() * 1000) # ใกล้ส่งมากที่สุด params = f"recvWindow=5000×tamp={timestamp}" signature = create_signature(params) # ส่ง request ทันที

หรือใช้ recvWindow มากขึ้นถ้าเครือข่ายช้า

params = f"recvWindow=10000×tamp={int(time.time() * 1000)}" # 10 วินาที

กรณีที่ 3: -2015 Invalid API-key, IP, or permissions

สาเหตุ: IP ของ server ไม่อยู่ใน whitelist หรือ API key หมดอายุ

# วิธีแก้:

1. ตรวจสอบว่า IP ปัจจุบันอยู่ใน whitelist หรือยัง

ไปที่: Binance -> API Management -> คลิก API Key -> เพิ่ม IP

2. ปิด IP restriction ชั่วคราว (ไม่แนะนำสำหรับ Production)

ตั้งค่า IP Restriction: OFF

3. ตรวจสอบว่า API key มีสิทธิ์เพียงพอ

Spot trading: ต้องมี Enable Spot & Margin Trading

Withdrawal: ต้องมี Enable Withdrawal

วิธีตรวจสอบ API key สถานะ

def check_api_key_status(): response = requests.get( f"{BASE_URL}/api/v3/account", headers={"X-MBX-APIKEY": API_KEY}, params={"timestamp": int(time.time()*1000), "recvWindow": 5000} ) if response.status_code == 401: print("🔴 ตรวจสอบ: API key, IP whitelist, หรือสิทธิ์การใช้งาน") return response.status_code == 200

กรณีที่ 4: Signature not found in request

สาเหตุ: Header ชื่อ signature ไม่ถูกส่งมาหรือชื่อผิด

# ❌ ผิด: ส่ง signature เป็น query parameter
url = f"{BASE_URL}/api/v3/account?{params}&signature={sig}"

✅ ถูกต้อง: สำหรับ GET request ส่งใน URL ก็ได้

แต่ต้องแน่ใจว่าไม่มี space หรือ encoding ผิด

✅ ถูกต้อง: สำหรับ request ที่มี body (POST/PUT/DELETE)

ต้องส่งเป็น form data ไม่ใช่ URL

headers = { "X-MBX-APIKEY": API_KEY, "Content-Type": "application/x-www-form-urlencoded" } body = f"{params}&signature={sig}" response = requests.post(f"{BASE_URL}/api/v3/order", headers=headers, data=body)

ตรวจสอบ header ที่ Binance คาดหวัง

GET: signature อยู่ใน URL query string

POST: signature อยู่ใน form body (X-MBX-APIKEY header ก็ต้องมี)

กรณีที่ 5: Connection timeout ใน High-frequency Trading

สาเหตุ: RecvWindow เล็กเกินไปสำหรับเครือข่ายที่มี latency สูง

# ❌ ผิด: recvWindow 5000ms อาจไม่พอถ้า latency 6 วินาที
params = f"recvWindow=5000×tamp={ts}"

✅ ถูกต้อง: ปรับ recvWindow ตาม network latency

วัด latency ก่อน

import time start = time.time() requests.get(f"{BASE_URL}/api/v3/ping") # ping endpoint latency_ms = (time.time() - start) * 1000

ตั้ง recvWindow เป็น 2 เท่าของ latency + buffer 500ms

recv_window = max(5000, int(latency_ms * 2) + 500) params = f"recvWindow={recv_window}×tamp={int(time.time()*1000)}" print(f"Using recvWindow: {recv_window}ms (latency: {latency_ms:.0f}ms)")

Best Practices สำหรับ Production

สรุป

การสร้างและตรวจสอบ Binance API signature นั้นไม่ซับซ้อน แต่ต้องระวังเรื่อง การเรียงลำดับพารามิเตอร์, ความถูกต้องของ timestamp, และ ระยะเวลา recvWindow หากทำผิดแม้เพียงเล็กน้อยจะได้รับข้อผิดพลาด 401 Unauthorized ทันที

สำหรับนักพัฒนาที่ต้องการเชื่อมต่อกับ AI API หลังจากประมวลผลข้อมูลจาก Binance หรือต้องการเรียกใช้ LLM หลายตัวเพื่อวิเคราะห์ตลาดคริปโต อาจพิจารณาใช้ HolySheep AI ซึ่งรองรับ LLM หลายรุ่นในราคาที่คุ้มค่า และมี latency ต่ำกว่า 50ms

👉 สมัคร HolySheep AI — รับเครดิตฟรีเมื่อลงทะเบียน