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 id of the physical terminal where the POS is running on. |
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}} - accessKey: To be provided by Ecentric, and treated as sensitive information. - signature: To be derived from steps below. |
X-tu-random | A unique 17-character random alphanumeric string. Provides entropy and uniqueness. |
X-tu-serial | The serial number of the terminal. |
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)
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).
Updated 13 days ago