Classic-platform icon

Signing notifications with HMAC

Add an extra layer of security to the notifications sent by Adyen for Platforms.

This feature is supported from Notifications v5 and later.

For information about platform integrations built after August 1, 2022, refer to our new integration guide instead.

By default, Adyen notifications use 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).

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 you perform deserialization before verifying, a valid signature may fail due to a different order of the JSON elements 

HMAC signature examples

To calculate the HMAC signature:

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

public class MarketplaceNotificationHmacExampleTest {

    @Test
    public void testMarketplaceNotificationHmac() {

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

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

        // Protocol. Retrieved 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\":{}}}";

        try {

            // decode HEX Key into bytes
            byte[] keyBytes = new BigInteger(hmacSignatureKey, 16).toByteArray();

            // get payload in bytes
            byte[] payloadBytes = payload.getBytes(StandardCharsets.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.getEncoder().encode(signedPayload);
            System.out.println("original HMAC signature: " + hmacSignature);
            System.out.println("computed HMAC signature: " + new String(encodedSignedPayload, StandardCharsets.US_ASCII));

            // assert the calculated Base64 encoded HMAC is equal to the received Base64 encoded HMAC
            Assertions.assertArrayEquals(encodedSignedPayload, hmacSignature.getBytes(StandardCharsets.UTF_8));

        } catch (NoSuchAlgorithmException e) {
            // HmacSHA256 should be supported
        } 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]

See also