Skip to content

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

  1. Extract the hash field from the initData query string, sort the remaining fields alphabetically
  2. Join the sorted fields with newline characters \n to create the data_check_string
  3. Compute the secret_key by running HMAC-SHA256 on the Bot Token using "WebAppData" as the key
  4. Run HMAC-SHA256 on the data_check_string using the secret_key
  5. Compare the result with the hash field

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

  1. Extract the signature field (Base64-encoded) from the initData query string, sort remaining fields alphabetically
  2. Build data_check_string in the format: WebAppData\n{bot_id}\n{sorted_params}
  3. 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 False

Node.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) or crypto.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;
}