Praxis Wiki logo

Overview How to build a signature


{danger.fa-exclamation} IMPORTANT: Current encryption algorithm applies to API version 1.2 (document version latest). Please pay attention at the request version that you are using.

Signing a Request

Request is signed as follows:

  1. All request parameter keys are taken in alphabetical order (nested objects are sorted internally)
  2. Corresponding request parameter values are concatenated into a string (concatenated strings from nested objects are added as values to their upper-level keys) - data
  3. Merchant secret is used as 0-padded 32 chars key
  4. Timestamp which is added to request (see #1), is taken as a string - 0-padded 16 chars initialization vector, or iv
  5. The data is encrypted with aes-256-cbc encryption using key and 16-chars iv - signature
  6. Signature is added to request parameters as "signature":"..."
  7. Request parameters are transformed into a JSON string

Please check out the online generator and validator to test your code.

Code Example - Signing a Request

The following example illustrates the algorithm of adding the authentication details and signature to your requests. The example code can be used to handle the data formatting, however you need to implement the network communication and data handling aspects based on the specifics of your application.

The implementation in each language below contains a generateRequest() function to receive the input data and build the valid request JSON. Also, you can find the acceptRequestAndGenerateResponse() function to parse the incoming notification requests, validate the authentication details and build the valid JSON response.

INPORTANT: TEST CASES. Both above methods are invoked by default within the _TEST_() method when you execute the code. The methods are invoked with sample data from built-in _GET_SAMPLE_DATA_TO_SEND_REQUEST_() and _GET_SAMPLE_INCOMING_NOTIFICATION_DATA_() mock generators accordingly.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;

namespace HowToBuildSignature
{
    class Program
    {
        // Your Merchant Account ID
        public const string MERCHANT_ID = "Test-Integration-Merchant";
        // Your Merchant Secret
        public const string MERCHANT_SECRET = "MerchantSecretKey";
        // Your Application Key
        public const string APPLICATION_KEY = "Sandbox";
        // Your API Version
        public const string API_VERSION = "1.2";

        private class _GET_SAMPLE_DATA_TO_SEND_REQUEST_
        {
            public string your_variable_key_1 { get; set; }
            public int your_variable_key_2 { get; set; }
            public bool your_variable_key_3 { get; set; }
            public bool your_variable_key_4 { get; set; }

            public _GET_SAMPLE_DATA_TO_SEND_REQUEST_()
            {
                your_variable_key_1 = "some_string_value";
                your_variable_key_2 = 12345;
                // your_variable_key_3 remains undefined
                your_variable_key_4 = true;
            }
        }

        private static string _GET_SAMPLE_INCOMING_NOTIFICATION_DATA_()
        {
            Dictionary<String, Object> request = new Dictionary<string, object>();
            request.Add("your_variable_key_1", "some_string_value");
            request.Add("your_variable_key_2", 12345);
            request.Add("your_variable_key_3", null);
            request.Add("your_variable_key_4", true);
            request.Add("merchant_id", MERCHANT_ID);
            request.Add("version", API_VERSION);
            request.Add("application_key", APPLICATION_KEY);
            request.Add("timestamp", getCurrentTimestamp());
            string concatenated_string = getConcatenatedString(request);
            concatenated_string += MERCHANT_SECRET;
            string signature = generateSignature(concatenated_string);
            request.Add("signature", signature);
            string request_json = exportDictionaryToJSON(request);

            return request_json;
        }

        private static Dictionary<string, object> getObjectAsDictionary(object instance)
        {
            Dictionary<string, object> objectAsDictionary = instance
                .GetType()
                .GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
                .ToDictionary(propertyInfo => propertyInfo.Name, propertyInfo => propertyInfo.GetValue(instance));

            return objectAsDictionary;
        }

        private static string getConcatenatedString(Dictionary<string, object> dictionary)
        {
            string concatenated_string = "";

            foreach (KeyValuePair<string, object> key in dictionary.OrderBy(key => key.Key))
            {
                try
                {
                    if (key.Value.Equals(null) || key.Value.Equals(false))
                    {
                        continue;
                    }
                    else if (key.Value.Equals(true) || key.Value.ToString().Equals("True"))
                    {
                        concatenated_string += "1";
                    }
                    else
                    {
                        concatenated_string += key.Value.ToString();
                    }
                }
                catch (Exception) {
                    continue;
                }
            }

            return concatenated_string;
        }

        private static string generateSignature(string input)
        {
            using (SHA384 sha384Hash = SHA384.Create())
            {
                //From String to byte array
                byte[] sourceBytes = Encoding.UTF8.GetBytes(input);
                byte[] hashBytes = sha384Hash.ComputeHash(sourceBytes);
                string hash = BitConverter.ToString(hashBytes).Replace("-", string.Empty);

                return hash.ToLower();
            }
        }

        private static string exportDictionaryToJSON(Dictionary<string, object> input)
        {
            string json_string = JsonSerializer.Serialize(input);
            return json_string;
        }

        private static Dictionary<string, object> importJSONToDictionary(string input)
        {
            Dictionary<string, object> jsonAsDictionary = JsonSerializer.Deserialize<Dictionary<string, object>>(input);
            return jsonAsDictionary;
        }

        private static long getCurrentTimestamp()
        {
            long timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
            return timestamp;
        }

        public static string generateRequest(object requestInstance)
        {
            Dictionary<string, object> request = getObjectAsDictionary(requestInstance);

            request.Add("timestamp", getCurrentTimestamp());
            request.Add("merchant_id", MERCHANT_ID);
            request.Add("application_key", APPLICATION_KEY);
            request.Add("version", API_VERSION);

            // Sort request array by keys ASC
            string concatenated_string = getConcatenatedString(request);

            // Concatenate Merchant Secret Key with request params
            concatenated_string += MERCHANT_SECRET;

            // Generate HASH of concatenated string
            string signature = generateSignature(concatenated_string);
            request.Add("signature", signature);

            // Get a JSON string
            string request_json_string = JsonSerializer.Serialize(request);

            return request_json_string;
        }

        public static string acceptRequestAndGenerateResponse(string incoming_json_string)
        {
            // Parse incoming JSON into data dict
            Dictionary<string, object> incoming_data = importJSONToDictionary(incoming_json_string);

            // validate signature
            string incoming_signature = incoming_data["signature"].ToString();
            incoming_data.Remove("signature");

            // Concatenate Merchant Secret Key with request params
            string incoming_concatenated_string = getConcatenatedString(incoming_data);

            // Concatenate Merchant Secret Key with incoming request params
            incoming_concatenated_string += MERCHANT_SECRET;

            // Generate HASH of concatenated string using your credentials
            string test_signature = generateSignature(incoming_concatenated_string);

            Dictionary<string, object> response = new Dictionary<string, object>();
            response.Add("version", API_VERSION);

            // Compare the incoming signature to match the expexted one
            if (!incoming_signature.Equals(test_signature))
            {
                response.Add("status", 1);
                response.Add("description", "Invalid signature");
            }
            else if (!incoming_data.ContainsKey("timestamp") || Convert.ToInt64(incoming_data["timestamp"].ToString()) < getCurrentTimestamp())
            {
                response.Add("status", 1);
                response.Add("description", "Invalid timestamp");
            }
            else if (!incoming_data.ContainsKey("merchant_id") || !incoming_data["merchant_id"].ToString().Equals(MERCHANT_ID))
            {
                response.Add("status", 1);
                response.Add("description", "Invalid merchant_id");
            }
            else if (!incoming_data.ContainsKey("application_key") || !incoming_data["application_key"].ToString().Equals(APPLICATION_KEY))
            {
                response.Add("status", 1);
                response.Add("description", "Invalid application_key");
            }
            else if (!incoming_data.ContainsKey("version") || !incoming_data["version"].ToString().Equals(API_VERSION))
            {
                response.Add("status", 1);
                response.Add("description", "Invalid version");
            }
            else
            {
                try
                {
                    // YOUR NOTIFICATION HANDLER GOES HERE
                    // YOUR NOTIFICATION HANDLER GOES HERE
                    // YOUR NOTIFICATION HANDLER GOES HERE

                    response.Add("status", 0);
                    response.Add("description", "Ok");
                }
                catch (Exception e)
                {
                    response.Add("status", -1);
                    response.Add("description", "Exception has occurred: " + e.Message);
                }
            }

            // Concatenate Merchant Secret Key with request params
            string concatenated_string = getConcatenatedString(response);
            concatenated_string += MERCHANT_SECRET;

            // Generate HASH of concatenated string
            String signature = generateSignature(concatenated_string);
            response.Add("signature", signature);

            // Get a JSON string
            String response_json_string = exportDictionaryToJSON(response);
            return response_json_string;
        }

        public static void _TEST_()
        {
            Console.WriteLine("=== TESTING REQUEST TO BE SENT ====");
            Object request_to_send = new _GET_SAMPLE_DATA_TO_SEND_REQUEST_();
            String request_to_send_json_string = generateRequest(request_to_send);
            Console.WriteLine("== data to send:");
            Console.WriteLine(request_to_send_json_string);
            Console.WriteLine("");

            Console.WriteLine("=== TESTING NOTIFICATION TO BE RECEIVED ====");
            String notification_to_receive = _GET_SAMPLE_INCOMING_NOTIFICATION_DATA_();
            String notification_response_to_send_json_string = acceptRequestAndGenerateResponse(notification_to_receive);
            Console.WriteLine("== data to receive:");
            Console.WriteLine(notification_to_receive);
            Console.WriteLine("== data to send in response:");
            Console.WriteLine(notification_response_to_send_json_string);
        }

        static void Main(string[] args)
        {
            _TEST_();
        }
    }
}
<?php

class HowToBuildSignature
{
    // Your Merchant Account ID
    const MERCHANT_ID = 'Test-Integration-Merchant';
    // Your Merchant Secret
    const MERCHANT_SECRET = 'MerchantSecretKey';
    // Your Application Key
    const APPLICATION_KEY = 'Sandbox';
    // Your API Version
    const API_VERSION = '1.3';

    public static function _GET_SAMPLE_DATA_TO_SEND_REQUEST_():array {
        $request = [
            'your_variable_key_1' => 'some_string_value', 
            'your_variable_key_2' => 12345, 
            'your_variable_key_3' => null,
            'your_variable_key_4' => true,
            'your_nested_variable' => [
                'your_nested_variable_key_1' => 'some_nested_string_value',
            ],
        ];

        return $request;
    }

    public static function _GET_SAMPLE_INCOMING_NOTIFICATION_DATA_():string {
        $request = [
            'your_variable_key_1' => 'some_string_value', 
            'your_variable_key_2' => 12345, 
            'your_variable_key_3' => null,
            'your_variable_key_4' => true,
            'your_nested_variable' => [
                'your_nested_variable_key_1' => 'some_nested_string_value',
            ],
            'merchant_id' => HowToBuildSignature::MERCHANT_ID,
            'version' => HowToBuildSignature::API_VERSION,
            'application_key' => HowToBuildSignature::APPLICATION_KEY,
            'timestamp' => self::getCurrentTimestamp()
        ];

        $request_string = HowToBuildSignature::getConcatenatedString($request);
        $request['signature'] = HowToBuildSignature::generateSignature($request_string, HowToBuildSignature::MERCHANT_SECRET, $request['timestamp']);
        $request_json = HowToBuildSignature::exportArrayToJSON($request);

        return $request_json;
    }

    private static function getConcatenatedString(array $data):string {
        foreach ($data as $key => $value) {
            if (is_array($value)) {
                $data[$key] = self::getConcatenatedString($value);
            }
        }
        ksort($data);
        return implode('', array_values($data));
    }

    private static function generateSignature(string $input, $key, $iv):string {
        $cipher = "aes-256-cbc";
        $iv = str_pad($iv, 16, '0');
        $hashtext = base64_encode(openssl_encrypt($input, $cipher, $key, OPENSSL_RAW_DATA, $iv));

        return $hashtext;
    }

    private static function exportArrayToJSON(array $input):string {
        $json_string = json_encode($input);

        return $json_string;
    }

    private static function importJSONToArray(string $input):array {
        $data_array = json_decode($input, true);

        return $data_array;
    }

    private static function getCurrentTimestamp():int {
        return time();
    }

    public static function generateRequest(array $request):string {
        $request['timestamp'] = self::getCurrentTimestamp();
        $request['merchant_id'] = self::MERCHANT_ID;
        $request['application_key'] = self::APPLICATION_KEY;
        $request['version'] = self::API_VERSION;

        // Sort request array by keys ASC
        $concatenated_string = self::getConcatenatedString($request);

        // Generate HASH of concatenated string
        $signature = self::generateSignature($concatenated_string, self::MERCHANT_SECRET, $request['timestamp']);
        $request['signature'] = $signature;

        // Get a JSON string
        $request_json_string = self::exportArrayToJSON($request);
        return $request_json_string;
    }

    public static function acceptRequestAndGenerateResponse(string $incoming_json_string):string {
        // Parse incoming JSON into data dict
        $incoming_data = self::importJSONToArray($incoming_json_string);

        // validate signature
        $incoming_signature = $incoming_data['signature'];
        unset($incoming_data['signature']);

        // Sort incoming data array by keys ASC
        $incoming_concatenated_string = self::getConcatenatedString($incoming_data);

        // Generate HASH of concatenated string using your credentials
        $test_signature = self::generateSignature($incoming_concatenated_string, self::MERCHANT_SECRET, $incoming_data['timestamp']);

        $response = [
            'version' => self::API_VERSION,
            'timestamp' => self::getCurrentTimestamp(),
        ];

        // Compare the incoming signature to match the expexted one
        if ($incoming_signature !== $test_signature) {
            $response['status'] = 1;
            $response['description'] = 'Invalid signature';
        } else if (empty($incoming_data['timestamp']) || $incoming_data['timestamp'] < (time() - 60)) {
            $response['status'] = 1;
            $response['description'] = 'Invalid timestamp';
        } else if (empty($incoming_data['merchant_id']) || $incoming_data['merchant_id'] !== self::MERCHANT_ID) {
            $response['status'] = 1;
            $response['description'] = 'Invalid merchant_id';
        } else if (empty($incoming_data['application_key']) || $incoming_data['application_key'] !== self::APPLICATION_KEY) {
            $response['status'] = 1;
            $response['description'] = 'Invalid application_key';
        } else if (empty($incoming_data['version']) || $incoming_data['version'] !== self::API_VERSION) {
            $response['status'] = 1;
            $response['description'] = 'Invalid version';
        } else {
            try {
                // YOUR NOTIFICATION HANDLER GOES HERE
                // YOUR NOTIFICATION HANDLER GOES HERE
                // YOUR NOTIFICATION HANDLER GOES HERE

                $response['status'] = 0;
                $response['description'] = 'Ok';
            } catch (Exception $e) {
                $response['status'] = -1;
                $response['description'] = 'Exception has occurred';
            }
        }

        // Sort response array by keys ASC
        $concatenated_string = self::getConcatenatedString($response);

        // Generate HASH of concatenated string
        $signature = self::generateSignature($concatenated_string, self::MERCHANT_SECRET, $response['timestamp']);
        $response['signature'] = $signature;

        // Get a JSON string
        $response_json_string = self::exportArrayToJSON($response);
        return $response_json_string;
    }

    public static function _TEST_() {
        echo "=== TESTING REQUEST TO BE SENT ===="; echo "\n";
        $request_to_send = self::_GET_SAMPLE_DATA_TO_SEND_REQUEST_();
        $request_to_send_json_string = self::generateRequest($request_to_send);
        echo "== data to send:"; echo "\n";
        echo $request_to_send_json_string; echo "\n";
        echo "\n";
        echo "=== TESTING NOTIFICATION TO BE RECEIVED ===="; echo "\n";
        $notification_to_receive = self::_GET_SAMPLE_INCOMING_NOTIFICATION_DATA_();
        $notification_response_to_send_json_string = self::acceptRequestAndGenerateResponse($notification_to_receive);
        echo "== data to receive:"; echo "\n";
        echo $notification_to_receive; echo "\n";
        echo "== data to send in response:"; echo "\n";
        echo $notification_response_to_send_json_string; echo "\n";
    }
}

HowToBuildSignature::_TEST_();
import hashlib
import time
import json

class HowToBuildSignature:
    # Your Merchant Account ID
    MERCHANT_ID = "Test-Integration-Merchant"
    # Your Merchant Secret
    MERCHANT_SECRET = "MerchantSecretKey"
    # Your Application Key
    APPLICATION_KEY = "Sandbox"
    # Your API Version
    API_VERSION = "1.2";

    @classmethod
    def _GET_SAMPLE_DATA_TO_SEND_REQUEST_(cls):
        request = {
            'your_variable_key_1':'some_string_value', 
            'your_variable_key_2':12345, 
            'your_variable_key_3':None,
            'your_variable_key_4':True
        }

        return request

    @classmethod
    def _GET_SAMPLE_INCOMING_NOTIFICATION_DATA_(cls):
        request = {
            'your_variable_key_1':'some_string_value', 
            'your_variable_key_2':12345, 
            'your_variable_key_3':None,
            'your_variable_key_4':True,
            'merchant_id': cls.MERCHANT_ID,
            'version': cls.API_VERSION,
            'application_key': cls.APPLICATION_KEY,
            'timestamp': cls.getCurrentTimestamp()
        }
        request_string = HowToBuildSignature.getConcatenatedString(request)
        request_string = request_string + HowToBuildSignature.MERCHANT_SECRET
        request['signature'] = HowToBuildSignature.generateSignature(request_string)
        request_json = HowToBuildSignature.exportDictToJSON(request)

        return request_json

    @classmethod
    def getConcatenatedString(cls, dictionary):
        keys = dictionary.keys()
        keys.sort()

        concatenated_string = ''
        for key in keys:
            if dictionary[key] is True:
                # boolean True should appear as 1
                concatenated_string = concatenated_string + '1'
            elif dictionary[key] is False or dictionary[key] is None:
                # boolean False and None should be skipped
                pass
            else:
                # all other data types appear with __str__() representation
                concatenated_string = concatenated_string + str(dictionary[key])

        return concatenated_string

    @classmethod
    def generateSignature(cls, input):
        hashtext = hashlib.sha384(input).hexdigest()
        return hashtext

    @classmethod
    def exportDictToJSON(cls, input):
        json_string = json.dumps(input)
        return json_string

    @classmethod
    def importJSONToDict(cls, input):
        data_dict = json.loads(input)
        return data_dict

    @classmethod
    def getCurrentTimestamp(cls):
        timestamp = int( time.time() )
        return timestamp

    @classmethod
    def generateRequest(cls, request):
        request['timestamp'] = cls.getCurrentTimestamp()
        request['merchant_id'] = cls.MERCHANT_ID
        request['application_key'] = cls.APPLICATION_KEY
        request['version'] = cls.API_VERSION

        # Sort request array by keys ASC
        concatenated_string = cls.getConcatenatedString(request)

        # Concatenate Merchant Secret Key with request params
        concatenated_string = concatenated_string + cls.MERCHANT_SECRET

        # Generate HASH of concatenated string
        signature = cls.generateSignature(concatenated_string)
        request['signature'] = signature

        # Get a JSON string
        request_json_string = cls.exportDictToJSON(request)
        return request_json_string

    @classmethod
    def acceptRequestAndGenerateResponse(cls, incoming_json_string):
        # Parse incoming JSON into data dict
        incoming_data = cls.importJSONToDict(incoming_json_string)

        # validate signature
        incoming_signature = incoming_data['signature']
        del incoming_data['signature']

        # Sort incoming data array by keys ASC
        incoming_concatenated_string = cls.getConcatenatedString(incoming_data)

        # Concatenate Merchant Secret Key with incoming request params
        incoming_concatenated_string = incoming_concatenated_string + cls.MERCHANT_SECRET

        # Generate HASH of concatenated string using your credentials
        test_signature = cls.generateSignature(incoming_concatenated_string)

        response = {
            'version': cls.API_VERSION
        }

        # Compare the incoming signature to match the expexted one
        if incoming_signature != test_signature:
            response['status'] = 1
            response['description'] = 'Invalid signature'
        elif not incoming_data.has_key('timestamp') or incoming_data['timestamp'] < (cls.getCurrentTimestamp() - 60):
            response['status'] = 1
            response['description'] = 'Invalid timestamp'
        elif not incoming_data.has_key('merchant_id') or incoming_data['merchant_id'] != cls.MERCHANT_ID:
            response['status'] = 1
            response['description'] = 'Invalid merchant_id'
        elif not incoming_data.has_key('application_key') or incoming_data['application_key'] != cls.APPLICATION_KEY:
            response['status'] = 1
            response['description'] = 'Invalid application_key'
        elif not incoming_data.has_key('version') or incoming_data['version'] != cls.API_VERSION:
            response['status'] = 1
            response['description'] = 'Invalid version'
        else:
            try:
                # YOUR NOTIFICATION HANDLER GOES HERE
                # YOUR NOTIFICATION HANDLER GOES HERE
                # YOUR NOTIFICATION HANDLER GOES HERE

                response['status'] = 0
                response['description'] = 'Ok'
            except:
                response['status'] = -1
                response['description'] = 'Exception has occurred'

        # Sort response array by keys ASC
        concatenated_string = cls.getConcatenatedString(response)

        # Concatenate Merchant Secret Key with response params
        concatenated_string = concatenated_string + cls.MERCHANT_SECRET

        # Generate HASH of concatenated string
        signature = cls.generateSignature(concatenated_string)
        response['signature'] = signature

        # Get a JSON string
        response_json_string = cls.exportDictToJSON(response)
        return response_json_string

    @classmethod
    def _TEST_(cls):
        print "=== TESTING REQUEST TO BE SENT ===="
        request_to_send = cls._GET_SAMPLE_DATA_TO_SEND_REQUEST_()
        request_to_send_json_string = cls.generateRequest(request_to_send)
        print "== data to send:"
        print request_to_send_json_string
        print ""
        print "=== TESTING NOTIFICATION TO BE RECEIVED ===="
        notification_to_receive = cls._GET_SAMPLE_INCOMING_NOTIFICATION_DATA_()
        notification_response_to_send_json_string = cls.acceptRequestAndGenerateResponse(notification_to_receive)
        print "== data to receive:"
        print notification_to_receive
        print "== data to send in response:"
        print notification_response_to_send_json_string

if __name__ == '__main__':
    HowToBuildSignature._TEST_()
const crypto = require('crypto');

// Your Merchant Account ID
const MERCHANT_ID = 'Test-Integration-Merchant';
// Your Merchant Secret
const MERCHANT_SECRET = 'MerchantSecretKey';
// Your Application Key
const APPLICATION_KEY = "Sandbox";
// Your API Version
const API_VERSION = "1.2";

function _GET_SAMPLE_DATA_TO_SEND_REQUEST_() {
    let request = {
        'your_variable_key_1': 'some_string_value',
        'your_variable_key_2': 12345,
        'your_variable_key_3': null,
        'your_variable_key_4': true
    };

    return request;
}

function _GET_SAMPLE_INCOMING_NOTIFICATION_DATA_() {
    let request = {
        'your_variable_key_1':'some_string_value', 
        'your_variable_key_2':12345, 
        'your_variable_key_3':null,
        'your_variable_key_4':true,
        'merchant_id': MERCHANT_ID,
        'version': API_VERSION,
        'application_key': APPLICATION_KEY,
        'timestamp': getCurrentTimestamp()
    };

    request_string = getConcatenatedString(request);
    request_string += MERCHANT_SECRET;
    request['signature'] = generateSignature(request_string);
    request_json = exportArrayToJSON(request);

    return request_json;
}

function getConcatenatedString(data) {
    let keys = [],
        concatenated_string = '';

    for (let key in data) {
        keys.push(key);
    }
    keys.sort();

    for (let index=0; index<keys.length; index++) {
        if (data[keys[index]] === null || data[keys[index]] === false) {
            continue;
        } else if (data[keys[index]] === true) {
            concatenated_string = concatenated_string + '1';
        } else {
            concatenated_string = concatenated_string + data[keys[index]].toString();
        }
    }

    return concatenated_string;
}

function generateSignature(input) {
    let signature = crypto.createHash('sha384').update(input, 'utf-8').digest('hex');

    return signature;
}

function exportArrayToJSON(input) {
    let json_string = JSON.stringify(input);

    return json_string;
}

function importJSONToArray(input) {
    let data_array = JSON.parse(input);

    return data_array;
}

function getCurrentTimestamp() {
    let timestamp = Math.round( (new Date()).getTime() / 1000);
    return timestamp;
}

function generateRequest(request) {
    request['timestamp'] = getCurrentTimestamp();
    request['merchant_id'] = MERCHANT_ID;
    request['application_key'] = APPLICATION_KEY;
    request['version'] = API_VERSION;

    // Sort request array by keys ASC
    let concatenated_string = getConcatenatedString(request);

    // Concatenate Merchant Secret Key with request params
    concatenated_string = concatenated_string + MERCHANT_SECRET;

    // Generate HASH of concatenated string
    let signature = generateSignature(concatenated_string);
    request['signature'] = signature;

    // Get a JSON string
    let request_json_string = exportArrayToJSON(request);
    return request_json_string;
}

function acceptRequestAndGenerateResponse(incoming_json_string) {
    var response;
    let incoming_data,
        incoming_signature,
        incoming_concatenated_string,
        test_signature,
        concatenated_string,
        signature,
        response_json_string;

    // Parse incoming JSON into data dict
    incoming_data = importJSONToArray(incoming_json_string);

    // validate signature
    incoming_signature = incoming_data['signature'];
    delete(incoming_data['signature']);

    // Sort incoming data array by keys ASC
    incoming_concatenated_string = getConcatenatedString(incoming_data);

    // Concatenate Merchant Secret Key with incoming request params
    incoming_concatenated_string += MERCHANT_SECRET;

    // Generate HASH of concatenated string using your credentials
    test_signature = generateSignature(incoming_concatenated_string);

    response = {
        'version': API_VERSION
    };

    // Compare the incoming signature to match the expexted one
    if (incoming_signature !== test_signature) {
        response['status'] = 1;
        response['description'] = 'Invalid signature';
    } else if (typeof incoming_data['timestamp'] === 'undefined' || incoming_data['timestamp'] < (getCurrentTimestamp() - 60)) {
        response['status'] = 1;
        response['description'] = 'Invalid timestamp';
    } else if (typeof incoming_data['merchant_id'] === 'undefined' || incoming_data['merchant_id'] !== MERCHANT_ID) {
        response['status'] = 1;
        response['description'] = 'Invalid merchant_id';
    } else if (typeof incoming_data['application_key'] === 'undefined' || incoming_data['application_key'] !== APPLICATION_KEY) {
        response['status'] = 1;
        response['description'] = 'Invalid application_key';
    } else if (typeof incoming_data['version'] === 'undefined' || incoming_data['version'] !== API_VERSION) {
        response['status'] = 1;
        response['description'] = 'Invalid version';
    } else {
        try {
            // YOUR NOTIFICATION HANDLER GOES HERE
            // YOUR NOTIFICATION HANDLER GOES HERE
            // YOUR NOTIFICATION HANDLER GOES HERE

            response['status'] = 0;
            response['description'] = 'Ok';
        } catch (e) {
            response['status'] = -1;
            response['description'] = 'Exception has occurred';
        }
    }

    // Sort response array by keys ASC
    concatenated_string = getConcatenatedString(response);

    // Concatenate Merchant Secret Key with response params
    concatenated_string += MERCHANT_SECRET;

    // Generate HASH of concatenated string
    signature = generateSignature(concatenated_string);
    response['signature'] = signature;

    // Get a JSON string
    response_json_string = exportArrayToJSON(response);
    return response_json_string;
}

function _TEST_() {
    console.log('=== TESTING REQUEST TO BE SENT ====');
    let request_to_send = _GET_SAMPLE_DATA_TO_SEND_REQUEST_();
    let request_to_send_json_string = generateRequest(request_to_send);
    console.log('== data to send:');
    console.log(request_to_send_json_string);
    console.log('');
    console.log('=== TESTING NOTIFICATION TO BE RECEIVED ====');
    let notification_to_receive = _GET_SAMPLE_INCOMING_NOTIFICATION_DATA_();
    let notification_response_to_send_json_string = acceptRequestAndGenerateResponse(notification_to_receive);
    console.log('== data to receive:');
    console.log(notification_to_receive);
    console.log('== data to send in response:');
    console.log(notification_response_to_send_json_string);
}

_TEST_();
package com.praxiscashier;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

public class Main {

    // Your Merchant Account ID
    public static final String MERCHANT_ID = "Test-Integration-Merchant";
    // Your Merchant Secret
    public static final String MERCHANT_SECRET = "MerchantSecretKey";
    // Your Application Key
    public static final String APPLICATION_KEY = "Sandbox";
    // Your API Version
    public static final String API_VERSION = "1.2";

    // Sample request data
    private static class _GET_SAMPLE_DATA_TO_SEND_REQUEST_ {
        public String your_variable_key_1;
        public Integer your_variable_key_2;
        public Boolean your_variable_key_3;
        public Boolean your_variable_key_4;

        public _GET_SAMPLE_DATA_TO_SEND_REQUEST_() {
            your_variable_key_1 = "some_string_value";
            your_variable_key_2 = 12345;
            your_variable_key_4 = true;
        }
    }

    // Sample incoming notification data
    private static String _GET_SAMPLE_INCOMING_NOTIFICATION_DATA_() {
        Map<String, Object> request = new HashMap<String, Object>();
        request.put("your_variable_key_1", "some_string_value");
        request.put("your_variable_key_2", 12345);
        request.put("your_variable_key_3", null);
        request.put("your_variable_key_4", true);
        request.put("merchant_id", MERCHANT_ID);
        request.put("version", API_VERSION);
        request.put("application_key", APPLICATION_KEY);
        request.put("timestamp", getCurrentTimestamp());
        Map<String, Object> request_sorted = getSortedMap(request);
        String request_string = getConcatenatedString(request_sorted);
        request_string += MERCHANT_SECRET;
        String signature = generateSignature(request_string);
        request.put("signature", signature);
        String request_json = exportMapToJSON(request);

        return request_json;
    }

    private static Map<String, Object> getObjectAsMap(Object instance) {
        Class<?> instanceClass = instance.getClass();
        Field[] fields = instanceClass.getDeclaredFields();
        Map<String, Object> fieldsAsMap = new HashMap<String, Object>();

        for (Field field : fields) {
            try {
                fieldsAsMap.put(field.getName().toString(), field.get(instance));
            }
            catch (IllegalArgumentException ignored) { }
            catch (IllegalAccessException e) { }
        }

        return fieldsAsMap;
    }

    private static Map<String, Object> getSortedMap(Map<String, Object> map) {
        Map<String, Object> mapSorted = new TreeMap<String, Object>(map);

        return mapSorted;
    }

    private static String getConcatenatedString(Map<String, Object> map) {
        StringBuilder concatenated_string = new StringBuilder(14);

        for (Map.Entry<String, Object> entry : map.entrySet()) {
            try {
                if (entry.getValue() == null || entry.getValue().equals(false)) {
                    // should not be added to resulting string
                } else if (entry.getValue().equals(true)) {
                    concatenated_string.append("1");
                } else {
                    concatenated_string.append(entry.getValue().toString());
                }
            }
            catch (NullPointerException ignored) { }
        }

        return concatenated_string.toString();
    }

    private static String generateSignature(String input) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-384");
            byte[] messageDigest = md.digest(input.getBytes());
            BigInteger no = new BigInteger(1, messageDigest);
            String hashtext = no.toString(16);

            return hashtext;
        }

        catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }

    private static String exportMapToJSON(Map<String, Object> map) {
        Gson gson = new Gson();
        String mapAsJson = gson.toJson(map);

        return mapAsJson;
    }

    private static Map<String, Object> importJSONToMap(String input) {
        Gson gson = new Gson();
        Type type = new TypeToken<Map<String, Object>>(){}.getType();
        Map<String, Object> jsonAsMap = gson.fromJson(input, type);

        // the API communication does not have floats nor doubles, make sure that all large numeric values are long
        for (Map.Entry<String, Object> entry : jsonAsMap.entrySet()) {
            try {
                if (entry.getValue() instanceof Double) {
                    Double doubleValue = Double.parseDouble(entry.getValue().toString());
                    Long longValue = doubleValue.longValue();
                    jsonAsMap.put(entry.getKey(), longValue);
                }
            }
            catch (NullPointerException ignored) { }
        }

        return jsonAsMap;
    }

    private static long getCurrentTimestamp() {
        long timestamp = System.currentTimeMillis() / 1000L;
        return timestamp;
    }

    public static String generateRequest(Object requestInstance) {
        Map<String, Object> request = getObjectAsMap(requestInstance);

        long timestamp = getCurrentTimestamp();
        request.put("timestamp", timestamp);
        request.put("merchant_id", MERCHANT_ID);
        request.put("application_key", APPLICATION_KEY);
        request.put("version", API_VERSION);

        // Sort request array by keys ASC
        Map<String, Object> requestSorted = getSortedMap(request);

        // Concatenate Merchant Secret Key with request params
        String concatenated_string = getConcatenatedString(requestSorted);
        concatenated_string = concatenated_string + MERCHANT_SECRET;

        // Generate HASH of concatenated string
        String signature = generateSignature(concatenated_string);
        requestSorted.put("signature", signature);

        // Get a JSON string
        String request_json_string = exportMapToJSON(requestSorted);
        return request_json_string;
    }

    public static String acceptRequestAndGenerateResponse(String incoming_json_string) {
        // Parse incoming JSON into data dict
        Map<String, Object> incoming_data = importJSONToMap(incoming_json_string);

        // validate signature
        String incoming_signature = incoming_data.get("signature").toString();
        incoming_data.remove("signature");

        // Sort incoming data array by keys ASC
        Map<String, Object> incoming_data_sorted = getSortedMap(incoming_data);

        // Concatenate Merchant Secret Key with request params
        String incoming_concatenated_string = getConcatenatedString(incoming_data_sorted);
        incoming_concatenated_string += MERCHANT_SECRET;

        // Generate HASH of concatenated string using your credentials
        String test_signature = generateSignature(incoming_concatenated_string);

        Map<String, Object> response = new HashMap<String, Object>();
        response.put("version", API_VERSION);

        // Compare the incoming signature to match the expexted one
        if (!incoming_signature.equals(test_signature)) {
            response.put("status", 1);
            response.put("description", "Invalid signature");
        } else if (Long.parseLong(incoming_data.getOrDefault("timestamp", 0).toString()) < getCurrentTimestamp()) {
            response.put("status", 1);
            response.put("description", "Invalid timestamp");
        } else if (!incoming_data.getOrDefault("merchant_id", "").toString().equals(MERCHANT_ID)) {
            response.put("status", 1);
            response.put("description", "Invalid merchant_id");
        } else if (!incoming_data.getOrDefault("application_key", "").toString().equals(APPLICATION_KEY)) {
            response.put("status", 1);
            response.put("description", "Invalid application_key");
        } else if (!incoming_data.getOrDefault("version", "").toString().equals(API_VERSION)) {
            response.put("status", 1);
            response.put("description", "Invalid version");
        } else {
            try {
                // YOUR NOTIFICATION HANDLER GOES HERE
                // YOUR NOTIFICATION HANDLER GOES HERE
                // YOUR NOTIFICATION HANDLER GOES HERE

                response.put("status", 0);
                response.put("description", "Ok");
            } catch (Exception e) {
                response.put("status", -1);
                response.put("description", "Exception has occurred");
            }
        }

        // Sort response array by keys ASC
        Map<String, Object> response_sorted = getSortedMap(response);

        // Concatenate Merchant Secret Key with request params
        String concatenated_string = getConcatenatedString(response_sorted);
        concatenated_string += MERCHANT_SECRET;

        // Generate HASH of concatenated string
        String signature = generateSignature(concatenated_string);
        response.put("signature", signature);

        // Get a JSON string
        String response_json_string = exportMapToJSON(response);
        return response_json_string;
    }

    public static void _TEST_() {
        System.out.println("=== TESTING REQUEST TO BE SENT ====");
        Object request_to_send = new _GET_SAMPLE_DATA_TO_SEND_REQUEST_();
        String request_to_send_json_string = generateRequest(request_to_send);
        System.out.println("== data to send:");
        System.out.println(request_to_send_json_string);
        System.out.println("");

        System.out.println("=== TESTING NOTIFICATION TO BE RECEIVED ====");
        String notification_to_receive = _GET_SAMPLE_INCOMING_NOTIFICATION_DATA_();
        String notification_response_to_send_json_string = acceptRequestAndGenerateResponse(notification_to_receive);
        System.out.println("== data to receive:");
        System.out.println(notification_to_receive);
        System.out.println("== data to send in response:");
        System.out.println(notification_response_to_send_json_string);
    }

    public static void main(String[] args) {
        _TEST_();
    }
}

Validate Incoming Request

Notification signature (received by your CRM) is verified in a similar way:

  1. Notification body (JSON string) is converted into object or array (list, map, dict, array, etc.)
  2. The signature key is removed from array
  3. All notification parameter keys are taken in alphabetical order
  4. Corresponding notification parameter values are concatenated into a string
  5. Merchant Secret is appended to the end of the string
  6. Resulting string is hashed with sha384 - expected signature
  7. Expected signature is compared to be equal to the signature value from request

Please check out the online generator and validator to test your code.

Signing a Response

Response signature is built as follows:

  1. Status is added to response parameters as "status":... (-1 for error - notification is resent, 0 for success, 1 for request validation error)
  2. Description is added to response parameters as a verbal explanation of status (for ex., "DB error", "success", "customer not found")
  3. API version is added to response parameters as "version":"1.2"
  4. Current timestamp (integer unixtime, seconds) is added to response parameters as "timestamp":...
  5. All response parameter keys are taken in alphabetical order
  6. Corresponding response parameter values are concatenated into a string
  7. Merchant Secret is appended to the end of the string
  8. Resulting string is hashed with sha384 - signature
  9. Signature is added to response parameters as "signature":"..."
  10. Response parameters are transformed into a JSON string

Please check out the online generator and validator to test your code.