Signing API requests

Current version: v1

Some Inbenta APIs require signed requests. The purpose of this page is to explain how you can create and validate API signatures.

Note: This documentation uses Reporting API examples to illustrate this process. 

First, include the following headers in all your requests:

  • x-inbenta-key: Like other Inbenta API, this API requires authorization.
  • authorization: Like other Inbenta API, this API requires authorization.
  • x-inbenta-signature: This is the main API header. See the following section for more detail.
  • x-inbenta-signature-version: This header identifies the protocol to follow when signing requests. There is only one version ( "v1") at this time.
  • x-inbenta-timestamp: This header carries a unix timestamp of the time of the request. This is a security measure to prevent replay attacks.

Note: The Inbenta API Signature Protocol is based on Oauth 1.0 RFC 5849.


In this page


Sign your requests

To calculate the signature of your requests, follow the steps below in the order indicated. Make sure to pay attention to when you must encode parts of your requests. You must have the relevant API key, secret and signature key. These credentials are mandatory in the signing process. You can obtain them in your Inbenta App under the Administration tab (e.g. under Administration > Reporting API).

Base String

Before you can create a signature, you need something to sign. The first step is to take the request you are about to send and turn it into a single string. To do that, the base string uses six pieces of data:

  1. The HTTP method (GETPOST, etc.),
  2. The URL path (from /v1 until the end, e.g. v1/events/sessions), percent-encoded.
  3. The query string parameters (if any), stringified following these steps:
    1. Sort alphabetically by key,
    2. For each parameter, create a string following the format {$key}={$value}stringifying and decoding the value before adding it to the mentioned string,
    3. Join all the above pieces together with a "&" character, and percent-encode the resulting string.
  4. The request body (if any), stringified and percent-encoded (For more information about body stringification, see the Troubleshooting section).
  5. The timestamp value in the x-inbenta-timestamp header,
  6. The signature version value in the x-inbenta-signature-version header.

Note: If your request does not contain any query parameters or body, do not add them to the signature. The other elements will always have a value and therefore must always be included.

To finish the base string, join the above elements with a "&" character. Here is an example in PHP:

$uri = "https://reporting-api-host.inbenta.com/v1/events/sessions?data_key=SEARCH&data_value=testing";  /* The target url */
 
/*
 * Assuming you work with a PSR Request (https://github.com/php-fig/http-message/blob/master/src/RequestInterface.php),
 * $request->getUri() == $uri
 */
 
// Get request method
$method = $request->getMethod();  // 'GET'
 
// Get request path (from API version, v1, onward)
$urlPath = urlencode($request->getUri()->getPath()); // 'v1/events/sessions'
 
// Join all given query string params in a single string
$queryParamsInArray = $request->getQueryParams();  // ['data_key' => 'SEARCH', 'data_value' => 'testing']
ksort($queryParamsInArray);
$queryStringEncodedElements = [];
foreach ($queryParams as $key => $value) {
    $value = urldecode(json_encode($value));
    $queryStringEncodedElements[$key] = "{$key}={$value}";
}
$queryString = rawurlencode(implode('&', $queryStringEncodedElements));  // 'data_key%253DSEARCH%26data_value%253Dtesting'
 
// Get request body
$body = urlencode($request->getBody()->getContents());  // '' (empty)
 
// Generate unix timestamp
$timestamp = time(); /* $request->getHeaderLine('x-inbenta-timestamp') */
 
// Fill signature version
$signatureVersion = 'v1'; /* $request->getHeaderLine('x-inbenta-signature-version') */
 
// Now that you have all the parts, join them if not empty
$signatureElements = [$method, $urlPath, $queryString, $body, $timestamp, $signatureVersion];
foreach ($signatureElements as $key => $elem) {
    if (empty($elem)) {
        unset($signatureElements[$key]);  // Skip it
    }
}
 
// Finally, join all the parts together
$myBaseString = implode('&', $signatureElements);  // 'GET&v1%2Fevents%2Fsessions&data_key%253DSEARCH%26data_value%253Dtesting&1548669124&v1'

Create the Signature Key

The only supported signature method is HMAC-SHA256. It uses a HMAC (Hash-based Message Authentication Code). It looks similar to a normal SHA256 hash, but it is in fact significantly different. Most importantly, it is immune to length extension attacks. It also needs two pieces: a key and the text to hash.

The text is the base string that you created in the previous step. You can obtain the key in your Inbenta App (e.g. under Administration > Reporting API). Once you have the base string and the signature, you can create the signature itself. As mentioned, Inbenta only support HMAC-SHA256 signatures. This means that the signature is always set to the result of HMAC-SHA (key, text).

Set the HMAC key to the signature key as described above. Set the HMAC text to the base string. The signature uses the result of the HMAC hashing.

Note: The hash is a hex-encoded digest. Many languages handle this by default, but it is possible that you may need to hex-encode it manually. It should always look like a string of characters (e.g. "ba7d2bf43c9f4eaecb70f655310b2913e35af8bdc9e30036da0c1b4f652134ad"), not like raw binary data or base64-encoded.

Here is an example in PHP:

$myBaseString = 'GET&v1%2Fevents%2Fsessions&data_key%253DSEARCH%26data_value%253Dtesting&1548669124&v1';
$signatureKey = 'fsfds3432fsf0er233xpeuem232qfsf';
 
$signature = hash_hmac('sha256', $myBaseString, $signatureKey);

Validate the API responses (optional)

The Inbenta APIs that requires signed requests must also return signed responses. This way both communications can be validated before considering them safe. This shows whoever is consuming your API that you can also be trusted.

The response is signed using three elements of data:

  1. The signature version that you sent with your request (in the x-inbenta-signature-version header).
  2. The timestamp that you sent with your request (in the x-inbenta-timestamp header).
  3. The body sent back to you in the response, percent-encoded and JSON-encoded (Because you want it as a string).

In summary, to validate a response, you must build the signature that you expect from the API so that you can compare and check if they match.

Here is an example in PHP:

// Make the call to the API
$response = myMethodToMakeApiRequests();
// Get back the signed header
$signedHeader = $response->getHeaderLine('x-inbenta-signature');
 
// Build the base string that the API should have generated
$responseElements = [
    $signatureVersionHeader,
    $timestampHeader,
    urlencode(json_encode($response->getBody(), JSON_UNESCAPED_SLASHES)),
];
$responseBaseString = implode('&', $responseElements);
 
// Generate the expected signature
$expectedSignature = hash_hmac('sha256', $responseBaseString, $signatureKey);
$responseIsValid = ($signedHeader === $expectedSignature);

Troubleshooting

URL path

When you take the URL path, make sure to start with the API version in the URL ("v1" for the moment). Continue until the end of the URI as explained above (not "/v1", just "v1"). Setting anything else creates an incorrect signature as it does not match this path.

Percent-encoding

Percent-encoding escapes all "url-friendly" characters from the base string (For more information about percent-encoding, click here). This means that, when you join the pieces together with the "&" character, the only "url-friendly" character is that "&" character. This makes sure that there are no issues when the system parses the signature provided (like problems with other "&" characters in the base string, which could happen if some parameter is not percent-encoded but was expected to be).

Stringifying the body

Certain languages like PHP escape some characters of the request body when you encode a string in JSON with json_encode. Be careful when you generate a string out of the request body. Make sure that you do not modify the body contents. if you do, the signature validation fails. If you follow the PHP example above, add the JSON_UNESCAPED_SLASHES flag to the json_encode call.