Authentication

Secure Authentication with HMAC-SHA256 Signature

To ensure secure communication with the REST API, requests to protected endpoints must be signed using the HMAC-SHA256 algorithm. This involves generating a request signature based on specific headers and including both the signature and public access key in the request headers.

📘

Note:

All requests need to include secure header authentication.


Steps to create a REST call

1. Set Required Headers

Include the following headers in your HTTP request:

  • HTTP Method: POST
  • Request Path
    • Asynchronous: /v/1/payment/initiate/async
    • Synchronous: /v/1/payment/initiate/sync
HEADERDESCRIPTION
X-pos-idThe id of the physical terminal where the POS is running on.
X-tu-authorizationThe HMAC signature header. Contains the access key, signed headers, and signature.

protocol:TU1,accesskey:{{accessKey}},signedheaders:User-Agent;X-tu-date;X-tu-random,signature:{{signature}}

- accessKey: To be provided by Ecentric, and treated as sensitive information.
- signature: To be derived from steps below.
X-tu-randomA unique 17-character random alphanumeric string. Provides entropy and uniqueness.
X-tu-serialThe serial number of the terminal.
X-tu-dateISO 8601 UTC timestamp (e.g. 2025-07-22T16:20:00Z). Prevents replay attacks.
User-AgentIdentifies the client application. Used as the first input in the HMAC chain.

2. Create Signature

The signature is calculated as follows using HMAC-SHA256:

  • First HMAC: HMAC(secretKey, User-Agent)
  • Second HMAC: HMAC(result1, X-tu-date)
  • Third HMAC: HMAC(result2, X-tu-random)

The final result is encoded as a lowercase hex string and placed in the X-tu-authorization header.

Signature Example:

// Simulated environment object to store variables
const environment = {};

// Initialize input values
const secretKey = "your-secret-key";       // Replace with actual value
const userAgent = "your-user-agent";       // Replace with actual value

// Generate timestamp and random value
const tuDate = new Date().toISOString();
const randomVal = (Math.random().toString(36) + '00000000000000000').slice(2, 18);

// Store them in our simulated "environment"
environment["X-tu-date"] = tuDate;
environment["X-tu-random"] = randomVal;

// Generate the signature
let kSigning = secretKey;
kSigning = CryptoJS.HmacSHA256(userAgent, kSigning);
kSigning = CryptoJS.HmacSHA256(tuDate, kSigning);
kSigning = CryptoJS.HmacSHA256(randomVal, kSigning);

// Store the signature in our environment object
environment["signature"] = kSigning.toString(CryptoJS.enc.Hex);
#!/bin/bash

# ==== Configurable Inputs ====
SECRET-KEY="your-secret-key"       # Replace with actual value
USER_AGENT="your-user-agent"       # Replace with actual value

# ==== Generate timestamp and random value ====
TU_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
RANDOM_VAL=$(shuf -i 10000000000000000-99999999999999999 -n 1 | cut -c 1-17)

# ==== HMAC Signature Chaining ====

# Generate signature by hmac of the headers
SIGNATURE="$SECRET_KEY"

# First HMAC: User-Agent
SIGNATURE=$(echo -n "$USER_AGENT" | openssl dgst -sha256 -hmac "$SIGNATURE" -binary | xxd -p -c 256)

# Second HMAC: tudate (convert hex back to binary for next step)
SIGNATURE=$(echo -n "$TU_DATE" | openssl dgst -sha256 -hmac "$(echo "$SIGNATURE" | xxd -r -p)" -binary | xxd -p -c 256)

# Third HMAC: randomVal
SIGNATURE=$(echo -n "$RANDOM_VAL" | openssl dgst -sha256 -hmac "$(echo "$SIGNATURE" | xxd -r -p)" -binary | xxd -p -c 256)
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.time.Instant;

public class HmacSignature {
  
	public static String generateSignature(String secretKey, String userAgent) throws Exception {
        String tuDate = Instant.now().toString();
   			String randomVal = generateRandom17CharString();

   			byte[] key = secretKey.getBytes(StandardCharsets.UTF_8);

        key = hmacSha256(userAgent, key);   // Step 1
        key = hmacSha256(tuDate, key);      // Step 2
        key = hmacSha256(randomVal, key);   // Step 3
    
				return bytesToHex(key).toLowerCase();
    }
  
		private static byte[] hmacSha256(String message, byte[] key) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec keySpec = new SecretKeySpec(key, "HmacSHA256");
        mac.init(keySpec);
        return mac.doFinal(message.getBytes(StandardCharsets.UTF_8));
    }
  
		private static String generateRandom17CharString() {
        String chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        SecureRandom random = new SecureRandom();
        StringBuilder sb = new StringBuilder(17);
        for (int i = 0; i < 17; i++) {
            sb.append(chars.charAt(random.nextInt(chars.length())));
        }
        return sb.toString();
    }
  
		private static String bytesToHex(byte[] bytes) {
        StringBuilder hex = new StringBuilder(2 * bytes.length);
        for (byte b : bytes) {
            hex.append(String.format("%02x", b));
        }
        return hex.toString();
    }
}

3. Create Payload

To initiate a transaction request, the client must construct a payload containing the required transaction fields. This payload should be included in the body of the HTTP POST request.

'{"launchType":"SALE","merchantID":"770000000000123","posID":"POS-STORE123-TERM01","serialNumber":"PF5544544664","transactionAmount":1000}'

4. Send the API Request

Full Example:

#!/bin/bash

# ==== Configurable Inputs ====
API_URL="https://paymentuat.test.thumbzup.com/posbuddy-cloud"
ACCESS_KEY="AAABBSS9NLLKKKK1MMMM"
SECRET_KEY="11rAAAceqB1RBBkYDcgjGtZSjgWpLxGlZeYetpET"
SERIAL_NUMBER="PF6666555544"
USER_AGENT="MyPOSApp/1.0"

# Generate ISO 8601 timestamp
TU_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")

# Generate 17-character random number
RANDOM_VAL=$(shuf -i 10000000000000000-99999999999999999 -n 1 | cut -c 1-17)

# ==== HMAC Chaining Logic ====

# Generate signature by hmac of the headers
SIGNATURE="$SECRET_KEY"

# First HMAC: user agent
SIGNATURE=$(echo -n "$USER_AGENT" | openssl dgst -sha256 -hmac "$SIGNATURE" -binary | xxd -p -c 256)

# Second HMAC: date (convert hex back to binary for next step)
SIGNATURE=$(echo -n "$TU_DATE" | openssl dgst -sha256 -hmac "$(echo "$SIGNATURE" | xxd -r -p)" -binary | xxd -p -c 256)

# Third HMAC: random value
SIGNATURE=$(echo -n "$RANDOM_VAL" | openssl dgst -sha256 -hmac "$(echo "$SIGNATURE" | xxd -r -p)" -binary | xxd -p -c 256)

# Make the request
CURL_CMD=$(cat <<EOF
curl --request POST \
  --url '$API_URL/v/1/payment/initiate/async' \
  --header "X-pos-id: 123456" \
  --header "X-tu-authorization: protocol:TU1,accesskey:$ACCESS_KEY,signedheaders:User-Agent;X-tu-date;X-tu-random,signature:$SIGNATURE" \
  --header "X-tu-random: $RANDOM_VAL" \
  --header "X-tu-serial: $SERIAL_NUMBER" \
  --header "X-tu-date: $TU_DATE" \
  --header "User-Agent: $USER_AGENT" \
  --header "Content-Type: application/json" \
  --data '{
    "launchType": "SALE", 
    "merchantID": "115528000000663",
    "posID": "POS-STORE123-TERM01",
    "serialNumber": "PF6666555544",
    "transactionAmount": 1000
  }'
  EOF
)
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.time.Instant;
import java.util.Base64;
import java.util.HexFormat;
import java.io.OutputStream;

public class PosBuddyRequest {

    public static void main(String[] args) throws Exception {
        // ==== Configurable Inputs ====
        String apiUrl = "https://paymentuat.test.thumbzup.com/posbuddy-cloud/v/1/payment/async";
        String accessKey = "AAABBSS9NLLKKKK1MMMM";
        String secretKey = "11rAAAceqB1RBBkYDcgjGtZSjgWpLxGlZeYetpET";
        String serialNumber = "PF6666555544";
        String userAgent = "MyPOSApp/1.0";

        // ==== Generate headers ====
        String currentDate = Instant.now().toString(); // ISO 8601
        String randomValue = generateRandom17DigitString();

        // ==== Signature generation ====
        byte[] kSigning = hmacSha256(userAgent, secretKey.getBytes(StandardCharsets.UTF_8));
        kSigning = hmacSha256(currentDate, kSigning);
        kSigning = hmacSha256(randomValue, kSigning);
        String signatureHex = HexFormat.of().formatHex(kSigning);

        // ==== Construct the authorization header ====
        String authorizationHeader = String.format(
            "protocol:TU1,accesskey:%s,signedheaders:User-Agent;X-tu-date;X-tu-random,signature:%s",
            accessKey, signatureHex
        );

        // ==== Prepare JSON payload ====
        String jsonPayload = """
            {
              "launchType": "SALE",
              "merchantID": "115528000000663",
              "posID": "POS-STORE123-TERM01",
              "serialNumber": "PF6666555544",
              "transactionAmount": 1000
            }
            """;

        // ==== Send HTTP request ====
        URL url = new URL(apiUrl);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("POST");
        conn.setDoOutput(true);

        conn.setRequestProperty("Content-Type", "application/json");
        conn.setRequestProperty("User-Agent", userAgent);
        conn.setRequestProperty("X-tu-date", currentDate);
        conn.setRequestProperty("X-tu-random", randomValue);
        conn.setRequestProperty("X-tu-serial", serialNumber);
        conn.setRequestProperty("X-pos-id", "123456");
        conn.setRequestProperty("X-tu-authorization", authorizationHeader);

        try (OutputStream os = conn.getOutputStream()) {
            byte[] input = jsonPayload.getBytes(StandardCharsets.UTF_8);
            os.write(input, 0, input.length);
        }

        int responseCode = conn.getResponseCode();
        System.out.println("HTTP response code: " + responseCode);
    }

    // HMAC-SHA256 helper
    private static byte[] hmacSha256(String data, byte[] key) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec keySpec = new SecretKeySpec(key, "HmacSHA256");
        mac.init(keySpec);
        return mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
    }

    // Generate a 17-digit numeric string
    private static String generateRandom17DigitString() {
        SecureRandom random = new SecureRandom();
        long min = 1_000_000_000_000_0000L;
        long max = 9_999_999_999_999_9999L;
        long val = min + ((long)(random.nextDouble() * (max - min)));
        return String.valueOf(val);
    }
}

5. API Verifies the Signature

On the server side, the API:

The API verifies the incoming request's authenticity by reconstructing the signature on the server side using the shared secret key. It applies the same HMAC-SHA256 chaining logic to the request headers User-Agent, X-tu-date, and X-tu-random, in that exact order. The server then compares the computed signature against the signature value provided in the X-tu-authorization header. If the signatures match, the request is trusted and processed; otherwise, it is rejected with an authorization error.

📘

Security Notes:

  • Keep you secret key secure, never send it in requests.
  • Always use a new X-tu-random per request to prevent replay attacks.
  • Ensure your device clocks are synced to UTC (Important for timestamp validation).