Data Validation
The initialization data (initData) passed from the Mini App to the server must be validated through signature verification to ensure the data source is trustworthy and has not been tampered with.
HMAC-SHA256 Validation (Server-side)
This is the standard method for Bot servers to validate initData.
Validation Steps
- Extract the
hashfield from theinitDataquery string, sort the remaining fields alphabetically - Join the sorted fields with newline characters
\nto create thedata_check_string - Compute the
secret_keyby running HMAC-SHA256 on the Bot Token using"WebAppData"as the key - Run HMAC-SHA256 on the
data_check_stringusing thesecret_key - Compare the result with the
hashfield
Python Example
python
import hashlib
import hmac
from urllib.parse import parse_qs
def validate_init_data(init_data: str, bot_token: str) -> bool:
"""Validate Mini App initialization data"""
parsed = parse_qs(init_data)
# Extract hash
received_hash = parsed.pop('hash', [None])[0]
if not received_hash:
return False
# Sort alphabetically and build data_check_string
data_check_pairs = []
for key in sorted(parsed.keys()):
data_check_pairs.append(f"{key}={parsed[key][0]}")
data_check_string = "\n".join(data_check_pairs)
# Compute secret_key
secret_key = hmac.new(
b"WebAppData",
bot_token.encode(),
hashlib.sha256
).digest()
# Compute and compare hash
computed_hash = hmac.new(
secret_key,
data_check_string.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(computed_hash, received_hash)Node.js Example
javascript
const crypto = require('crypto');
function validateInitData(initData, botToken) {
const params = new URLSearchParams(initData);
const hash = params.get('hash');
params.delete('hash');
// Sort alphabetically
const dataCheckString = Array.from(params.entries())
.sort(([a], [b]) => a.localeCompare(b))
.map(([key, value]) => `${key}=${value}`)
.join('\n');
// Compute secret_key
const secretKey = crypto
.createHmac('sha256', 'WebAppData')
.update(botToken)
.digest();
// Compute and compare hash
const computedHash = crypto
.createHmac('sha256', secretKey)
.update(dataCheckString)
.digest('hex');
return computedHash === hash;
}Go Example
go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"net/url"
"sort"
"strings"
)
func ValidateInitData(initData, botToken string) bool {
values, err := url.ParseQuery(initData)
if err != nil {
return false
}
receivedHash := values.Get("hash")
values.Del("hash")
// Sort alphabetically
keys := make([]string, 0, len(values))
for k := range values {
keys = append(keys, k)
}
sort.Strings(keys)
pairs := make([]string, 0, len(keys))
for _, k := range keys {
pairs = append(pairs, k+"="+values.Get(k))
}
dataCheckString := strings.Join(pairs, "\n")
// Compute secret_key
mac := hmac.New(sha256.New, []byte("WebAppData"))
mac.Write([]byte(botToken))
secretKey := mac.Sum(nil)
// Compute and compare hash
mac2 := hmac.New(sha256.New, secretKey)
mac2.Write([]byte(dataCheckString))
computedHash := hex.EncodeToString(mac2.Sum(nil))
return hmac.Equal([]byte(computedHash), []byte(receivedHash))
}Ed25519 Validation (Third-party)
Bot API 8.0+ introduced Ed25519 signature validation, suitable for third-party services to verify data from Mini Apps.
Platform Public Key
The entire platform uses a single Ed25519 key pair, shared by all Bots. Third-party services can use this public key directly to verify signatures.
Public Key (Hex):
你的公钥hex值Validation Steps
- Extract the
signaturefield (Base64-encoded) from theinitDataquery string, sort remaining fields alphabetically - Build
data_check_stringin the format:WebAppData\n{bot_id}\n{sorted_params} - Verify the signature using the platform Ed25519 public key
Important: The initData itself does not include bot_id (only used in signature calculation). Third-party services need to obtain bot_id from other sources (such as JWT, URL parameters, database, etc.).
Python Example
python
import base64
from nacl.signing import VerifyKey
from urllib.parse import parse_qs
# Platform public key (copy from above)
PLATFORM_PUBLIC_KEY_HEX = "..."
def validate_init_data_ed25519(init_data: str, bot_id: int) -> bool:
"""
Validate Mini App initialization data using Ed25519
Args:
init_data: initData string passed from Mini App
bot_id: Bot ID (must be obtained from other sources, e.g., JWT, URL params)
"""
parsed = parse_qs(init_data)
# Extract signature
signature_b64 = parsed.pop('signature', [None])[0]
if not signature_b64:
return False
# Remove hash (not part of signature)
parsed.pop('hash', None)
# Sort alphabetically and build params string
data_check_pairs = []
for key in sorted(parsed.keys()):
data_check_pairs.append(f"{key}={parsed[key][0]}")
params_string = "\n".join(data_check_pairs)
# Telegram format: WebAppData\n{bot_id}\n{params}
data_check_string = f"WebAppData\n{bot_id}\n{params_string}"
try:
verify_key = VerifyKey(bytes.fromhex(PLATFORM_PUBLIC_KEY_HEX))
signature = base64.b64decode(signature_b64)
verify_key.verify(data_check_string.encode(), signature)
return True
except Exception:
return FalseNode.js Example
javascript
const nacl = require('tweetnacl');
// Platform public key (copy from above)
const PLATFORM_PUBLIC_KEY_HEX = '...';
function validateInitDataEd25519(initData, botId) {
const params = new URLSearchParams(initData);
// Extract signature
const signatureB64 = params.get('signature');
if (!signatureB64) return false;
params.delete('signature');
params.delete('hash');
// Sort alphabetically and build params string
const sortedKeys = Array.from(params.keys()).sort();
const dataCheckPairs = sortedKeys.map(key => `${key}=${params.get(key)}`);
const paramsString = dataCheckPairs.join('\n');
// Telegram format: WebAppData\n{bot_id}\n{params}
const dataCheckString = `WebAppData\n${botId}\n${paramsString}`;
try {
const publicKey = Buffer.from(PLATFORM_PUBLIC_KEY_HEX, 'hex');
const signature = Buffer.from(signatureB64, 'base64');
const message = Buffer.from(dataCheckString, 'utf8');
return nacl.sign.detached.verify(message, signature, publicKey);
} catch {
return false;
}
}Security Recommendations
- Always validate on the server — Never rely solely on client-side
initDataUnsafe - Check auth_date — Reject expired initialization data (recommended validity no more than 1 hour)
- Use constant-time comparison — Use
hmac.compare_digest(Python) orcrypto.timingSafeEqual(Node.js) to prevent timing attacks - Protect Bot Token — Never expose the Bot Token in client-side code
javascript
// Server-side validation example
function isInitDataValid(initData, botToken) {
if (!validateInitData(initData, botToken)) {
return false;
}
const params = new URLSearchParams(initData);
const authDate = parseInt(params.get('auth_date'));
const now = Math.floor(Date.now() / 1000);
// Check if data is within 1 hour
if (now - authDate > 3600) {
return false;
}
return true;
}