Skip to main content

Setup HMAC Authentication in Java

This guide walks you through implementing HMAC authentication for the Mekari POS API using Java 8 and Maven.

Prerequisites​

Install Dependencies​

Ensure you have a pom.xml file in the root of your project. Add OkHttp as a dependency for making HTTP requests:

<dependencies>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.1</version>
</dependency>
</dependencies>

Set Up Environment Variables​

Store your Mekari API credentials as environment variables:

MEKARI_API_CLIENT_ID=YOUR_MEKARI_API_CLIENT_ID
MEKARI_API_CLIENT_SECRET=YOUR_MEKARI_CLIENT_SECRET
MEKARI_API_BASE_URL=https://api.mekari.com
MEKARI_POS_USER_ID=<A_USER_ID_FROM_GET_USERS>

Use https://sandbox-api.mekari.com as MEKARI_API_BASE_URL for the sandbox. MEKARI_POS_USER_ID is the staff user your requests act as (an id from GET /users).

warning

Never hardcode credentials in your source code. Use environment variables or a secrets manager.

Generate the HMAC Signature​

The signature is an HMAC-SHA256 hash of the request line and Date header, encoded in Base64. Java includes the javax.crypto.Mac class in the standard library. See HMAC Authentication for the full specification.

Date Formatting (RFC 7231)​

import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

private static String getDateTimeNowUtcString() {
Instant instant = Instant.now();
return DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss O")
.withZone(ZoneOffset.UTC)
.withLocale(Locale.US)
.format(instant);
}

Signature Generation​

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

private static String generateAuthSignature(
String clientId, String clientSecret, String method,
String path, String dateString
) {
String requestLine = method + " " + path + " HTTP/1.1";
String payload = String.join("\n", Arrays.asList(
"date: " + dateString, requestLine
));
String signature = hmacSha256(clientSecret, payload);

return "hmac username=\"" + clientId
+ "\", algorithm=\"hmac-sha256\", headers=\"date request-line\", signature=\""
+ signature + "\"";
}

private static String hmacSha256(String clientSecret, String payload) {
try {
SecretKeySpec signingKey = new SecretKeySpec(
clientSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"
);
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(signingKey);

return Base64.getEncoder()
.encodeToString(mac.doFinal(payload.getBytes(StandardCharsets.UTF_8)));
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException("Failed to generate HMAC signature", e);
}
}

Complete Implementation​

Create HmacGeneratorApplication.java with the full implementation:

import okhttp3.*;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Base64;
import java.util.Locale;

public class HmacGeneratorApplication {

public static void main(String[] args) {

OkHttpClient client = new OkHttpClient();

// Configuration
String base = System.getenv("MEKARI_API_BASE_URL");
String clientId = System.getenv("MEKARI_API_CLIENT_ID");
String clientSecret = System.getenv("MEKARI_API_CLIENT_SECRET");

// Request details
String method = "GET";
String path = "/v2/mekari-pos/v2/customers"; // Request path without the query string
String query = "?limit=50"; // the signature is valid for any query on this path

// Generate auth headers
String dateFormatted = getDateTimeNowUtcString();

Request.Builder builder = new Request.Builder()
.url(base + path + query)
.get()
.addHeader("Date", dateFormatted)
.addHeader("Authorization",
generateAuthSignature(clientId, clientSecret, method,
path, dateFormatted))
.addHeader("Accept", "application/json");

// X-User-ID is needed on write requests; add it only when it is set
String userId = System.getenv("MEKARI_POS_USER_ID");
if (userId != null && !userId.isEmpty()) {
builder.addHeader("X-User-ID", userId);
}

Request request = builder.build();

Call call = client.newCall(request);
try {
Response response = call.execute();
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}

private static String generateAuthSignature(
String clientId, String clientSecret, String method,
String path, String dateString
) {
String requestLine = method + " " + path + " HTTP/1.1";
String payload = String.join("\n", Arrays.asList(
"date: " + dateString, requestLine
));
String signature = hmacSha256(clientSecret, payload);

return "hmac username=\"" + clientId
+ "\", algorithm=\"hmac-sha256\", headers=\"date request-line\", signature=\""
+ signature + "\"";
}

private static String hmacSha256(String clientSecret, String payload) {
try {
SecretKeySpec signingKey = new SecretKeySpec(
clientSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"
);
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(signingKey);

return Base64.getEncoder()
.encodeToString(mac.doFinal(
payload.getBytes(StandardCharsets.UTF_8)));
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException("Failed to generate HMAC signature", e);
}
}

private static String getDateTimeNowUtcString() {
Instant instant = Instant.now();
return DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss O")
.withZone(ZoneOffset.UTC)
.withLocale(Locale.US)
.format(instant);
}
}

Build and Run​

mvn compile exec:java -Dexec.mainClass="HmacGeneratorApplication"

If the credentials are valid, the API response is printed to stdout.

tip

Adapt the method, path, and query (and add a request body for write requests) to match the Mekari POS endpoint you are calling. Refer to the API reference for available endpoints. Write requests also need the X-User-ID and Idempotency-Key headers (see the header table).

Reference Repository​

See the complete example code on GitHub: mekari-api-hmac-example-java8