Zoe Fleischer
Zoe Fleischer

Reputation: 31

How to sign an OKEx API request?

I keep getting an invalid sign error when trying to authenticate to the OKEx API, but am unable to see why my sign is not going through. Another eye might help?

Here is some context from the OKEx API documentation:

*---Signing Messages---

  1. The OK-ACCESS-SIGN header is generated as follows: create a prehash string of timestamp + method + requestPath + body (where + represents String concatenation) prepare the Secret sign the prehash string with the Secret using the HMAC SHA256 encode the signature in the Base64 format Example: sign=CryptoJS.enc.Base64.stringify(CryptoJS.HmacSHA256(timestamp + 'GET' + '/users/self/verify', SecretKey))

  2. The timestamp value is the same as the OK-ACCESS-TIMESTAMP header with nanosecond precision.

  3. The request method should be UPPER CASE, i.e. GET and POST.

  4. The requestPath is the path of requesting an endpoint. Example: /orders?before=2&limit=30

  5. The body refers to the String of the request body. It can be omitted if there is no request body (frequently the case for GET requests). Example: {"product_id":"BTC-USD-0309","order_id":"377454671037440"}

  6. The SecretKey is generated when you create an APIKey. Example: 22582BD0CFF14C41EDBF1AB98506286D*

import hmac
import base64
import requests
import json

from Secrets import okex_key
from Secrets import okex_secret
from Secrets import okex_pass

#get time
def get_time():
    urltime= 'https://www.okex.com/api/general/v3/time'
    response=requests.get(urltime)
    time=response.json()
    time=time['iso']
    return time

# signature
def signature(timestamp, method, request_path, body,secret_key):
    if str(body) == '{}' or str(body) == 'None':
        body = ''
    message = str(timestamp) + str.upper(method) + request_path + str(body)
    mac = hmac.new(bytes(secret_key, encoding='utf8'), bytes(message, encoding='utf-8'), digestmod='sha256')
    d = mac.digest()
    return base64.b64encode(d)


# set request header
def get_header():
    body= {}
    request= 'GET'
    endpoint= '/api/spot/v3/accounts'
    header = dict()
    header['CONTENT-TYPE'] = 'application/json'
    header['OK-ACCESS-KEY'] = okex_key
    header['OK-ACCESS-SIGN'] = signature(get_time(), request, endpoint , body, okex_secret)
    header['OK-ACCESS-TIMESTAMP'] = str(get_time())
    header['OK-ACCESS-PASSPHRASE'] = okex_pass
    return header


url = 'http://www.okex.com/api/spot/v3/accounts'
header = get_header()
response= requests.get(url, headers=header)
response.json()

Upvotes: 3

Views: 9753

Answers (4)

Rok Povsic
Rok Povsic

Reputation: 4935

The problem is that you are giving two different time values when calculating the values for OK-ACCESS-SIGN and OK-ACCESS-TIMESTAMP. Put get_time() into a single variable and use it in both places.

current_time = get_time()

header['OK-ACCESS-SIGN'] = signature(current_time, request, endpoint , body, okex_secret)
header['OK-ACCESS-TIMESTAMP'] = str(current_time)

Also note that currently, in get_time, you ask OKEx's servers what time it is. But if your computer's local time is reasonably correct (which you can check at https://time.is, or use NTP on a server) you can avoid doing that HTTP request and just use the local time.

import datetime

def get_time():
    now = datetime.datetime.utcnow()
    t = now.isoformat("T", "milliseconds")
    return t + "Z"

Upvotes: 4

artonbej
artonbej

Reputation: 287

This is not in python but i leave here if anyone comes here from Flutter dev, This is how i use in dart (Flutter) if anyone is interested with a simple post method. I buy a coin with a take profit and stop loss

import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:intl/intl.dart';

class ApiOKX {

  static Map<String, String> headers(String method, api, String body) {

    String time = DateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").format(DateTime.now().toUtc());
    List<int> messageBytes = utf8.encode('$time$method$api$body');
    List<int> key = utf8.encode('secret');
    Hmac hash = Hmac(sha256, key);
    Digest digest = hash.convert(messageBytes);
    String token = base64.encode(digest.bytes);

    var headers = {

      // For OKX
      'OK-ACCESS-KEY': 'accessKey',
      'OK-ACCESS-SIGN': accessSign,
      'OK-ACCESS-TIMESTAMP': time,
      'OK-ACCESS-PASSPHRASE': 'your password',
      'Content-Type': 'application/json',
      'x-simulated-trading': '1'
    };

    return headers;
  }

  static Future<String?> post(BuildContext context, api, {body, params})  async {

    var uri = Uri.parse('$base$api');

    print(uri);
    print('POST');
    var b = jsonEncode(body);
    log('body: $b');

    var response = await http.post(
        uri,
        headers: headers('POST', api, b),
        body: b
    );

    if (response.statusCode == 200) {
      return response.body;
    } else {
      print('Request failed with status: ${response.statusCode}. ${response.body}');
    }

    return null;
  }

Calling from anywhere you want

  _buy() async {

    var payload =  {
      "instId": '${_leftCoin.text.toUpperCase()}-${_sideCoin.text.toUpperCase()}',
      "tdMode": "cash",
      "side": "buy",
      "ordType": "market",
      "sz": "100",
      "tpTriggerPx": _tp.text,
      "tpOrdPx": _tp.text,
      "slTriggerPx": _sl.text,
      "slOrdPx": _sl.text
    };

    var res = await ApiOKX.post(context, ApiOKX.order, body: payload);

    if (res != null) {

      var parsed = json.decode(res);

      if (parsed['data'].length > 0) {

        if (parsed['code'] == '0') {
          Toaster.success(context, parsed['data'][0]['sMsg']);
        }
        else {
          Toaster.error(context, parsed['data'][0]['sMsg']);
        }

      }
    }
  }

Upvotes: 0

valentinmk
valentinmk

Reputation: 1021

This is my solution for signing request to OKEx API. Thanks to zoe-fleischer and rok-povsic.

import base64
import datetime as dt
import hmac
import requests

APIKEY = "xxxxx"
APISECRET = "xxxx"
PASS = "xxxx"
BASE_URL = 'https://aws.okex.com'

def send_signed_request(http_method, url_path, payload={}):
    '''
    See https://stackoverflow.com/questions/66486374/how-to-sign-an-okex-api-request
    '''
    def get_time():
        return dt.datetime.utcnow().isoformat()[:-3]+'Z'
    
    def signature(timestamp, method, request_path, body, secret_key):
        if str(body) == '{}' or str(body) == 'None':
            body = ''
        message = str(timestamp) + str.upper(method) + request_path + str(body)
        mac = hmac.new(bytes(secret_key, encoding='utf8'), bytes(message, encoding='utf-8'), digestmod='sha256')
        d = mac.digest()
        return base64.b64encode(d)
    
    # set request header
    def get_header(request='GET', endpoint='', body:dict=dict()):
        cur_time = get_time()
        header = dict()
        header['CONTENT-TYPE'] = 'application/json'
        header['OK-ACCESS-KEY'] = APIKEY
        header['OK-ACCESS-SIGN'] = signature(cur_time, request, endpoint , body, APISECRET)
        header['OK-ACCESS-TIMESTAMP'] = str(cur_time)
        header['OK-ACCESS-PASSPHRASE'] = PASS
        return header

    url = BASE_URL + url_path
    header = get_header(http_method, url_path, payload)
    print(url)
    print(header)
    response = requests.get(url, headers=header)
    response.json()
    return response.json()

send_signed_request("GET", "/api/v5/account/balance", payload={})

Upvotes: 3

Ketan Ramani
Ketan Ramani

Reputation: 5773

I'm executing the REST API in Postman and It's working fine for me. Below are the steps. (You need to use timestamp from okex time api)

GET : https://www.okex.com/api/v5/account/balance

Header:

OK-ACCESS-KEY:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (API_KEY)
OK-ACCESS-SIGN:{{sign}}
OK-ACCESS-TIMESTAMP:{{timestamp}}
OK-ACCESS-PASSPHRASE:YOUR_PASSPHRASE
Content-Type:application/json

Pre-request Script

pm.sendRequest('https://www.okex.com/api/general/v3/time', function (err, res) {
        console.log('Response_ISO: '+res.json().iso);
        pm.expect(err).to.not.be.ok;
        var timestamp = res.json().iso;
        var sign = CryptoJS.enc.Base64.stringify(CryptoJS.HmacSHA256(timestamp + 'GET' + '/api/v5/account/balance', 'YOUR_SECRET_KEY'));
        console.log(sign);

        postman.setEnvironmentVariable('timestamp',timestamp)  
        postman.setGlobalVariable('timestamp',timestamp) 

        postman.setEnvironmentVariable('sign',sign)  
        postman.setGlobalVariable('sign',sign)
    }); 

Upvotes: 0

Related Questions