Authentication

Shoptimiza API Authentication

Every request to Shoptimiza API must include the X-Shoptimiza-Auth http header. The goal of this header is to prevent unauthorised use of the platform, API's, third party services, etc by manipulating, reusing, or copying the request data.

The header includes apiKey, unix timestamp, body signature (optional) and a hmac signature of: apiKey, unix timestamp, body signature (optional), url and http verb.

The header

The header can be built by concatenating your apiKey a unix timestamp and a signature with dots. I.e $apiKey.$unix_time.$signature.

Where:

  • $apiKey: Is your Shoptimiza API Key
  • $unix_time: UNIX time
  • $signature: Request signature

How to build the signature

For GET, HEAD, DELETE requests:
The header value looks like $apiKey.$unixTime.$signature. Were signature is built by:

  1. Join with dot as a separator apiKey, unixTime, httpVerb and target url without protocol
  2. Get the hmacSHA256 signature of the previous string formatted with base64

Building the authentication header is better explained with an example:

// We want to request 'https://api.shoptimiza.com/some_function
var urlWithouthProtocol = 'api.shoptimiza.com/some_function';
var apiKey = '123';
var unixTime = unixTimestamp();
var httpVerb = 'GET' // always use uppercase verbs

var signature = base64(hmacSHA256(apiKey + '.' + unixTime + '.' + httpVerb + '.' +  urlWithouthProtocol);
var headerValue = apiKey + '.' + unixTime + '.' + signature;

We also have an implementation for node.js implementation for node.js

For POST, PUT the signature is similar but adding the body signature (sha1 encoded as base64).
The header value will look like $apiKey.$unix_time.$bodySignature.$signature.
The signature can be built as follows:

// We want to request 'https://api.shoptimiza.com/some_function
var urlWithouthProtocol = 'api.shoptimiza.com/some_function';
var bodySignature = ...// base64(sha1(body));
var apiKey = '123';
var unixTime = unixTimestamp();
var httpVerb = 'GET' // always use uppercase verbs

var signature = base64(hmacSHA256(apiKey + '.' + unixTime + '.' + httpVerb + '.' + urlWithouthProtocol + '.' + bodySignature);
var headerValue = apiKey + '.' + unixTime '.' +bodySignature + '.' + signature;

Errors

Any mismatch between the expected signature and the submitted signature will end up into a 403 error. The server will return also a JSON object with a reason like:

{ "reason": "invalid apiKey" }

We include a timestamp ($unix_time) in the signature in order to prevent header replay attacks. Is up to the server to set a timeout but we recommend something around 2 seconds. The server will respond with status 403 and {"reason":"timeout", "time": xxxxxxxxxx} when the difference between server time and header $unix_time is bigger than timeout defined by the server. In this case the client will retry the request updating the header and adjusting the time.

The proper way to do this is to sum the time difference between client and server to the time client request at header.

For instance:

// pseudo code here

header = createHeader(apiKey, now, verb, url)
result = request(verb, url, header);
if result.statusCode === 403  && result.reason === 'timeout' {
   delta = now - result.time
   header(createHeader(apiKey, now + delta, verb, url);
   result = request(url, header);
}

Remember to limit the number of retries!

Possible errors

  • 403 {"reason": "missing header"} Header X-Shoptimiza-Auth not present
  • 403 {"reason": "invalid apiKey"} Action: Check the api key
  • 403 {"reason":"timeout", "time": xxxxxxxxxx} Action: Apply time delta to your time
  • 403 {"reason": "invalid signature"} Action: something is missing in your signature. Different url? lower case HTTP verb?

Recommendations

For client developers

Build your headers just before the request happens to prevent timeouts

For server developers

Track the incoming request start time before any other action. Later you can check if the request has timedout against that parameter. Otherwise you are creating a non real delay. An example explains better why:

// current time : 1
handleRequest(request){
doSomethingReallySlow()
// current time : 100
checkSomethingImportant();
// current time : 200
now = time() // now = 200;
checkHeaderTimeout(now, headerTime) // wooops! You just introduced a 200 time units delay
}

If you track time from the very begin this does not happen

// current time : 1
handleRequest(request){
now = time() // now = 1
doSomethingReallySlow()
// current time : 100
checkSomethingImportant();
// current time : 200
checkHeaderTimeout(now, headerTime) // now = 1 
}