I am new to using Telegram and I'm trying to validate the hash provided by Telegram's initData for a webapp mini app (not the login widget) in Java, using the steps from the official Telegram documentation and this guide: Telegram authentication in Spring Boot app
Despite following all guidelines, the calculated hash never matches the hash received from Telegram. According to Telegram’s official documentation, the data-check string for login is constructed by concatenating all received fields (except the hash itself), sorted in alphabetical order, with each key= pair separated by a newline (\n) core.telegram.org, which is what I have done.
What I did:
Using Telegram Mini App (WebApp) via window.Telegram.WebApp.initDataUnsafe
Hash received is something like:
{
"id": 785xxxxxx,
"first_name": "xxxxxxx",
"last_name": "xxxxxx",
"language_code": "en",
"allows_write_to_pm": true,
"photo_url": "https://t.me/i/userpic/320/abc123.svg",
"auth_date": "1750072455",
"hash": "a55cdfdexxxxxx"
}
My Java validation code:
public static boolean isValid(Map<String, String> authData, String botToken) {
String receivedHash = authData.get("hash");
authData.remove("hash");
// Step 1: Build data_check_string
Map<String, String> sorted = new TreeMap<>(authData);
String dataCheckString = sorted.entrySet().stream()
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining("\n"));
try {
// Step 2: Generate secret key (HMAC_SHA256 of bot token using "WebAppData")
Mac mac1 = Mac.getInstance("HmacSHA256");
mac1.init(new SecretKeySpec("WebAppData".getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] secretKey = mac1.doFinal(botToken.getBytes(StandardCharsets.UTF_8));
// Step 3: HMAC_SHA256 of dataCheckString using secretKey
Mac mac2 = Mac.getInstance("HmacSHA256");
mac2.init(new SecretKeySpec(secretKey, "HmacSHA256"));
byte[] hmac = mac2.doFinal(dataCheckString.getBytes(StandardCharsets.UTF_8));
String calculatedHash = bytesToHex(hmac);
System.out.println("DataCheckString: " + dataCheckString);
System.out.println("Expected Hash: " + receivedHash);
System.out.println("Calculated Hash: " + calculatedHash);
return calculatedHash.equalsIgnoreCase(receivedHash);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
What I’ve tried:
Ensured no extra newline or space characters in dataCheckString
Matched PHP logic (which works fine)
Used both SHA256(botToken) and HMAC_SHA256(botToken, "WebAppData") as secret
Verified same ordering of keys and value formatting
Still facing:
Mismatch between calculated hash and Telegram's received hash for every input.
How can I succesfully verify the hash from initDataUnsafe in Java (with Telegram webapp)? Please help me identify why I am not able to produce the same hash value.