Skip to content

API Request Signing

Note

**The following applies to the xConnect and Asset Management APIs

https://assetmgmt-api.senecaxconnect.com

https://api.senecaxconnect.com

All HTTP API requests into the xConnect Platform must be signed by the client application and validated by the Asset Management/xConnect API Platform to ensure they come from authenticated clients. There are 2 important pieces of information - apiKey and secretKey that can be obtained from the cloud portal and embedded into the client application for signing requests. It's recommended that these keys are stored encrypted on the client side.

For example in this document, we use the following keys

apiKey

5501f50fdc62aee5d04dbd6a58b68b781ee2aaade8ad1eb24b1e4e77cb282ae2

secretKey

ARAzUzRzekFwRTNACBQYUx89LlZyImhKFVloHUVMDw8EGRxxSCckFgdFPysAAWJCLDgMdkstZzw3GGVqNHxXcno5Iz54LRBSKy0TaCBwNndkfQNdD38KAA==

The client application needs to process the API request details using 5 steps explained below. The input parameters include such information as the URL, HTTP method, query string, JSON payload, HTTP headers, the security keys, etc. The computation outcome is a signature value which is included in the HTTP header.

Step 1: Create a Canonical Request for signing

The first step is to build a string representation of your request in a pre-defined format.

canonicalRequest =
    httpRequestMethod + '\n'  +
    canonicalURI + '\n' +
    canonicalQueryString + '\n' +
    hexEncode(hash(payload));
hexEncode = hashlib.sha256(body_payload.encode('utf-8')).hexdigest()

if url_query_params != '':
    canonicalRequest = httpRequestMethod + '\n' + canonicalURI + '\n' + query_params_to_encrypt + '\n' + hexEncode
else:
    canonicalRequest = httpRequestMethod + '\n' + canonicalURI + '\n' + hexEncode

httpRequestMethod

Put one of the following methods in a line by itself - GET, POST, PUT, PATCH

POST

canonicalURI

The canonical URI is the URI-encoded version of the absolute path component of the URI, which is everything in the URI from the HTTP host to the question mark character ("?") that begins the query string parameters (if any).

/api/v1/kronos/gateways

canonicalQueryString

Create each line containing a name/value pair in the format name=value. Name field is converted to lowercase and URL-encoded. Sort all the lines in ascending order

Example QueryString:

lastName=Doe&firstName=Jane&Age=30


age=30
firstname=Jane
lastname=Doe
hexEncode(hash(payload))

The payload (if any) is most likely in JSON format. If there's no payload, use an empty string Hash the payload content with SHA-256 Hex-encode the hash value Convert the hex-encoded value to lowercase Example of an empty payload:

e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 Combining all of the results above produces the following string:

POST /api/v1/kronos/gateways age=30 firstname=Jane lastname=Doe e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

Finally, hash this computed canonical request, hex-encode and lowercase it. This is the result of step 1.

5a2d3589ffb15fab720069fbd26fd8e8311a1c7047e5899608faff450df6d7dc

Step 2: Create the string to sign

stringtoSign =
     hashedCanonicalRequest + '\n' +
     apiKey + '\n' +
     requestTimestamp + '\n' +
     apiversion;

hashedCanonicalRequest

This is the result of step 1

5a2d3589ffb15fab720069fbd26fd8e8311a1c7047e5899608faff450df6d7dc

apiKey

Provided apiKey from xConnect team (support@senecaxconnect.com)

5501f50fdc62aee5d04dbd6a58b68b781ee2aaade8ad1eb24b1e4e77cb282ae2

requestTimestamp

Current UTC time in ISO-8601 format - YYYY-MM-DDThh🇲🇲s.sssZ

2016-04-12T14:28:36.218Z

apiVersion

Version is 1

1

Combining all of the results above produces the following string. This is the result of step 2.

5a2d3589ffb15fab720069fbd26fd8e8311a1c7047e5899608faff450df6d7dc
5501f50fdc62aee5d04dbd6a58b68b781ee2aaade8ad1eb24b1e4e77cb282ae2
2016-04-12T14:28:36.218Z
1

Step 3: Create the signing key

In this step we're going to use the HMAC-SHA256 algorithm a few times to generate the signing key. In the pseudocode, it'll be shown as hmacSha256(key, data). Note the order of the input parameters because if you're using some third-party library for this function, the order of the input parameters might be reversed. The output of this function is in binary, hence note the hex-encode function being used below

Java

signingKey = secretKey; signingKey = hexEncode(hmacSha256(apiKey, signingKey)); signingKey = hexEncode(hmacSha256(requestTimestamp, signingKey)); signingKey = hexEncode(hmacSha256(apiVersion, signingKey)); secretKey

Provided secretKey from Kronos portal

apiKey

Provided apiKey from Arrow Connect portal

requestTimestamp

Must be the same value as in step 2

apiVersion

Must be the same value as in step 2

For the example in this guide, here's the output of this step. This is the result of step 3

signingKey = ARAzUzRzekFwRTNACBQYUx89LlZyImhKFVloHUVMDw8EGRxxSCckFgdFPysAAWJCLDgMdkstZzw3GGVqNHxXcno5Iz54LRBSKy0TaCBwNndkfQNdD38KAA== signingKey = 3c6e85f6a719e5b8bd77fde0cbdbe19d947f38451afbc8ef6e49a083d86a9c54 signingKey = 3223bf9bc2d2180046cc40c2e1ed6f9d08261a6c4a394b23c5311e83633a8ef7 signingKey = d0d1518fc5290c22f1444d46d9c08dd03cc33c6fdad8bbcd57be65b1e2b0b493

Step 4: Create the final signature

The signature is computed by signing the result from step 2 and step 3

Java

signature = hexEncode(hmacSha256(signingKey, stringToSign) signingKey

Result of step 3

stringToSign

Result of step 2

Result of step 4 is below

28c3ab6cc82294b61e9b2855b428090e474fd1e066c4da63f9715bd2204df553

Step 5: Add required headers to request and submit

x-arrow-apikey: Provided apiKey x-arrow-date: requestTimestamp from step 2 x-arrow-version: apiVersion from step 2 x-arrow-signature: final signature from step 4

x-arrow-apikey: 5501f50fdc62aee5d04dbd6a58b68b781ee2aaade8ad1eb24b1e4e77cb282ae2
x-arrow-date: 2016-04-12T14:28:36.218Z
x-arrow-version: 1
x-arrow-signature: 28c3ab6cc82294b61e9b2855b428090e474fd1e066c4da63f9715bd2204df553

Complete API Request Example

Note

**The Java example simply generates the signed token. You must use the signature within a tool such as Postman to perform the full request.

 public void generateSignedRequestToken() {

     String API_KEY = "Your_Raw_API_key";
     String SECRET_KEY = "Your_Raw_Secret_key";
     String apiMethodUri = "/api/v1/kronos/devices";
     String timestamp = Instant.now().toString();
     String httpRequestMethod = "GET";
     List<String> uriParameters = new ArrayList<>();
     HashMap<String, String> uriParametersMap = new HashMap<>();
     String signature = "";

     try {
            // region Specify query parameters here
            uriParametersMap.put("_page", "0");
            uriParametersMap.put("_size", "100");
            // endregion

            for (Map.Entry<String, String> entry : uriParametersMap.entrySet()) {
                String name = entry.getKey();
                String value = entry.getValue();
                try {
                    uriParameters.add(
                            String.format("%s=%s", URLEncoder.encode(name.toLowerCase(), StandardCharsets.UTF_8.toString()),
                                    AcsUtils.trimToEmpty(value)));
                } catch (Exception e) {
                    // ignore
                }
            }

            // region Create canonical request
            StringBuilder builder = new StringBuilder();
            builder.append(httpRequestMethod).append('\n');
            builder.append(apiMethodUri).append('\n');

            // append parameters
            if (uriParameters.size() > 0) {
                Collections.sort(uriParameters);
                uriParameters.forEach(p -> builder.append(p).append('\n'));
            }

            builder.append(sha256Hex(""));
            String canonicalRequest = builder.toString();
            // endregion

            // region Sign the string
            StringBuilder stringToSign = new StringBuilder();
            stringToSign.append(sha256Hex(canonicalRequest)).append('\n');
            stringToSign.append(API_KEY).append('\n');
            stringToSign.append(timestamp).append('\n');
            stringToSign.append("1");

            signature = hmacSha256Hex(
                    hmacSha256Hex("1",
                            hmacSha256Hex(timestamp, hmacSha256Hex(API_KEY, SECRET_KEY))),
                    stringToSign.toString());
        } catch (Exception e) {
        }
     System.out.print("Signed auth token = " + signature);
}

 public static String hmacSha256Hex(String key, String data) {
    try {
        SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSha256");
        Mac mac = Mac.getInstance("HmacSha256");
        mac.init(signingKey);
        return printHex(mac.doFinal(data.getBytes(StandardCharsets.UTF_8)));
    } catch (Exception e) {
        throw new AcsRuntimeException("error while calculating: " + "HmacSha256", e);
    }
}

public static String sha256Hex(String data) {
    try {
        return printHex(MessageDigest.getInstance("SHA-256").digest(data.getBytes(StandardCharsets.UTF_8)));
    } catch (Exception e) {
        throw new AcsRuntimeException("error while calculating: " + "SHA-256", e);
    }
}

public static String printHex(byte[] data) {
    return DatatypeConverter.printHexBinary(data).toLowerCase();
}
# -*- coding: utf-8 -*-
import datetime
import hashlib
import hmac
import json

import requests
import urllib.parse

dir(hashlib)

# Base URL and api method URI here:
base_url = 'https://assetmgmt-api.senecaxconnect.com'
api_method_uri = '/api/v1/kronos/telemetries/devices/{deviceHid}/latest'

# Put API key and Secret Keys (RAW) here:
# Contact support@senecaxconnect.com for this information.
application_hid = "your_applicationhid_goes_here"
apiKey = 'api_key_goes_here'
secretKey = 'secret_key_goes_here'
device_hid = 'device_hid_goes_here'

api_method_uri = api_method_uri.format(deviceHid=device_hid)

# *************************STEP-1 Create a Canonical Request for signing***********
httpRequestMethod = 'GET'

# Telemetry pull by application hid
canonicalURI = api_method_uri

# Example on how to handle the from/to timestamps for API calls
# from_timestamp_raw = datetime.datetime.now() - datetime.timedelta(hours=HOURS_TO_RETRIEVE)
# to_timestamp_raw = datetime.datetime.now()
# from_timestamp = from_timestamp_raw.strftime('%Y-%m-%dT%H:%M:%S.%fZ')
# to_timestamp = to_timestamp_raw.strftime('%Y-%m-%dT%H:%M:%S.%fZ')

# Paging variables go here.
PAGE_TO_RETRIEVE = '0'
NUM_ITEMS_ON_EACH_PAGE = '150'

# Example: URL Query params with timestamp ranges
# url_query_params = '_page=' + urllib.parse.quote(PAGE_TO_RETRIEVE) + '&' + '_size=' + urllib.parse.quote(NUM_ITEMS_ON_EACH_PAGE) + '&' + 'fromTimestamp=' + urllib.parse.quote(from_timestamp) + "&" + 'toTimestamp=' + urllib.parse.quote(to_timestamp)
# query_params_to_encrypt = '_page=' + PAGE_TO_RETRIEVE + '\n' + '_size=' + NUM_ITEMS_ON_EACH_PAGE + '\n' + 'fromtimestamp=' + from_timestamp + '\n' + 'totimestamp=' + to_timestamp

# URL query params with paging only
url_query_params = ''
query_params_to_encrypt = ''
# url_query_params = '_page=' + urllib.parse.quote(PAGE_TO_RETRIEVE) + '&' + '_size=' + urllib.parse.quote(NUM_ITEMS_ON_EACH_PAGE)
# query_params_to_encrypt = '_page=' + PAGE_TO_RETRIEVE + '\n' + '_size=' + NUM_ITEMS_ON_EACH_PAGE

# use this to handle any body payloads coming in
body_payload = ""

# print(canonicalQueryString)
# ************* REQUEST VALUES *************

hexEncode = hashlib.sha256(body_payload.encode('utf-8')).hexdigest()

if url_query_params != '':
    canonicalRequest = httpRequestMethod + '\n' + canonicalURI + '\n' + query_params_to_encrypt + '\n' + hexEncode
else:
    canonicalRequest = httpRequestMethod + '\n' + canonicalURI + '\n' + hexEncode

print(canonicalRequest)

# ********************STEP-2  Create the string to sign*********************************
hashedCanonicalRequest = hashlib.sha256(canonicalRequest.encode('utf-8')).hexdigest()

# Set the current timestamp of the request, API version should be set to 1
t = datetime.datetime.utcnow()
requestTimestamp = t.strftime('%Y-%m-%dT%H:%M:%S.%fZ')
apiVersion = "1"

# Step-2
stringToSign = hashedCanonicalRequest + '\n' + apiKey + '\n' + requestTimestamp + '\n' + apiVersion
print(stringToSign)

# ************STEP-3  Create the signing key**********************

def sign(key, msg):
    return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).hexdigest()

# Create the signing key using the function defined above.
signingKey = secretKey
signingKey = sign(apiKey.encode('utf-8'), signingKey)
signingKey = sign(requestTimestamp.encode('utf-8'), signingKey)
signingKey = sign(apiVersion.encode('utf-8'), signingKey)

# ************STEP-4  Create the final signature*******************************
# Sign the string_to_sign using the signing_key
signature = sign(signingKey.encode('utf-8'), stringToSign)


# Make the GET call for this API function
# Example with application HID
# url = base_url + api_method_uri + application_hid + "?" + url_query_params
url = base_url + api_method_uri + "?" + url_query_params
headers = {
    'Content-type': 'application/json',
    'Accept': 'application/json',
    'x-arrow-apikey': apiKey,
    'x-arrow-date': requestTimestamp,
    'x-arrow-version': apiVersion,
    'x-arrow-signature': signature
}

r = requests.get(url, headers=headers)

if r and r.content:
    # format the content as dictionary objects
    returnedData = json.loads(r.content)
    for data in returnedData['data']:
        print(data)
else:
    print('No telemetry data returned.')
else:
    print('There was no data returned from the API.')