How to Sign OKEx POST API Requests Correctly

·

Signing OKEx API requests—especially POST requests—can be challenging, even if your GET requests work perfectly. Many developers encounter the frustrating {'msg': 'Invalid Sign', 'code': '50113'} error when trying to place orders via the /api/v5/trade/order endpoint. This guide walks you through the correct way to sign POST API requests for OKEx (now known as OKX), explains common pitfalls, and provides a working Python implementation that ensures your signature is accepted.

Whether you're building a trading bot, automating portfolio rebalancing, or integrating market data into your application, understanding how to properly authenticate with the OKX API is essential. We’ll cover everything from timestamp formatting to payload handling, ensuring your requests are securely and accurately signed.


Understanding OKX API Authentication

OKX uses HMAC-SHA256 signatures to authenticate private API endpoints. Every authenticated request must include the following headers:

The signature is generated by combining:

timestamp + method (uppercase) + request path + body

This string is then hashed using HMAC-SHA256 with your secret key and encoded in base64.

👉 Discover how to securely manage your API keys and start trading with confidence.


Why GET Works But POST Fails

A common issue developers face is that GET requests succeed, but POST requests return "Invalid Sign". The root cause often lies in how the request body is handled during signature generation.

For GET requests, there's typically no body or it’s empty, so the message string only includes timestamp, method, and endpoint. However, for POST requests:

A subtle mismatch between how the body is serialized in the signature vs. how it's sent in the actual HTTP request can lead to signature verification failure.


Step-by-Step Guide to Signing POST Requests

Here’s a complete, tested Python function to sign and send both GET and POST requests to OKX:

import hmac
import json
import base64
import requests
import datetime as dt

# Replace these with your actual credentials
APIKEY = "your_api_key"
APISECRET = "your_api_secret"
PASS = "your_passphrase"
BASE_URL = 'https://www.okx.com'

def send_signed_request(http_method: str, url_path: str, payload: dict = None):
    def get_timestamp():
        return dt.datetime.utcnow().isoformat(fraction=False) + ".000Z"

    def generate_signature(timestamp: str, method: str, request_path: str, body: str, secret: str):
        if body is None or body == '{}' or body == 'null':
            body = ''
        message = timestamp + method.upper() + request_path + body
        mac = hmac.new(
            bytes(secret, encoding='utf-8'),
            bytes(message, encoding='utf-8'),
            digestmod='sha256'
        )
        return base64.b64encode(mac.digest()).decode()

    timestamp = get_timestamp()
    body_str = json.dumps(payload) if payload else ''

    headers = {
        'OK-ACCESS-KEY': APIKEY,
        'OK-ACCESS-SIGN': generate_signature(timestamp, http_method, url_path, body_str, APISECRET),
        'OK-ACCESS-TIMESTAMP': timestamp,
        'OK-ACCESS-PASSPHRASE': PASS,
        'Content-Type': 'application/json'
    }

    url = BASE_URL + url_path

    if http_method.upper() == 'GET':
        response = requests.get(url, headers=headers)
    elif http_method.upper() == 'POST':
        response = requests.post(url, headers=headers, data=body_str)
    else:
        raise ValueError("Unsupported HTTP method")

    return response.json()

Example: Placing a Market Buy Order

order_data = {
    "instId": "BTC-USDT",
    "tdMode": "cash",
    "side": "buy",
    "ordType": "market",
    "sz": "0.001"
}

result = send_signed_request("POST", "/api/v5/trade/order", order_data)
print(result)

This approach ensures consistent serialization of the payload both in the signature and in the actual request.


Common Mistakes and Fixes

IssueSolution
Using json.dumps(body) only in request but not in signatureSerialize body once and use same string for both
Mismatched timestampsGenerate timestamp right before signing
Including query parameters in path incorrectlyOnly use path (e.g., /api/v5/trade/order), not full URL
Forgetting to set Content-Type: application/jsonAlways include this header

👉 Learn how to automate trades safely using secure API practices.


Frequently Asked Questions (FAQ)

Q: What does error code 50113 mean?
A: Error code 50113 means "Invalid Sign". This indicates that the signature provided in the OK-ACCESS-SIGN header does not match what the server expects based on your timestamp, method, endpoint, and body.

Q: Should I include query parameters in the request path when signing?
A: Yes—if your request includes query parameters (e.g., /api/v5/account/balance?ccy=BTC), you must include them exactly as they appear in the URL path when generating the signature.

Q: Can I reuse the same timestamp for multiple requests?
A: No. Each request must have a unique timestamp within a few seconds of server time. Reusing timestamps may cause signature invalidation due to replay protection.

Q: Do I need to enable anything on my OKX account for API trading?
A: Yes. You must create an API key with sufficient permissions (e.g., "Trade" permission enabled) and ensure IP whitelisting allows your server's IP address.

Q: Is it safe to hardcode API keys in scripts?
A: No. Always store API keys securely using environment variables or secret management tools, especially in production environments.

Q: Does OKX support simulated or paper trading?
A: Yes. You can enable demo trading by adding the header 'x-simulated-trading': '1' to your requests. This allows testing without risking real funds.


Final Tips for Reliable Integration

With proper implementation, signing OKX POST API requests becomes straightforward and reliable.

👉 Start building powerful trading applications with real-time API access today.