HMAC Signing

Background

HMAC stands for Hash-based Message Authentication Code, and is described here: https://en.wikipedia.org/wiki/HMAC, but essentially it's a cryptographically secure hash value generated based on a computed value and a pre-shared key.

HMAC Secret

HMAC signing is enabled by associating an HMAC secret with the API key - this will be either an API user or an API integration. Once a secret is set, valid HMAC signatures must accompany every API request.

HMAC Header Creation

The HMAC signature should be sent in a custom HTTP Header named X-PX-Request-ID>. This header should be built using the following values concatenated together, in this order:

  1. A timestamp as represented as number of milliseconds since Linux Epoch
  2. The portion of the request URI following '/api/v1', including any URL parameters, e.g. /merchant/30/restaurants/pxweb/menu/tier?key=9dxxxxxfe843bbxxxxxcd9xxxxxf88d850xxxxx
  3. The request body, if the request has one. In the case of of GET requests this will, of course, not be included

Once this string is generated, the following operations should be performed on it to get the message signature:

  1. HMAC should be computed using HMACSHA256 algorithm using the string described above
  2. The resulting binary HMAC value should be base64-encoded
  3. A string should be built by concatenating the timestamp used in the hash, followed by a semicolon, followed by the base64-encoded hash value
  4. The resulting string should be base64-encoded as a UTF-8 string
  5. The resulting base64-encoded string should be sent in an HTTP header named X-PX-Request-ID

GET Example - no body:

Request URI: https://od.pxsweb.com/api/v1/merchant/30/restaurants/pxweb/menu/tier?key=9dxxxxxfe843bbxxxxxcd9xxxxxf88d850xxxxx
Body: None
Timestamp: 1583254634525

Value to be hashed: 1583254634525/merchant/30/restaurants/pxweb/menu/tier?key=9dxxxxxfe843bbxxxxxcd9xxxxxf88d850xxxxx
Base64 encoded hash result: 4iX2WnHGrCL2fIc2V9zOH2z2SY/UswsQS+MQSmlrlN8=

Header without base64-encoding: 1583254634525;4iX2WnHGrCL2fIc2V9zOH2z2SY/UswsQS+MQSmlrlN8=

Actual header to be sent:
X-PX-Request-ID: MTU4MzI1NDYzNDUyNTs0aVgyV25IR3JDTDJmSWMyVjl6T0gyejJTWS9Vc3dzUVMrTVFTbWxybE44PQ==
		

POST Example - json body:

Request URI: https://od.pxsweb.com/api/v1/orders/xxxxx/items?key=9dxxxxxfe843bbxxxxxcd9xxxxxf88d850xxxxx
Body: {"id":"xxx","quantity":1,"size":""}
Timestamp: 1583254967310

Value to be hashed: 1583254967310/orders/xxxxx/items?key=9dxxxxxfe843bbxxxxxcd9xxxxxf88d850xxxxx{"id":"xxx","quantity":1,"size":""}
Base64 encoded hash result: uE9rkxYON1+FU+SWVrRVTZFpO04w0IUvkm28GWF7hI=

Header without base64-encoding: 1583254967310;EuE9rkxYON1+FU+SWVrRVTZFpO04w0IUvkm28GWF7hI=

Actual header to be sent:
X-PX-Request-ID: MTU4MzI1NDk2NzMxMDtFdUU5cmt4WU9OMStGVStTV1ZyUlZUWkZwTzA0dzBJVXZrbTI4R1dGN2hJPQ==

Postman Pre-Request Script

The following Javascript implements this algorithm in Javascript as a Postman Pre-request Script

// 1. Get timestamp in milliseconds
var timestampMs = new Date().getTime();

// 2. Get all of url, including query parameters, following /api/v1
var fullUrl = pm.variables.replaceIn(pm.request.url.toString());
var partialUrl = fullUrl.match(/\/api\/v1(.*)$/)[1];

// 3. Get request body, if one exists
var requestBody = pm.request.body.raw || '';

// 4. Concatenate
var rawHeader = ''.concat(timestampMs, partialUrl, requestBody);

// 5. Create binary HMAC using your secret and encode binary result in base64
var signBytes = CryptoJS.HmacSHA256(rawHeader, pm.variables.get('hmac_secret'));
var signBase64 = CryptoJS.enc.Base64.stringify(signBytes);

// 6. Concatenate timestamp, a semicolon and the base64 signed value and base64 encode the result
var hmacBase64 = btoa(''.concat(timestampMs, ';', signBase64));

// 7. Set custom header with final header value
pm.request.headers.add({
	key: "X-PX-Request-ID",
	value: hmacBase64
});