I found a sample github that is able to properly generate a signature for the API in question, their code is as follows:
so i found their github which has a sample in object oriented like this:
if ($api === 'private') {
$this->check_required_credentials();
$timestamp = $this->seconds();
$xPhemexRequestExpiry = $this->safe_integer($this->options, 'x-phemex-request-expiry', 60);
$expiry = $this->sum($timestamp, $xPhemexRequestExpiry);
$expiryString = (string) $expiry;
$headers = array(
'x-phemex-access-token' => $this->apiKey,
'x-phemex-request-expiry' => $expiryString,
);
$payload = '';
if ($method === 'POST') {
$payload = $this->json($params);
$body = $payload;
$headers['Content-Type'] = 'application/json';
}
$auth = $requestPath . $queryString . $expiryString . $payload;
$headers['x-phemex-request-signature'] = $this->hmac($this->encode($auth), $this->encode($this->secret));
}
$url = $this->urls['api'][$api] . $url;
return array( 'url' => $url, 'method' => $method, 'body' => $body, 'headers' => $headers );
}
my code is core and looks like this, and I can not seem to get the proper result of a valid signature, any guidance possible?
$epoch=strtotime(date('r', time()).'+2 days');
$data = '{"clOrdId":"123456","ordType":"LIMIT","symbol":"BTC-USD","side":"BUY","orderQty":"0.1","price":"100"}';
$content=("/orders".$epoch.$data);
//SECRET KEY
$str = 'cn87t3Z8wLw-OR626cuAZFIUhHT2z3XSfEt6X8OBSyFjU3Ny05ZWYyLTQ3ZjItODkwNy1mODlhNWMxM2UzMWY';
$decode= base64_decode($str);
$signature = hash_hmac('SHA256',$content,'$decode');
In short I need to send the signature inside the headers like so:
"x-phemex-request-signature: $signature)",
UPDATED CODE:
$epoch=strtotime(date('r', time()).'+5 minutes');
$data = '{"clOrdId":"123456","ordType":"LIMIT","symbol":"BTC-USD","side":"BUY","orderQty":"0.1","price":"100"}';
$content="/orders".$epoch.$data;
//SECRET KEY
$str = 'cn87t3Z8wLw-OR626cuAZFIUhHT2z3XSfEt6X8OBSyFkMjE0NjU3Ny05ZWYyLTQ3ZjItODkwNy1mODlhNWMxM2UzMWY';
$signature = hash_hmac('SHA256',$auth,'cn87t3Z8wLw-OR626cuAZFIUhHT2z3XSfEt6X8OBSy***5ZWYyLTQ3ZjItODkwNy1mODlhNWMxM2UzMWY');
$headers = array(
"accept: application/json",
//API KEY
"x-phemex-access-token:e92028ca-4704-4317-8149-c1c7a7f85226",
"Content-Type: application/json",
"x-phemex-request-expiry: $epoch",
//this right here is what we need to have working proper
"x-phemex-request-signature: $signature)",
);
The format of a proper signature looks like:
API REST Request URL: https://api.phemex.com/orders
Request Path: /orders
Request Query:
Request Body: {"symbol":"BTCUSD","clOrdID":"uuid-1573058952273","side":"Sell","priceEp":93185000,"orderQty":7,"ordType":"Limit","reduceOnly":false,"timeInForce":"GoodTillCancel","takeProfitEp":0,"stopLossEp":0}
Request Expiry: 1575735514
Signature: HMacSha256( /orders + 1575735514 + {"symbol":"BTCUSD","clOrdID":"uuid-1573058952273","side":"Sell","priceEp":93185000,"orderQty":7,"ordType":"Limit","reduceOnly":false,"timeInForce":"GoodTillCancel","takeProfitEp":0,"stopLossEp":0})
signed string is /orders1575735514{"symbol":"BTCUSD","clOrdID":"uuid-1573058952273","side":"Sell","priceEp":93185000,"orderQty":7,"ordType":"Limit","reduceOnly":false,"timeInForce":"GoodTillCancel","takeProfitEp":0,"stopLossEp":0}
Related
After getting all the credentials I'm now requesting a resource from Etsy API. It's working fine without query parameters to the url but when I add them I get signature invalid.
Here's what I got working without the query parameters in the url:
// enconding, sorting and creating the base URI
function buildBaseString($baseURI, $method, $params){
$r = array();
ksort($params);
foreach($params as $key=>$value){
$r[] = "$key=" . rawurlencode($value);
}
return $method."&" . rawurlencode($baseURI) . '&' . rawurlencode(implode('&', $r));
}
// create encoded header
function buildAuthorizationHeader($oauth){
$r = 'Authorization: OAuth ';
$values = array();
foreach($oauth as $key=>$value)
$values[] = "$key=\"" . rawurlencode($value) . "\"";
$r .= implode(', ', $values);
return $r;
}
$consumer_key = 'the_key';
$consumer_secret = 'the_secret';
$shop_id = '00000000000';
$oauth_access_token = 'access_token';
$oauth_access_token_secret = 'token_secret';
$receipts_by_status_url = 'https://openapi.etsy.com/v2/shops/'.$shop_id.'/receipts/opens';
$oauth = array(
'oauth_callback' => 'oob',
'oauth_consumer_key' => $consumer_key,
'oauth_nonce' => time(),
'oauth_signature_method' => 'HMAC-SHA1',
'oauth_timestamp' => time(),
'oauth_token' => $oauth_access_token,
'oauth_version' => '1.0',
);
$base_info = buildBaseString($receipts_by_status_url, 'GET', $oauth);
$composite_key = rawurlencode($consumer_secret) . '&' . rawurlencode($oauth_access_token_secret);
$oauth_signature = base64_encode(hash_hmac('sha1', $base_info, $composite_key, true));
$oauth['oauth_signature'] = $oauth_signature;
$header = array(buildAuthorizationHeader($oauth), 'Expect:', 'Content-Type: application/x-www-form-urlencoded');
/*
* Set curl options
*/
//ob_start();
//$curl_log = fopen('php://output', 'w');
$ch = curl_init($receipts_by_status_url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 25);
curl_setopt($ch, CURLOPT_VERBOSE, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
// Execute curl
$result_token = curl_exec($ch);
when I add the parameters to the url:
$receipts_by_status_url = 'https://openapi.etsy.com/v2/shops/'.$shop_id.'/receipts/opens/?includes=Listings';
I get signature invalid.
$base info:
'GET&https%3A%2F%2Fopenapi.etsy.com%2Fv2%2Fshops%2F12962774%2Freceipts%2Fopen%3Fincludes%3DListings&oauth_callback%3Doob%26oauth_consumer_key%thekey%26oauth_nonce%3D1607965396%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1607965396%26oauth_token%oauthtoken%26oauth_version%3D1.0'
$oauth signature:
0Dr9wz24LU6NPkO7eKvP//HCOWk=
$header:
0 => string 'Authorization: OAuth oauth_callback="oob", oauth_consumer_key="consumerkey", oauth_nonce="1607965396", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1607965396", oauth_token="oauthtoken", oauth_version="1.0", oauth_signature="0Dr9wz24LU6NPkO7eKvP%2F%2FHCOWk%3D"' (length=301)
1 => string 'Expect:' (length=7)
2 => string 'Content-Type: application/x-www-form-urlencoded
What am I doing wrong?
I got it working.
I Manually created the base string without the function in the question, this was the root of the problem, because when I added parameters to the endpoint it didn't work.
The server takes the parameters in the authrization header and creates an OAuth signature and compares it to yours, if the don't match you'll get invalid signature like in my case.
The base string should be 3 chunks of data concatenated together and rawurlencoded:
Http method '&' url endpoint '&' params
Things to make sure:
Use a url endpoint without query parameters
Make sure the whole base string has only two ampersands.
Sort params alphabetically.
Use Postman and succeed in sending the request there, after that you can compare the signature generated in the header there to yours, when you can match it then it should work.
Make sure you're using the long url (with query params) in cURL, the clean one is only to create the base string.
I didn't notice but Etsy is actually sending the base string they created from the params in the response when the signature is invalid, all I had to do is compare mine to theirs and fix it.
Hope I helped anyone
I have the API request as outlined below which works fine (given the correct replacement of "xxxyyy"!) however I want to perform this call simultaneously with up to 5-10 different remote URLs. In the example below I've shown just 1 remote URL however I have an array of 10,000 urls which I would like to query as quickly as possible, all of which return the same structure in JSON.
After researching the topic I believe this can be done in PHP using Curl Multi, does anyone know if this is true, if so how would I go about this so i can call say 10 at once rather than each one individually?
<?php
$username = "xxxyyyxxxyyyxxxyyyxxxyyy";
$password = "xxxyyyxxxyyyxxxyyyxxxyyyxxxyyyxxxyyyxxxyyyxxxyyy";
$remote_url_1 = 'https://xxxyyyxxxyyyxxxyyyxxxyyy_1.json';
{
$headers = array();
$headers[] = "Authorization: Basic " . base64_encode("$username:$password");
$headers[] = "X-Page:" . $pages;
$opts = array(
'http'=>array(
'method'=>"GET",
'header' => $headers
)
);
$context = stream_context_create($opts);
$file1 = file_get_contents($remote_url, false, $context);
$data = json_decode($file1, true);
$data2 = (array_values($data));
$orderline_id = $data2[0];
$orderline_sale_number = $data2[1];
$orderline_status = $data2[2];
$orderline_notes = $data2[3];
}
}
?>
I have created a fully functional Coinbase API system in PHP for my own purposes. However, there is one part of the system that I cannot get to function properly: Market Orders.
The code that appears below is a reproduction of the steps that occur in the Object Oriented system. I thought linear code would be easier to troubleshoot in this forum than digging through layers of inheritance.
The Coinbase API returns error messages as shown in the comment text at the base of the code here. The 'price' parameter is not required for the 'market' order API, which is described here: Coinbase Exchange API Documentation. When I respond to the error messages by adding the requested fields, the order eventually succeeds, but the order is processed as a 'limit' order instead of a 'market' order as indicated by the type.
Can you spot the mistake I am making?
<?php
$settings = \parse_ini_file("API.ini", true);
$apiSecret = $settings['trader_sandbox']['API Secret'];
$apiKey = $settings['trader_sandbox']['API Key'];
$apiPassPhrase = $settings['trader_sandbox']['Passphrase'];
$urlBase = "https://api-public.sandbox.exchange.coinbase.com";
//get timestamp
$date = new \DateTime("now", new \DateTimeZone("America/Los_Angeles"));
$timestamp = $date->getTimestamp();
//API url
$relUrl = "/orders";
//set the method type GET|POST|DELETE|PUT
$method = "POST";
$params = [
"type" => "market",
"side" => "sell",
"product_id" => "BTC-USD",
"stp" => "dc",
"size" => "0.10000000"
];
//copied from coinbase's documentation added apiSecret
function signature($request_path = '', $body = '', $timestamp = false, $method = 'GET', $apiSecret=null) {
/**
* Modified $body assignment to exclude empty bodies
* #author Jared Clemence <jaredclemence#alum.drexel.edu>
* #ref https://community.coinbase.com/t/get-fills-invalid-signature-error-php-example-included/911
*/
$body = is_array($body) ? ( \count($body) > 0 ? json_encode($body) : null ) : $body;
$timestamp = $timestamp ? time() : $timestamp;
$what = $timestamp . $method . $request_path . $body;
return base64_encode(hash_hmac("sha256", $what, base64_decode($apiSecret), true));
}
$url = $urlBase . $relUrl;
$ch = curl_init($url);
$output = \json_encode($params);
$signature = \signature($relUrl, $output, $timestamp, $method, $apiSecret);
$headers = [
"User-Agent" => "Traderbot/v1.0",
"CB-ACCESS-KEY" => $apiKey,
"CB-ACCESS-SIGN" => $signature,
"CB-ACCESS-TIMESTAMP" => $timestamp,
"CB-ACCESS-PASSPHRASE" => $apiPassPhrase,
"Content-Type" => "application/json"
];
\curl_setopt($ch, CURLOPT_POST, true);
\curl_setopt($ch, CURLOPT_POSTFIELDS, $output);
foreach ($headers as $key => &$header) {
//this I found is necessary. Before I added this, the headers lacked the field data and only had the content values
$header = "{$key}:$header";
}
\curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
\curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
\curl_close($ch);
try {
$newResult = \json_decode($result);
$result = $newResult;
} catch (Exception $ex) {
}
var_dump( $result );
/**
*
* Output:
class stdClass#2 (1) {
public $message =>
string(13) "Invalid price"
}
*/
I would guess the only thing causing this to fail is lack of support currently for market orders via the Exchange API. From the docs:
Documentation for the upcoming market order feature is for reference
only. The feature is not yet available. You should however update your
feed handlers in preparation of the new message types.
Using the API here: https://dev.mention.com/resources/alert_mentions/#put-accounts-id-alerts-alert-id-mentions-mention-id
From what I understand, if I want to mark a specific "mention" as read, I do something like this:
$browser = new \Buzz\Browser(new \Buzz\Client\Curl);
$params = ['read' => true]; //tried this
$url = "https://api.mention.net/api/accounts/" . $this->getAccountId() . "/alerts/{$alert_id}/mentions/{$mention_id}";
if(!empty($params))
{
$url .= '?' . http_build_query($params); //i think this isnt needed because i pass $params below to the $browser->put but it was worth a try.
}
$response = $browser->put($url, array(
"Authorization: Bearer {$this->getAccessToken()}",
"Accept: application/json",
), $params);
if (200 != $response->getStatusCode()) {
return false;
}
However, when I run the code, it doesn't produce any errors and infact returns a valid response, but the "read" flag is still set to false.
Also tried:
$params = ['read' => 'true']; //tried this
$params = ['read' => 1]; //tried this
The Mention API accepts JSON in request bodies: https://dev.mention.com/#request-format
You can mark a mention as read like this:
$params = ['read' => true];
// params are json encoded
$params = json_encode($params);
$response = $browser->put($url, array(
"Authorization: Bearer $token",
"Accept: application/json",
// the Content-Type header is set to application/json
"Content-Type: application/json",
), $params);
I'm trying to build a basic app where I can modify my own google calendar using Google's RESTful api, but I'm having trouble getting the oAuth token. I've chosen to do it using the Services Application oAuth flow - I don't want to have to constantly re-agree to letting my own app use my calendar, but if there's a better way to do this please let me know.
Every time I make the http request I get a Bad Request error. Any ideas/help?
Here's the code:
<?php
$payload = array(
"iss"=>"services client email",
"scope"=>"https://www.googleapis.com/auth/calendar",
"aud"=>"https://accounts.google.com/o/oauth2/token",
"iat"=>date("U"),
"exp"=>date("U")+3600
);
$key = "Simple API key";
$jwt = encode_header($payload,$key);
print_r(request_g_token($jwt));
function request_g_token($jwt)
{
$data = array(
'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
'assertion' => $jwt
);
$data = http_build_query($data);
$url = "https://accounts.google.com/o/oauth2/token";
//echo $data;
$opts = array('http' =>
array(
'protocol_version' => '1.1',
'method' => 'POST',
'header' => "Host: accounts.google.com\r\n" .
"Content-Type: application/x-www-form-urlencoded\r\n",
'content' => $data
)
);
$context = stream_context_create($opts);
return (file_get_contents($url, false, $context));
}
function urlsafeb64encode($input){
return str_replace('=','',strtr(base64_encode($input),'+/','-_'));
}
function encode_header($payload, $key, $algo = 'RS256'){
$header = array('typ' => 'JWT', 'alg' => $algo);
$segments = array();
$segments[] = urlsafeb64encode(json_encode($header));
$segments[] = urlsafeb64encode(json_encode($payload));
$signing_input = implode('.',$segments);
$sig = sign_encode($signing_input,$key);
$segments[]=urlsafeb64encode($sig);
return implode('.',$segments);
}
function sign_encode($msg, $key){
return hash_hmac('sha256', $msg, $key, true);
}
?>
Any help would be greatly appreciated
UPDATE
So I went through the service process again and realized I need to use a private key, which I'm now doing. My major question is whether or not to include the "private-key.p12" part of what I downloaded from google or not. I'm still receiving a Bad Request error unfortunately...
UPDATE 2
Realized I needed to pull the key from the pk12 file, and I did so with this code:
function getKey($file){
$p12cert = array();
$fd = fopen($file, 'r');
$p12buf = fread($fd,filesize($file));
fclose($fd);
if ( openssl_pkcs12_read($p12buf, $p12cert, 'notasecret') )
{
//worked
$temp = $p12cert['pkey'];
$temp = str_replace("-----BEGIN PRIVATE KEY-----","",$temp);
$temp = str_replace("-----END PRIVATE KEY-----","",$temp);
return $temp;
}
else
{
//failed
return "failed";
}
}
However, it's still giving me a bad request error and I think it's to do with the fact that the key comes back in multiple lines. Any ideas?
For anyone experiencing this issue, I've basically come to the conclusion that google hasn't interfaced calendar with their service accounts yet, and am using a refresh token to achieve similar results without having to log in all the time.