Signing MarketPay notifications with HMAC

Add an extra layer of security to your notifications.


By default, Adyen notifications use mutual authentication over SSL in combination with basic authentication. You can optionally enable Hash-based Message Authentication Code (HMAC) signatures, which adds an extra layer of security to your Standard notifications

An HMAC signature is calculated using a request's key-value pairs and a secret key, which is known only to you and the Adyen payments platform. By verifying this signature, you'll confirm that the notification was not modified during transmission.

Enable HMAC signatures

You can use any 32bit hexadecimal HMAC key you like. If you subscribe to payment notifications you can reuse the same key. For more information, see Signing notifications with HMAC.

If you generate a new HMAC key, your previous notifications will still be signed with your previous HMAC key.

Subscribe to notifications

Subscribe to notifications with a HMAC Signature Key to receive HMAC signed notifications to the URL you specify in the /createNotificationConfiguration call. We activate the ACCOUNT_HOLDER_VERIFICATION notification and send it to the endpoint on your server (https://www.merchant-domain.com/notification-handler) using the specified connection credentials (testUserName and testPassword).

Request

{  
   "configurationDetails":{  
      "active":true,
      "apiVersion":5,
      "description":"Test notification439493",
      "eventConfigs":[  
         {  
            "eventType":"ACCOUNT_CREATED",
            "includeMode":"INCLUDE"
         }
      ],
      "hmacSignatureKey":"79A3EAF309C43708726A8C284C0D72618696A12E840DFA1DF3A158AFA3B577DA",
      "notificationId":0,
      "notifyPassword":"password",
      "notifyURL":"https://notificationConsumersUrl",
      "notifyUsername":"user"
   }
}

Response

{
  "pspReference": "8815317329454208",
  "submittedAsync": "false",
  "configurationDetails": {
    "active": "true",
    "apiVersion": 5,
    "description": "Test notification439493",
    "messageFormat": "JSON",
    "notificationId": 14833,
    "notifyURL": "https://notificationConsumersUrl",
    "sendActionHeader": "false",
    "sslProtocol": "SSLInsecureCiphers"
  }
}

Verify HMAC signature

To verify the signature, retrieve the values of the HmacSignature and Protocol parameters from the HTTP header of the incoming notification. HmacSignature contains the signature itself and Protocol is the protocol used to create the signature (only SHA256 is supported).

To compute the HMAC signature, apply the algorithm/protocol to the whole HTTP body using the key. If the computed HMAC signature is equal to the one in the header, the verification is successful.

Perform this check before deserializing the request. If they perform the deserialization before verifying, a valid signature may failed due to a different order of the JSON elements 

HMAC signature examples

 To calculate the HMAC signature for a Marketplace notification:

import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.junit.Assert;
import org.junit.Test;

public class MarketplaceNotificationHmacExampleTest {

    @Test
    public void testMarketplaceNotificationHmac(){

        // Example HEX Key (submitted at the moment of subcription)
        String hmacSignatureKey = "79A3EAF309C43708726A8C284C0D72618696A12E840DFA1DF3A158AFA3B577DA";

        // Signature. retrived from HTTP header under the name HmacSignature
        String hmacSignature = "A2bHr0WPlKg1fJLVEDReVAdUDWt3znmsuYvp2KdihXY=";

        // Protocol. retrived from HTTP header under the name Protocol
        String protocol =  "HmacSHA256";

        // Payload. the payload of the notification consists on the whole body of the notification
        String payload = "{\"eventDate\":\"2018-07-09T12:07:27+02:00\",\"eventType\":\"ACCOUNT_HOLDER_CREATED\",\"executingUserKey\":\"ws\",\"live\":false,\"pspReference\":\"9915311308462016\","
                + "\"content\":{\"invalidFields\":[],\"pspReference\":\"9915311308462016\",\"accountCode\":\"9915311308462024\",\"accountHolderCode\":\"6750d8cf-80ab-4a34-b2c5-f8a1f37a79da\","
                + "\"accountHolderDetails\":{\"bankAccountDetails\":[],\"email\":\"testEmail@gmail.com\",\"individualDetails\":{\"name\":{\"firstName\":\"TestFirstName\",\"gender\":\"MALE\","
                + "\"lastName\":\"TestData\"}},\"merchantCategoryCode\":\"7999\"},\"accountHolderStatus\":{\"status\":\"Active\",\"processingState\":{\"disabled\":false,"
                + "\"processedFrom\":{\"currency\":\"EUR\",\"value\":0},\"processedTo\":{\"currency\":\"EUR\",\"value\":0},\"tierNumber\":0},\"payoutState\":{\"allowPayout\":false,"
                + "\"disabled\":false,\"tierNumber\":0},\"events\":[]},\"legalEntity\":\"Individual\",\"verification\":{}}}";


        Base64 base64 = new Base64();
        try {

            // decode HEX Key into bytes
            byte[] keyBytes = Hex.decodeHex(hmacSignatureKey.toCharArray());

            // get payload in bytes
            byte[] payloadBytes = payload.getBytes("UTF-8");

            // instantiate the MAC object using HMAC / SHA256
            Mac hmacSha256 = Mac.getInstance(protocol);

            // create a key object using the secret key and MAC object
            SecretKey secretKey = new SecretKeySpec(keyBytes, hmacSha256.getAlgorithm());

            // initialise the MAC object
            hmacSha256.init(secretKey);

            // finalise the MAC operation
            byte[] signedPayload = hmacSha256.doFinal(payloadBytes);
            // encode the signed payload in Base64
            byte[] encodedSignedPayload = base64.encode(signedPayload);
            System.out.println("original HMAC signature: " + hmacSignature);
            System.out.println("computed HMAC signature: " + new String(encodedSignedPayload, "ASCII"));

            // assert the calculated Base64 encoded HMAC is equal to the received Base64 encoded HMAC 
            Assert.assertTrue(Arrays.equals(encodedSignedPayload, hmacSignature.getBytes("UTF-8")));

        } catch (NoSuchAlgorithmException e) {
            // HmacSHA256 should be supported
        } catch (UnsupportedEncodingException e) {
            // UTF-8 should be supported
        } catch (DecoderException e) {
            // Check key for odd number or characters outside of HEX (base16)
        } catch (InvalidKeyException e) {
            // The key is invalid
        }
    }
}

If the calculated HmacSignature matches the value you received in the notification, the notification wasn't modified during transmission.

The following is an example of a header containing an HMAC signature:

[Content-Type: application/json; charset=utf-8, Authorization: Basic dGVzdFVzZXJOYW1lOnRlc3RQYXNzd29yZA==, HmacSignature: awA4ZTCAYLp/ctyt9yFPlidqZcLjWs5EZikhIQCz98k=, Protocol: HmacSHA256, Content-Length: 819, Host: localhost:57851, Connection: Keep-Alive, User-Agent: Adyen HttpClient 1.0, Accept-Encoding: gzip,deflate]