HPP HMAC calculation

We use HMAC signing for setting up the payment in our systems; you should use it to verify the payment result when the shopper returns to your site.

  • The signature is computed using the HMAC algorithm with SHA-256 hashing.
  • The data passed in the form fields is concatenated into a string – the signing string.
  • The merchant signature is then computed using the key specified in the skin settings.
  • The signature is passed along with the form data.
  • When Adyen receives it, we use the key to verify that the data has not been tampered with while in transit.
  • The signing string should be packed into a binary format containing hex characters, and then Base64-encoded for transmission.

Setting in Customer Area 

In the Adyen Customer Area (CA), in the Skins section, you can set an HMAC key for your test and live skins:

  1. Log into the Adyen Customer Area (CA) using your merchant credentials.
  2. On the left navigation sidebar, go to Skins.
  3. In the Skins section, select the New tab.
  4. Select the HMAC SHA-256 option.
  5. On the following screen, in the Test & Live configuration section, click the Generate new HMAC key buttons under Test platform and under Live platform.
    You need to generate both test and live HMAC values; otherwise, it is not possible to correctly save the new skin.
    The new HMAC values are stored with the corresponding skin settings.

    The HMAC key is used to verify the integrity of the payment and must always be stored on the server even when using in-app payments. If not done so, the payment details can be tempered if the app is compromised which will result in consequences.


  6. Save your changes.

Payment setup

 Create the merchant signature (HMAC SHA-256)

This example uses the following sample key/value pairs to generate the signing string: 

Key

Value

shopperLocale

en_GB

merchantReference

PAYMENTTEST:143522\64\39255

merchantAccount

TestMerchant

currencyCode

EUR

paymentAmount

1995

sessionValidity

2015-06-25T10:31:06Z

shipBeforeDate

2015-07-01

skinCode

X7hsNDWp

Sort key/value pairs by key

Sort the signing string key/value components by key. This is the default or natural order the String Java class uses.

Key

Value

currencyCode EUR
merchantAccount TestMerchant
merchantReference PAYMENTTEST:143522\64\39255

paymentAmount

1995

sessionValidity

2015-06-25T10:31:06Z

shipBeforeDate

2015-07-01

shopperLocale en_GB

skinCode

X7hsNDWp

Open invoice parameter

For open invoice, additionalData.acquirerReference is returned in the return URL.

Remove unnecessary keys

Remove the following keys, as they are not used in the signature:

  • sig
  • merchantSig
  • Any keys whose name starts with ignore. (dot included).

Concatenate and delimit key names and key values

  • Concatenate the key names, first; then, the key values.
  • Use a colon (":") to delimit key names and key values.
  • Escape embedded "\" characters as "\\", and embedded ":" as "\:".

Key (sorted + escaped)

Value (escaped)

currencyCode

EUR

merchantAccount

TestMerchant

merchantReference

PAYMENTTEST\:143522\\64\\39255

paymentAmount

1995

sessionValidity

2015-06-25T10\:31\:06Z

shipBeforeDate

2015-07-01

shopperLocale

en_GB

skinCode

X7hsNDWp

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

Calculate the HMAC SHA-256

To calculate the HMAC SHA-256 you need:

  • The concatenated key names + key values string.
  • The HMAC key, decoded from the corresponding hex representation.

Encode the HMAC SHA-256

  • After calculating the HMAC, encode it using Base64.

# Returns the binary data represented by the hexadecimal string
hmac_key = binascii.a2b_hex("4468D9782DEF54FCD7....AAA7550C48")

# Calculates HMAC SHA-256
hm = hmac.new(hmac_key, signing_string, hashlib.sha256)

# Encodes HMAC SHA-256 using Base64
merchantSig =  base64.b64encode(hm.digest())

Pass the signature

  • Pass to the HPP all signed key/value pairs.
  • Pass the signature with the merchantSig parameter.

You can input the calculated HMAC also as a hexadecimal-encoded 64-character key (it needs to be as long as the automatically generated 32-byte SHA-256).

  • Apart from the unnecessary keys, all the key/value pairs that are passed to the HPP must be signed.
  • Do not pass any unsigned parameters.
  • Sort the signing string key/value components by key. This is the default or natural order the String Java class uses.
  •  If a key value is null or if it is an empty value, do not submit it (recommended). If you do need or want to submit it, make sure the value is an empty string ("").
  • Whenever possible, it is a good idea to remove embedded newlines, as browsers often attempt to convert them. If you can't remove them, make sure they only consist of CR-LF pairs.
  • Set UTF-8 as the encoding for your code and for the HPP web page.
  • The deliveryAddressType field is necessary only when using the open invoice payment method.

Code examples

The examples below show simple implementations of the HMAC SHA-256 calculation for HPP.

package tests;

import java.nio.charset.Charset;
import java.security.SignatureException;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import com.google.common.io.BaseEncoding;

public class SigningTest {

    private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";
    private static final Charset C_UTF8 = Charset.forName("UTF8");

    public static void main(String[] args) {
        
        byte[] hmacKey =  BaseEncoding.base16().decode("4468D9782DEF54FCD706C9100C71EC43932B1EBC2ACF6BA0560C05AAA7550C48");

        // Sort order is important (using natural ordering)
        SortedMap<String, String> params = new TreeMap<>();
        params.put("merchantAccount", "TestMerchant");
        params.put("currencyCode", "EUR");
        params.put("paymentAmount", "199" );
        params.put("sessionValidity", "2015-06-25T10:31:06Z");
        params.put("shipBeforeDate", "2015-07-01");
        params.put("shopperLocale", "en_GB"); 
        params.put("merchantReference", "SKINTEST-1435226439255");
        params.put("skinCode", "X7hsNDWp");

        // Calculate the data to sign
        String signingData = Stream.concat(params.keySet().stream(), params.values().stream())
                .map(SigningTest::escapeVal)
                .collect(Collectors.joining(":"));

        // Create the signature and add it to the parameter map
        try {
            params.put("merchantSig",calculateHMAC(signingData, hmacKey));
        } catch (SignatureException e) {
            e.printStackTrace();
            return;
        }
        // Expected sig: GJ1asjR5VmkvihDJxCd8yE2DGYOKwWwJCBiV3R51NFg=
        System.out.println(params);
    }

    private static String escapeVal(String val) {
        if(val == null) { return ""; }
        return val.replace("\\", "\\\\").replace(":", "\\:");
    }
    
    private static String calculateHMAC(String data, byte[] key)  throws java.security.SignatureException {
        try {
            // Create an hmac_sha256 key from the raw key bytes
            SecretKeySpec signingKey = new SecretKeySpec(key, HMAC_SHA256_ALGORITHM);

            // Get an hmac_sha256 Mac instance and initialize with the signing key
            Mac mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
            mac.init(signingKey);

            // Compute the hmac on input data bytes
            byte[] rawHmac = mac.doFinal(data.getBytes(C_UTF8));

            // Base64-encode the hmac
            return  BaseEncoding.base64().encode(rawHmac);

        } catch (Exception e) {
            throw new SignatureException("Failed to generate HMAC : " + e.getMessage());
        }
    }
}

<?php
/*
    This PHP code provides a payment form for the Adyen Hosted Payment Pages
*/

/*
account details
    $skinCode:        the skin to be used
    $merchantAccount: the merchant account we want to process this payment with.
    $sharedSecret:    the shared HMAC key.
*/

    $skinCode        = "X7hsNDWp";
    $merchantAccount = "TestMerchant";
    $hmacKey         = "4468D9782DEF54FCD706C9100C71EC43932B1EBC2ACF6BA0560C05AAA7550C48";


/*
    payment-specific details
*/

    $params = array(
      "merchantReference" => "SKINTEST-1435226439255",
      "merchantAccount"   =>  $merchantAccount, 
      "currencyCode"      => "EUR", 
      "paymentAmount"     => "199", 
      "sessionValidity"   => "2015-06-25T10:31:06Z", 
      "shipBeforeDate"    => "2015-07-01", 
      "shopperLocale"     => "en_GB", 
      "skinCode"          => $skinCode );


/*
    process fields
*/
    
    // The character escape function
    $escapeval = function($val) {
     return str_replace(':','\\:',str_replace('\\','\\\\',$val));
    };
    
    // Sort the array by key using SORT_STRING order
    ksort($params, SORT_STRING);

    // Generate the signing data string
    $signData = implode(":",array_map($escapeval,array_merge(array_keys($params), array_values($params))));
    
    // base64-encode the binary result of the HMAC computation
    $merchantSig = base64_encode(hash_hmac('sha256',$signData,pack("H*" , $hmacKey),true));
        $params["merchantSig"] = $merchantSig;
    
?>


<!-- Complete submission form -->

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <title>Adyen Payment</title>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
</head>
<body>
    <form name="adyenForm" action="https://test.adyen.com/hpp/pay.shtml" method="post">
        
<?php
foreach ($params as $key => $value){
    echo '        <input type="hidden" name="' .htmlspecialchars($key,   ENT_COMPAT | ENT_HTML401 ,'UTF-8'). 
                                   '" value="' .htmlspecialchars($value, ENT_COMPAT | ENT_HTML401 ,'UTF-8') . '" />' ."\n" ;
}
?>
    <input type="submit" value="Submit" />
    </form>
</body>
</html>

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import base64
import hmac
import hashlib
import binascii
from collections import OrderedDict

def escapeVal(val):
  return val.replace('\\','\\\\').replace(':','\\:')

def signParams(parms):
  signing_string = ':'.join(map(escapeVal, parms.keys() + parms.values()))
  hm = hmac.new(hmac_key, signing_string, hashlib.sha256)
  parms['merchantSig'] =  base64.b64encode(hm.digest())
  return parms

hmac_key = binascii.a2b_hex("4468D9782DEF54FCD706C9100C71EC43932B1EBC2ACF6BA0560C05AAA7550C48")

rawparams = {
  'merchantAccount': 'TestMerchant', 
  'currencyCode': 'EUR', 
  'paymentAmount': '199', 
  'sessionValidity': '2015-06-25T10:31:06Z', 
  'shipBeforeDate': '2015-07-01', 
  'shopperLocale': 'en_GB', 
  'merchantReference': 'SKINTEST-1435226439255', 
  'skinCode': 'X7hsNDWp' }

params = OrderedDict(sorted(rawparams.items(), key=lambda t: t[0]))

# expected merchantSig = GJ1asjR5VmkvihDJxCd8yE2DGYOKwWwJCBiV3R51NFg=
print signParams(params)

Testing

If you experience any issues with the merchant signature, you can check your HMAC calculation in the Adyen Customer Area (CA). To do so, enter the payment session fields, then check if your merchant signature matches the merchant signature computed by Adyen.

After logging in to the Adyen Customer Area (CA), you can also check your HMAC calculation by submitting a payment request to ca-test.adyen.com/ca/ca/skin/checkhmac.shtml. Besides the standard payment fields, you can also submit the signingString field value.