HMAC signature calculation

To ensure authenticity and data integrity of incoming requests Adyen requires these requests to be signed. This signature is based on a Hash-based Message Authentication Code (HMAC) calculated using a request's key-value pairs and a secret key, which is known only to you and Adyen. 

Before sending a request to Adyen, you calculate a signature and add it as a request parameter. When a request comes in, Adyen calculates the same signature based on the received key-value pairs and the secret key stored by Adyen. By verifying that both signatures are equal, Adyen ensures that the request is not tampered.

Similarly, you can validate responses from Adyen by calculating the corresponding signature and comparing it with the signature in the response.

Signatures can also be used to add an extra layer of security for notifications. However, the calculation for this functionality is different. For details, refer to the Signing notifications with HMAC guide.

Getting HMAC keys

You need to generate your secret HMAC keys to use them for signature calculation. To obtain these keys for the test and live platform, follow the steps below:

  1. Sign in to the Customer Area using your company-level account.

  2. From the main menu, select Account > Skins.

  3. Select an existing skin from the List tab or create a new skin by switching to the New tab.

  4. Click Generate new HMAC key both for the Test platform and Live platform. Then copy new keys and store them in a secure place in your system to access these values later.

  5. Provide a description for this skin.

  6. To save the skin click Create New Skin on Test at the page bottom.

Implementing signature calculation

In this tutorial, we use the following HMAC key and key-value pairs as an example.

Sample HMAC key:

44782DEF547AAA06C910C43932B1EB0C71FC68D9D0C057550C48EC2ACF6BA056

Sample key-value pairs:

Key

Value

shopperLocale

en_GB

merchantReference

paymentTest:143522\64\39255

merchantAccount

TestMerchant

sessionValidity

2018-07-25T10:31:06Z

shipBeforeDate

2018-07-30

paymentAmount

1995

currencyCode

EUR

skinCode

X7hsNDWp

Don't use this example key for your real integration with Adyen.

String HMAC_KEY = "44782DEF547AAA06C910C43932B1EB0C71FC68D9D0C057550C48EC2ACF6BA056";
Map<String, String> pairs = new HashMap<>();
pairs.put("shopperLocale", "en_GB");
pairs.put("merchantReference", "paymentTest:143522\\64\\39255");
pairs.put("merchantAccount", "TestMerchant");
pairs.put("sessionValidity", "2018-07-25T10:31:06Z");
pairs.put("shipBeforeDate", "2018-07-30");
pairs.put("paymentAmount", "1995");
pairs.put("currencyCode", "EUR");
pairs.put("skinCode", "X7hsNDWp");
HMAC_KEY = "44782DEF547AAA06C910C43932B1EB0C71FC68D9D0C057550C48EC2ACF6BA056"
pairs = {
    'shopperLocale': 'en_GB',
    'merchantReference': 'paymentTest:143522\\64\\39255',
    'merchantAccount': 'TestMerchant',
    'sessionValidity': '2018-07-25T10:31:06Z',
    'shipBeforeDate': '2018-07-30',
    'paymentAmount': '1995',
    'currencyCode': 'EUR',
    'skinCode': 'X7hsNDWp'
}
$HMAC_KEY = "44782DEF547AAA06C910C43932B1EB0C71FC68D9D0C057550C48EC2ACF6BA056";
$pairs["shopperLocale"] = "en_GB";
$pairs["merchantReference"] = "paymentTest:143522\\64\\39255";
$pairs["merchantAccount"] = "TestMerchant";
$pairs["sessionValidity"] = "2018-07-25T10:31:06Z";
$pairs["shipBeforeDate"] = "2018-07-30";
$pairs["paymentAmount"] = "1995";
$pairs["currencyCode"] = "EUR";
$pairs["skinCode"] = "X7hsNDWp";

1. Sort the key-value pairs by key.

SortedMap<String, String> sortedPairs = new TreeMap<>(pairs);
sorted_pairs = OrderedDict(sorted(pairs.items(), key=lambda t: t[0]))
ksort($pairs, SORT_STRING);

2. Replace null values with an empty string ("") and escape the following characters in the value of each pair:

  • "\" (backslash) as "\\"
  • ":" (colon) as "\:"
SortedMap<String, String> escapedPairs =
        sortedPairs.entrySet().stream()
                .collect(Collectors.toMap(
                        e -> e.getKey(),
                        e -> (e.getValue() == null) ? "" : e.getValue().replace("\\", "\\\\").replace(":", "\\:"),
                        (k, v) -> k,
                        TreeMap::new
                ));
def escape(val):
    if isinstance(val,int):
        return val
    if val is None:
        return ""
    return val.replace('\\', '\\\\').replace(':', '\\:')

escaped_pairs = OrderedDict(map(lambda t: (t[0], escape(t[1])), sorted_pairs.items()))
foreach ($sortedPairs as $key => $value) {
	$escapedPairs[$key] = str_replace(':','\\:', str_replace('\\', '\\\\', $value));
}

The table below reflects the sorting and escaping steps.

Key

Value

currencyCode

EUR

merchantAccount

TestMerchant

merchantReference

paymentTest\:143522\\64\\39255

paymentAmount

1995

sessionValidity

2018-07-25T10\:31\:06Z

shipBeforeDate

2018-07-30

shopperLocale

en_GB

skinCode

X7hsNDWp

3. Concatenate the key names, first, followed by the values. Use a colon (“:”) to delimit the key names and values to obtain the signing string.

String signingString = Stream.concat(escapedPairs.keySet().stream(), escapedPairs.values().stream())
        .collect(Collectors.joining(":"));
signing_string = ":".join(escaped_pairs.keys() + escaped_pairs.values())
$signingString = implode(":", array_merge(array_keys($escapedPairs), array_values($escapedPairs)));

The signing string below shows the concatenated and delimited key-value pairs.

currencyCode:merchantAccount:merchantReference:paymentAmount:sessionValidity:shipBeforeDate:shopperLocale:skinCode:EUR:TestMerchant:paymentTest\:143522\\64\\39255:1995:2018-07-25T10\:31\:06Z:2018-07-30:en_GB:X7hsNDWp

4. Convert the HMAC key to the binary representation. Note that the HMAC key is considered as hexadecimal value.

// import from com.google.common.io.BaseEncoding;
byte[] binaryHmacKey = BaseEncoding.base16().decode(HMAC_KEY);
binary_hmac_key = binascii.a2b_hex(HMAC_KEY)
$binaryHmacKey = pack("H*" , $HMAC_KEY);

5. Calculate the HMAC with the signing string, in binary representation given the UTF-8 charset, using the cryptographic hash function SHA-256.

// Create an HMAC SHA-256 key from the raw key bytes
SecretKeySpec signingKey = new SecretKeySpec(binaryHmacKey, "HmacSHA256");

// Get an HMAC SHA-256 Mac instance and initialize with the signing key
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(signingKey);

// calculate the hmac on the binary representation of the signing string
byte[] binaryHmac = mac.doFinal(signingString.getBytes(Charset.forName("UTF8")));
binary_hmac = hmac.new(binary_hmac_key, signing_string, hashlib.sha256)
$binaryHmac = hash_hmac('sha256', $signingString, $binaryHmacKey, true);

6. Encode the result using the Base64 encoding scheme to obtain the signature.

String signature = Base64.getEncoder().encodeToString(binaryHmac);
signature = base64.b64encode(binary_hmac.digest())
$signature = base64_encode($binaryHmac);

The signature calculated, for example, key-value pairs and HMAC key is:

8SFtIc6zQlswxAZqDKXL+BpRmlDvIWyjOwU8wdl0zK4=

Testing a signature

You can check your signature calculation by constructing a request URL from the key-value pairs and a check HMAC endpoint:
https://ca-test.adyen.com/ca/ca/skin/checkhmac.shtml

To make the request, you have to be signed into your merchant account, as the endpoint is a part of the Customer Area. Note that for the test to work you also need to supply your merchant account and a skin code associated with it.

Supply the signature in the merchantSig field, and the parameters have to be URL-encoded.

String signature = calculateSignature(pairs);
pairs.put("merchantSig", signature);

String queryString = pairs.keySet().stream()
        .map(key -> {
            try {
                return key + "=" +  URLEncoder.encode(pairs.get(key), "UTF-8");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            return "Error: could not URL-encode value";
        }).collect(Collectors.joining("&"));

String testUrl = "https://ca-test.adyen.com/ca/ca/skin/checkhmac.shtml" + "?" + queryString;
signature = calculate_signature(pairs)
pairs['merchantSig'] = signature

test_url = "https://ca-test.adyen.com/ca/ca/skin/checkhmac.shtml" + '?' + urlencode(pairs)
$signature = $this->calculateSignature($pairs);
$pairs["merchantSig"] = $signature;
$queryString = http_build_query($pairs);
    	
$testUrl = "https://ca-test.adyen.com/ca/ca/skin/checkhmac.shtml" . "?" . $queryString;

Optionally, to compare your signing string with the string that Adyen calculates from the supplied key-value pairs, you can submit the signingString field value in addition to the standard payment fields.