I'm trying to sign an API request to OKX but have been getting "Invalid sign" responses. This is hard to debug, so hopefully someone has done this before. This is what I have:
// AUTHENTICATION https://www.okx.com/docs-v5/en/#rest-api-authentication
function api_request( $endpoint, $parameters ) {
$timestamp = new \DateTime( 'now', new \DateTimeZone( 'UTC' ) );
$timestamp = $timestamp->format( 'Y-m-d\TH:i:s.v\Z' );
$header = array( );
$passphrase = 'xxx';
$apikey = 'xxx';
$secretkey = 'xxx';
$url = 'https://aws.okx.com';
// build request path
$requestpath = '/api/v5/' . $endpoint;
if( count( $parameters ) > 0 ) {
$parameters = http_build_query( $parameters ); // query string encode the parameters
$requestpath .= '?' . $parameters;
}
// build header
$signed = base64_encode( hash_hmac( 'sha256', $timestamp . 'GET' . $requestpath, $secretkey ) );
$header[] = 'OK-ACCESS-KEY: ' . $apikey;
$header[] = 'OK-ACCESS-SIGN: ' . $signed;
$header[] = 'OK-ACCESS-TIMESTAMP: ' . $timestamp;
$header[] = 'OK-ACCESS-PASSPHRASE: ' . $passphrase;
// send request and receive response
$curl = curl_init(); // Get cURL resource
curl_setopt_array( $curl, array( // Set cURL options
CURLOPT_HTTPHEADER => $header, // set the header
CURLOPT_URL => $url . $requestpath, // set the request URL
CURLOPT_RETURNTRANSFER => 1 // ask for raw response instead of bool
) );
$response = curl_exec($curl); // Send the request, save the response
curl_close($curl); // Close request
$response = json_decode( $response, true ); // Convert to array
return $response;
}
// CHECK BALANCE https://www.okx.com/docs-v5/en/#rest-api-funding-get-balance
$a = api_request( 'account/balance', array() );
echo( '<pre>' ); var_dump( $a ); echo( '</pre>' );
This is what I get:
array(2) {
["msg"]=>
string(12) "Invalid Sign"
["code"]=>
string(5) "50113"
}
I tried including the base URL in $requestpath, with or without query parameters, etc. I am also not very familiar with hashing, I have seen python scripts attempting to do this very same thing that will hash it with something like: hash_hmac( 'sha256', bytes( $message, 'utf8' ), bytes( $secret, 'utf8' )
, not sure if I should be using something like this in PHP as well.
You have one small bug in creating your signature. You should add a fourth parameter true
to the hash_hmac
function to return the raw binary format.
So, your final code for the signature should look like this:
$signed = base64_encode(hash_hmac('sha256', $timestamp . 'GET' . $requestpath, $secretkey, true));
Final working code:
<?php
// AUTHENTICATION https://www.okx.com/docs-v5/en/#rest-api-authentication
function api_request( $endpoint, $parameters ) {
$timestamp = new \DateTime( 'now', new \DateTimeZone( 'UTC' ) );
$timestamp = $timestamp->format( 'Y-m-d\TH:i:s.v\Z' );
$header = array( );
$passphrase = 'xxx';
$apikey = 'xxx';
$secretkey = 'xxx';
$url = 'https://aws.okx.com';
// build request path
$requestpath = '/api/v5/' . $endpoint;
if( count( $parameters ) > 0 ) {
$parameters = http_build_query( $parameters ); // query string encode the parameters
$requestpath .= '?' . $parameters;
}
// build header
$signed = base64_encode( hash_hmac( 'sha256', $timestamp . 'GET' . $requestpath, $secretkey, true ) );
$header[] = 'OK-ACCESS-KEY: ' . $apikey;
$header[] = 'OK-ACCESS-SIGN: ' . $signed;
$header[] = 'OK-ACCESS-TIMESTAMP: ' . $timestamp;
$header[] = 'OK-ACCESS-PASSPHRASE: ' . $passphrase;
// send request and receive response
$curl = curl_init(); // Get cURL resource
curl_setopt_array( $curl, array( // Set cURL options
CURLOPT_HTTPHEADER => $header, // set the header
CURLOPT_URL => $url . $requestpath, // set the request URL
CURLOPT_RETURNTRANSFER => 1 // ask for raw response instead of bool
) );
$response = curl_exec($curl); // Send the request, save the response
curl_close($curl); // Close request
$response = json_decode( $response, true ); // Convert to array
return $response;
}
// CHECK BALANCE https://www.okx.com/docs-v5/en/#rest-api-funding-get-balance
$a = api_request( 'account/balance', array() );
echo( '<pre>' ); var_dump( $a ); echo( '</pre>' );
Tested with 3 endpoints: account/balance account/interest-rate account/trade-fee
UPDATED: Added code below that works for both request types (GET and POST):
<?php
// AUTHENTICATION https://www.okx.com/docs-v5/en/#rest-api-authentication
function api_request( $endpoint, $type = 'GET', $parameters = []) {
$timestamp = new \DateTime( 'now', new \DateTimeZone( 'UTC' ) );
$timestamp = $timestamp->format( 'Y-m-d\TH:i:s.v\Z' );
$header = array( );
$passphrase = 'xxx';
$apikey = 'xxx';
$secretkey = 'xxx';
$url = 'https://aws.okx.com';
// build request path
$requestpath = '/api/v5/' . $endpoint;
$body = '';
if( count( $parameters ) > 0) {
if ($type === 'GET') {
$parameters = http_build_query($parameters); // query string encode the parameters
$requestpath .= '?' . $parameters;
} else {
$body = json_encode($parameters);
}
}
// build header
$signed = base64_encode( hash_hmac( 'sha256', $timestamp . $type . $requestpath . $body, $secretkey, true ) );
$header[] = 'Content-Type: application/json';
$header[] = 'OK-ACCESS-KEY: ' . $apikey;
$header[] = 'OK-ACCESS-SIGN: ' . $signed;
$header[] = 'OK-ACCESS-TIMESTAMP: ' . $timestamp;
$header[] = 'OK-ACCESS-PASSPHRASE: ' . $passphrase;
// send request and receive response
$curl = curl_init(); // Get cURL resource
curl_setopt_array( $curl, array( // Set cURL options
CURLOPT_HTTPHEADER => $header, // set the header
CURLOPT_URL => $url . $requestpath, // set the request URL
CURLOPT_RETURNTRANSFER => 1 // ask for raw response instead of bool
) );
if ($type === 'POST') {
curl_setopt($curl, CURLOPT_POSTFIELDS, $body);
}
$response = curl_exec($curl); // Send the request, save the response
curl_close($curl); // Close request
$response = json_decode( $response, true ); // Convert to array
return $response;
}
// CHECK BALANCE https://www.okx.com/docs-v5/en/#rest-api-funding-get-balance
$a = api_request( 'trade/order', 'POST', array(
'instId' => 'BTC-USDT',
'tdMode' => 'cash',
'side' => 'buy',
'ordType' => 'limit',
'px' => '1000',
'sz' => '0.01'
) );
echo( '<pre>' ); var_dump( $a ); echo( '</pre>' );
If you want to test your code in the DEMO trading environment, don't forget to add a custom header: $header[] = 'x-simulated-trading: 1';