Reputation: 31
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---
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))
The timestamp value is the same as the OK-ACCESS-TIMESTAMP header with nanosecond precision.
The request method should be UPPER CASE, i.e. GET and POST.
The requestPath is the path of requesting an endpoint. Example:
/orders?before=2&limit=30
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"}
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
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
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
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
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