API Authentication: HMAC with SHA-256

Overview

Hash-based Message Authentication Code (HMAC) with SHA-256 cryptographic hashing is a common security mechanism used for Authentication and Authorization when sending requests to secure APIs. The process involves both encryption and hashing elements. To ensure that Skipify remains secure, we utilize MAC with SHA-256 for most Skipify API requests.

Required Headers for HMAC with SHA-256 Requests

API calls that utilize HMAC with SHA-256 authentication require a set of 4 headers to be sent along with the request:

  • x-merchant-id
  • timestamp
  • nonce
  • signature

The first three headers will be used to generate the string for the signature as shown below.

Header ElementDescriptionExample Value
X-Merchant-IdThis is a GUID (Globally Unique Identifier) that was provided to you during onboarding by the Skipify team. You can also view this identifier by logging into the merchant portal.76aae15d-de06-46df-91c8-3ff5beca1c8d
TimestampThis is the current timestamp in Unix Epoch time (s).1616562172
NonceA nonce is a unique identifier to protect private communications by preventing replay attacks. Nonces are random or pseudo-random numbers/strings that authentication protocols attach to communications.

This is a string set by you that must be unique for each request.
51c1442ebe284b74814cbc8411502b7c
SignatureThe signature is a hashed string containing both your Skipify merchant credentials, and elements of the specific request.

Below are additional Instructions for generating the signature.
8bd00d630eea81f7ec1243cc0e2bc40516ecc3b91d863ba5d398d19aa294c2d8

Generating the Signature

Signatures will be generated from a string containing the x-merchant-id, x-api-key, timestamp, nonce, requestUri, requestMethod, and requestBody. Descriptions for these elements are listed below:

String ElementDescriptionExample Value
X-Merchant-IdThis is a GUID (Globally Unique Identifier) that was provided to you during onboarding by the Skipify team. You can also view this identifier by logging into the merchant portal.76aae15d-de06-46df-91c8-3ff5beca1c8d
X-Api-KeyYour API Key that was provided to you during onboarding by the Skipify team. You can view your key in the Configuration page of the merchant portal.f51fa8fc7b2d55689c21009ab3ffcbc4
TimestampThis is the current timestamp in Unix Epoch time (s).1616562172
NonceA nonce is a unique identifier to protect private communications by preventing replay attacks. Nonces are random or pseudo-random numbers/strings that authentication protocols attach to communications.

This is a string set by you that must be unique for each request.
51c1442ebe284b74814cbc8411502b7c
RequestURIThis is the request path. If performing a GET request, the query string must be included.

Note: The query string parameters must be in alphabetical order and the parameter values must be URL encoded using UTF-8 format like the example shown.
orders/e40b83b7-4c5e-47e9-b6a7-c005831eb1d8/capture

payment-requests?begin=2022-02-02t21%3a21%3a21z&end=2022-02-02t21%3a21%3a21z&pageNumber=1&pageSize=25

Note: do not include a leading or trailing forward slash, "/", in the URI
RequestMethodMethod associated with the request.GET | POST | PUT | DELETE
RequestBodyBody of the request (empty for GET requests).

See specific documentation for the required body for each endpoint.
{
"object": {
"a": "b",
"c": "d",
"e": "f"
},
"array": [
1    
2
    ],
"string": "Hello World"
}

The string should be formatted as follows:

[x-merchant-id]|[x-api-key]|[Timestamp]|[Nonce]|[RequestURI]|[RequestMethod]|[RequestBody]

Using the example data from the table above, the generated string will be as follows:

POST:
76aae15d-de06-46df-91c8-3ff5beca1c8d|f51fa8fc7b2d55689c21009ab3ffcbc4|1616562172|51c1442ebe284b74814cbc8411502b7c|orders/e40b83b7-4c5e-47e9-b6a7-c005831eb1d8/capture|POST|{"object":{"a":"b","c":"d","e":"f"},"array":[1,2],"string":"Hello World"}

GET:
76aae15d-de06-46df-91c8-3ff5beca1c8d|f51fa8fc7b2d55689c21009ab3ffcbc4|1616562172|51c1442ebe284b74814cbc8411502b7c|payment-requests?begin=2022-02-02t21%3a21%3a21z&end=2022-02-02t21%3a21%3a21z&pageNumber=1&pageSize=25|GET|

Once the string has been generated, it should be formatted, encoded, and hashed following the steps below:

  1. Remove all whitespace from the string (spaces, line feeds, carriage returns, and tabs should be replaced with an empty string)
  2. Convert string to uppercase
  3. Encode the string with Base64
  4. Generate a SHA-256 hash from the Base64 encoded string

Generating the Signature Examples

The following are code examples, all of which generate the HMAC with SHA256 signature. These methods take in the parameters (X-Merchant-Id, X-Api-Key, Timestamp, Nonce, RequestURI, RequestMethod, RequestBody) and return a value to be used in the request signature header.

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

public class HeaderUtil
{
    /*
    Generate the MAC with SHA-256 signature using the necessary keys and HTTP elements
    <param name="merchantId">The provided MID (e.g., "00000000-0000-0000-0000-000000000000") </param>
    <param name="apiKey">The provided API key (e.g., "00000000-0000-0000-0000-000000000000")</param>
    <param name="timestamp">The current Unix Epoch time (in seconds) (e.g., "1634324248" )</param>
    <param name="nonce">Unique Request Identifier (e.g., "00000000-0000-0000-0000-000000000000")</param>
    <param name="uriPath">Path of the request URI (e.g., "items/1")</param>
    <param name="httpVerb">HTTP verb of the request (e.g., "POST")</param>
    <param name="body">Body of the request(e.g., "{ "field1": "value1" }")</param>
    <returns> The signature header for MAC with SHA-256 requests </returns> */

    public String macSignature(String merchantId, String apiKey, String timestamp, String nonce, String uriPath,
                                String httpVerb, String body)
            throws NoSuchAlgorithmException
    {
        String formattedSignature = merchantId + "|" + apiKey + "|" + timestamp
                + "|" + nonce + "|" + uriPath + "|" + httpVerb + "|" + body;
        //remove all whitespace and convert to uppercase
        formattedSignature = formattedSignature.replaceAll("[\\r\\n\\t\\s]", "").toUpperCase();
        //base 64 encode
        String base64EncodedString = Base64.getEncoder().encodeToString(formattedSignature.getBytes());
      
      	MessageDigest digest = null;
        try {
            digest = MessageDigest.getInstance("SHA-256");
        } catch(NoSuchAlgorithmException e) {
            System.out.println("Could not find algorithm");
        }
      
        byte[] sha256Hash = digest.digest(base64EncodedString.getBytes(StandardCharsets.UTF_8));
        return convertBytesToHex(sha256Hash);

    }

    private static String convertBytesToHex(byte[] hash)
    {
        StringBuilder hexString = new StringBuilder(2 * hash.length);
        for (byte b : hash)
        {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1)
            {
                hexString.append('0');
            }
            hexString.append(hex);
        }
        return hexString.toString();
    }
}
using System;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;

namespace MAC {
    public class HeaderUtility {

        /// <summary>
        /// Generate the MAC signature with SHA-256 using the necessary keys and HTTP elements
        /// </summary>
        /// <param name="merchantId">The provided MID (e.g., "00000000-0000-0000-0000-000000000000") </param>
        /// <param name="apiKey">The provided API key (e.g., "00000000-0000-0000-0000-000000000000")</param>
        /// <param name="timestamp">The current Unix Epoch time (in seconds) (e.g., "1634324248" )</param>
        /// <param name="nonce">Unique Request Identifier (e.g., "00000000-0000-0000-0000-000000000000")</param>
        /// <param name="uriPath">Path of the request URI (e.g., "items/1")</param>
        /// <param name="httpVerb">HTTP verb of the request (e.g., "POST")</param>
        /// <param name="body">Body of the request(e.g., "{ "field1": "value1" }")</param>
        /// <returns>The signature header for MAC with SHA-256 requests </returns>
        public static string GenerateSignature(string merchantId, string apiKey, string timestamp, string nonce, string uriPath, string httpVerb, string body) {
            // Compose signature elements
            string signatureRaw = $"{merchantId}|{apiKey}|{timestamp}|{nonce}|{uriPath}|{httpVerb}|{body}";

            // Remove characters CR, LF, TAB, and whitespace
            string signatureCleaned = Regex.Replace(signatureRaw, @"[\r\n\t\s]", string.Empty);

            // Convert string to upper-case
            string signatureUpper = signatureCleaned.ToUpperInvariant();

            // Base64 encode string
            string signatureBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(signatureUpper));

            // SHA-256 hashed string
            using SHA256 sha256 = SHA256.Create();
            byte[] signatureHashedBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(signatureBase64));
            string signature = string.Join(null, signatureHashedBytes.Select(b => b.ToString("x2")));

            return signature;
        }
    }
}
<?PHP
/**
 * Generate the MAC with SHA-256 signature using the necessary keys and HTTP elements
 * @param string $merchant_id The provided MID (e.g., "00000000-0000-0000-0000-000000000000")
 * @param string $api_key The provided API key (e.g., "00000000-0000-0000-0000-000000000000")
 * @param integer $timestamp The current Unix Epoch time (in seconds) (e.g., "1634324248"
 * @param string $nonce Unique Request Identifier (e.g., "00000000-0000-0000-0000-000000000000")
 * @param string $uri_path Path of the request URI (e.g., "items/1")
 * @param string $http_verb HTTP verb of the request (e.g., "POST")
 * @param string $body Body of the request(e.g., "{ "field1": "value1" }")
 * @return string The signature header for MAC with SHA-256 requests
 */
function mac_signature($merchant_id, $api_key, $timestamp, $nonce, $uri_path, $http_verb, $body)
{
    // Compose signature elements
    $signature_raw = "{$merchant_id}|{$api_key}|{$timestamp}|{$nonce}|{$uri_path}|{$http_verb}|{$body}";

    // Remove characters CR, LF, TAB, and whitespace
    $signature_cleaned = str_replace(array("\r", "\n", "\t", " ") , '', $signature_raw);

    // Convert string to upper-case
    $signature_upper = strtoupper($signature_cleaned);

    // Base64 encode string
    $signature_base64 = base64_encode($signature_upper);

    // SHA-256 encode string
    $signature = hash('sha256', $signature_base64);

    return $signature;
}
?>

Skipify baner with link to contact us form