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
HEADER | DESCRIPTION |
|---|---|
X-pos-id | The POS ID is a unique identifier for the originating Point of Sale terminal. In multi-terminal environments, each device requires a distinct alphanumeric identifier (e.g., POS1, POS2, CHECKOUT_A). |
X-tu-authorization | The 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}}
|
X-tu-random | A unique 17-character random alphanumeric string. Provides entropy and uniqueness. |
X-tu-serial | The serial number of the target payment terminal for this payment request. |
X-tu-date | ISO 8601 UTC timestamp (e.g. 2025-07-22T16:20:00Z). Prevents replay attacks. |
User-Agent | Identifies 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)private static final SecureRandom SECURE_RANDOM = new SecureRandom();
// ==== Generate 17-digit random numeric string ====
private static String generateRandomValue() {
StringBuilder sb = new StringBuilder(17);
for (int i = 0; i < 17; i++) {
sb.append(SECURE_RANDOM.nextInt(10));
}
return sb.toString();
}
// ==== 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));
}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":"910100000000001","posID":"POS-STORE123-TERM01","serialNumber":"PC05P2CG10036","transactionAmount":1500}'4. Send the API Request
Full Example:
#!/bin/bash
# ==== Configurable Inputs ====
API_URL="https://paymentuat.test.thumbzup.com/posbuddy-cloud/v/1"
ACCESS_KEY="AAABBSS9NLLKKKK1MMMM"
SECRET_KEY="11rAAAceqB1RBBkYDcgjGtZSjgWpLxGlZeYetpET"
SERIAL_NUMBER="PC05P2CG10036"
USER_AGENT="MyPOSApp/1.0"
MERCHANT_ID="910100000000001"
# 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
RESPONSE=$(curl --request POST \
--url "$API_URL/payment/sync" \
--header "X-pos-id: POS-STORE123-TERM01" \
--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\": \"$MERCHANT_ID\",
\"posId\": \"POS-STORE123-TERM01\",
\"serialNumber\": \"$SERIAL_NUMBER\",
\"transactionAmount\": 1500
}" \
)
echo "Response: $RESPONSE"package org.example;
import kong.unirest.core.HttpResponse;
import kong.unirest.core.JsonNode;
import kong.unirest.core.RequestBodyEntity;
import kong.unirest.core.Unirest;
import kong.unirest.core.json.JSONObject;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.time.Instant;
import java.util.HashMap;
import java.util.HexFormat;
import java.util.Map;
import java.util.UUID;
//TIP To <b>Run</b> code, press <shortcut actionId="Run"/> or
// click the <icon src="AllIcons.Actions.Execute"/> icon in the gutter.
public class Main {
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
private static String generateRandomString() {
StringBuilder sb = new StringBuilder(18);
for (int i = 0; i < 17; i++) {
sb.append(SECURE_RANDOM.nextInt(10));
}
return sb.toString();
}
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));
}
public static Map<String, String> generateHeaders(String secretKey, String accessKey, String userAgent, String posId, String serialNumber) throws Exception {
String currentDate = String.valueOf(Instant.now().getEpochSecond());
String randomValue = generateRandomString();
byte[] kSigning = hmacSha256(userAgent, secretKey.getBytes(StandardCharsets.UTF_8));
kSigning = hmacSha256(currentDate, kSigning);
kSigning = hmacSha256(randomValue, kSigning);
String signatureHex = HexFormat.of().formatHex(kSigning);
Map<String, String> headers = new HashMap<>();
headers.put("content-type", "application/json");
headers.put("Accept", "application/json");
headers.put("X-tu-date", currentDate);
headers.put("X-tu-random", randomValue);
headers.put("X-tu-serial", serialNumber);
headers.put("User-Agent", userAgent);
headers.put("X-pos-id", posId);
headers.put("x-tu-authorization", "protocol:TU1,accesskey:" + accessKey + ",signedheaders:User-Agent;X-tu-date;X-tu-random,signature:" + signatureHex);
return headers;
}
public static void main(String[] args) throws Exception {
String apiUrl = "https://paymentuat.test.thumbzup.com/posbuddy-cloud/v/1/payment/sync";
String accessKey = "AAABBSS9NLLKKKK1MMMM"; // Provided by Ecentric
String secretKey = "11rAAAceqB1RBBkYDcgjGtZSjgWpLxGlZeYetpET"; // Provided by Ecentric
String merchantID = "910100000000001";
String serialNumber = "PC05P2CG10036"; // The serial number of the target payment terminal for this payment request
String userAgent = "MyPOSApp/1.0";
String posId = "POS-STORE123-TERM01"; // Unique identifier for the POS sending the request
JSONObject extraParameters = new JSONObject();
extraParameters.put("merchantName", "test merchant");
extraParameters.put("transactionUuid", UUID.randomUUID().toString());
JSONObject requestBody = new JSONObject();
requestBody.put("launchType", "SALE");
requestBody.put("merchantID", merchantID);
requestBody.put("posId", posId);
requestBody.put("serialNumber", serialNumber);
requestBody.put("transactionAmount", 1500);
requestBody.put("extraParameters", extraParameters);
System.out.println("Sending request: " + requestBody + "...\n");
Map<String, String> headers = generateHeaders(secretKey, accessKey, userAgent, serialNumber, posId);
RequestBodyEntity request = Unirest.post(apiUrl)
.headers(headers)
.body(requestBody.toString());
HttpResponse<JsonNode> response = request.asJson();
System.out.println("Received response: " + response.getBody());
}
}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).
Updated 27 days ago
