1. Overview
This document provides a detailed explanation of how to generate a Client Access Token for authenticating Annex Cloud public API calls.
The token is a JSON Web Token (JWT) signed using the HMAC-SHA256 algorithm to ensure:
- Data integrity – the request cannot be altered without detection.
- Authentication – confirming the request comes from a trusted client.
This guide covers:
- JWT structure and components
- How to generate the HMAC value for both POST/PATCH and GET requests
- Step-by-step Java implementation example
- Required HTTP headers when calling Annex Cloud APIs
Library used
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20230227</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>2. Understanding JWT
A JWT consists of three parts, each Base64URL-encoded and separated by a period (.):
<base64url-encoded header>.<base64url-encoded payload>.<base64url-encoded signature>
2.1. JWT Components
a. Header
Defines the type of token and the signing algorithm used.
{
"alg": "HS256",
"typ": "JWT"
} - alg: Signing algorithm (HS256 = HMAC with SHA-256)
- typ: Token type (JWT)
b. Payload
Contains claims and authentication-related data.
Mandatory claims:
- sub – Client identifier (shared during integration)
- exp – Expiration time (in Unix timestamp seconds; ensures the token is valid only for a set duration)
- site_id – Your Annex Cloud site ID
-
hmac – Base64-encoded HMAC-SHA256 signature of either:
- The request JSON payload (POST/PATCH requests), or
- The query parameter value (GET requests)
Example Payload:
{
"sub": "socialannextestsite",
"exp": 1568674228,
"site_id": "yoursiteid",
"hmac": "generated_hmac_here"
}
c. Signature
The JWT signature is created as follows:
HMACSHA256( base64url(header) + "." + base64url(payload), shared_secret )
Where:
- base64url(header) - Base64URL-encoded JSON header
- base64url(payload) - Base64URL-encoded JSON payload
- shared_secret - Your Annex Cloud shared secret key
3. HMAC Generation Rules
For POST / PATCH Requests
- Take the exact JSON payload you will send in the API request.
- Base64-encode the payload (standard Base64, not Base64URL).
- Generate the HMAC using SHA-256 with your shared secret key.
- Base64-encode the HMAC output.
- Insert this Base64-encoded HMAC into the hmac field in the JWT payload.
For GET Requests
- Take the query parameter value you will send.
- Convert it to JSON format (enclose it in quotes).
- Base64-encode this JSON string.
- Generate the HMAC using SHA-256 with your shared secret key.
- Base64-encode the HMAC output.
- Insert this Base64-encoded HMAC into the hmac field in the JWT payload.
Important: The payload or parameter used to generate the HMAC must exactly match what is sent in the API request. Even a single space difference will cause authentication failure.
4. Java Implementation Example
package org.annexcloud.utility;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import com.google.gson.Gson;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
public class CreateToken {
private static final String SECRET_KEY = "secretKey";
private static final String SITE_ID = "siteId";
private static final String HMAC_ALGORITHM = "HmacSHA256";
public static String getToken(String payload, HashMap<String, String> envDetails) {
try {
// Generate HMAC
byte[] hmac = generateHMAC(payload, envDetails.get(SECRET_KEY));
// Prepare JWT claims
Map<String, Object> tokenParams = setTokenParams(hmac, envDetails);
String jsonTokenObj = new Gson().toJson(tokenParams, LinkedHashMap.class);
// Sign JWT
String annexKey = Base64.getEncoder().encodeToString(envDetails.get(SECRET_KEY).getBytes());
return "Bearer " + Jwts.builder()
.setPayload(jsonTokenObj)
.signWith(SignatureAlgorithm.HS256, annexKey)
.compact();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private static byte[] generateHMAC(String payload, String secretKey)
throws NoSuchAlgorithmException, InvalidKeyException {
byte[] encodedPayload = Base64.getEncoder().encode(payload.getBytes());
Mac sha256_HMAC = Mac.getInstance(HMAC_ALGORITHM);
SecretKeySpec secret_key = new SecretKeySpec(secretKey.getBytes(), HMAC_ALGORITHM);
sha256_HMAC.init(secret_key);
return Base64.getEncoder().encode(sha256_HMAC.doFinal(encodedPayload));
}
private static Map<String, Object> setTokenParams(byte[] hMacEncodedBytes, HashMap<String, String> envDetails) {
Map<String, Object> tokenParams = new LinkedHashMap<>();
tokenParams.put("sub", "socialannextestsite");
tokenParams.put("exp", String.valueOf(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(3600)));
tokenParams.put("site_id", envDetails.get(SITE_ID));
tokenParams.put("hmac", new String(hMacEncodedBytes));
return tokenParams;
}
} 5. Required HTTP Headers for API Calls
When sending requests to Annex Cloud APIs, you must include the following headers:
Header Name |
Value |
Description |
|---|---|---|
Authorization |
Bearer <JWT_access_token> |
The JWT generated using the above process |
X-AnnexCloud-Site |
<site_id> |
Your Annex Cloud site ID |
Content-Type |
application/json |
Request body format |
POST /api/3.0/points HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
X-AnnexCloud-Site: yoursiteid
Content-Type: application/json