Telegram Mini Apps (TMA), Fullscreen & initData Validation
Telegram Mini Apps (TMA) Architecture
Telegram Mini Apps allow you to embed modern HTML5/JavaScript web applications directly inside Telegram. Mini Apps support full-screen mode, native dark/light theme synchronization, haptic feedback, cloud storage, biometric authentication, and Telegram Stars checkout.
1. Launching Mini Apps from Buttons
Mini Apps can be opened from Inline Keyboards, Reply Keyboards, or the persistent Chat Menu Button:
<?php
// ── Botlify Cloud .env — auto-injected, never hardcode secrets ──
$BOT_TOKEN = getenv('BOT_TOKEN'); // Telegram bot token from @BotFather
$ADMIN_CHAT_ID = getenv('ADMIN_CHAT_ID'); // Your numeric admin Telegram user ID
$BOT_USERNAME = getenv('BOT_USERNAME'); // Bot username without @
declare(strict_types=1);
// 1. Inline Keyboard Mini App Button (with Telegram 8.0 style)
$inlineMarkup = [
'inline_keyboard' => [
[
[
'text' => '🚀 Launch Web3 Game',
'web_app' => ['url' => 'https://tma.botlify.cloud/game'],
'style' => 'primary'
]
]
]
];
// 2. Persistent Menu Button (Bottom-Left Chat Input)
bot('setChatMenuButton', [
'menu_button' => [
'type' => 'web_app',
'text' => 'Open App',
'web_app' => ['url' => 'https://tma.botlify.cloud']
]
]);
2. Cryptographic initData Validation in Pure PHP 8.2
When a user opens your Mini App, Telegram injects a secure string called initData containing the user's ID, name, username, and a cryptographic hash. To prevent attackers from spoofing identities, you MUST validate this hash on your PHP backend using your BOT_TOKEN:
<?php
// ── Botlify Cloud .env — auto-injected, never hardcode secrets ──
$BOT_TOKEN = getenv('BOT_TOKEN'); // Telegram bot token from @BotFather
$ADMIN_CHAT_ID = getenv('ADMIN_CHAT_ID'); // Your numeric admin Telegram user ID
$BOT_USERNAME = getenv('BOT_USERNAME'); // Bot username without @
declare(strict_types=1);
/**
* Validates Telegram Mini App initData signature using pure native PHP
* Returns parsed user array if valid; false if spoofed or expired
*/
function validate_telegram_init_data(string $initData, string $botToken, int $maxAgeSeconds = 86400): array|false {
if (empty($initData) || empty($botToken)) {
return false;
}
// 1. Parse URL query string into key-value pairs
parse_str($initData, $params);
if (!isset($params['hash'])) {
return false;
}
$receivedHash = $params['hash'];
unset($params['hash']);
// 2. Sort parameters alphabetically by key
ksort($params);
// 3. Build data-check-string (key=value\n...)
$dataCheckParts = [];
foreach ($params as $k => $v) {
$dataCheckParts[] = "{$k}={$v}";
}
$dataCheckString = implode("\n", $dataCheckParts);
// 4. Derive Secret Key: HMAC-SHA256(botToken, "WebAppData")
$secretKey = hash_hmac('sha256', $botToken, 'WebAppData', true);
// 5. Compute Calculated Hash: HMAC-SHA256(dataCheckString, secretKey)
$calculatedHash = hash_hmac('sha256', $dataCheckString, $secretKey);
// 6. Timing-Safe Hash Verification
if (!hash_equals($calculatedHash, $receivedHash)) {
return false; // Signature mismatch! Data was tampered with!
}
// 7. Check Timestamp to Prevent Replay Attacks
if (isset($params['auth_date']) && (time() - (int)$params['auth_date']) > $maxAgeSeconds) {
return false; // Authentication data expired
}
// Return parsed user object
if (isset($params['user'])) {
$params['user'] = json_decode($params['user'], true);
}
return $params;
}
// ─────────────────────────────────────────────────────────────────────────────
// Example API Endpoint Usage:
// ─────────────────────────────────────────────────────────────────────────────
$rawInitData = $_SERVER['HTTP_X_TELEGRAM_INIT_DATA'] ?? ($_POST['initData'] ?? '');
$validated = validate_telegram_init_data($rawInitData, BOT_TOKEN);
if ($validated === false) {
http_response_code(403);
echo json_encode(['ok' => false, 'error' => 'Unauthorized Telegram Session']);
exit;
}
$user = $validated['user'];
$telegramUserId = $user['id'];
$telegramName = $user['first_name'];
echo json_encode([
'ok' => true,
'message' => "Welcome, {$telegramName}!",
'user_id' => $telegramUserId
]);
3. Full-Screen Mode & Orientation Locks (API 8.0)
Inside your Mini App's front-end JavaScript, you can request full-screen mode and lock screen orientation:
// Check if Telegram WebApp SDK is available
if (window.Telegram?.WebApp) {
const tg = window.Telegram.WebApp;
tg.ready();
// 1. Expand to maximum height
tg.expand();
// 2. Request True Full-Screen Mode (Telegram 8.0+)
if (tg.requestFullscreen) {
tg.requestFullscreen();
}
// 3. Lock Orientation to Landscape (Great for Web3 games)
if (tg.lockOrientation) {
tg.lockOrientation('landscape');
}
// 4. Disable Vertical Swipes (Prevents accidental sheet closing)
if (tg.disableVerticalSwipes) {
tg.disableVerticalSwipes();
}
}
4. Prepared Inline Messages (savePreparedInlineMessage)
Mini Apps can pre-package rich interactive messages on the server so users can share them into any Telegram chat with one tap:
<?php
// ── Botlify Cloud .env — auto-injected, never hardcode secrets ──
$BOT_TOKEN = getenv('BOT_TOKEN'); // Telegram bot token from @BotFather
$ADMIN_CHAT_ID = getenv('ADMIN_CHAT_ID'); // Your numeric admin Telegram user ID
$BOT_USERNAME = getenv('BOT_USERNAME'); // Bot username without @
declare(strict_types=1);
function prepare_game_share_card(int $userId, int $score): string {
$res = bot('savePreparedInlineMessage', [
'user_id' => $userId,
'result' => [
'type' => 'article',
'id' => 'score_card_' . time(),
'title' => "🏆 I scored {$score} points in Botlify Cloud Runner!",
'description' => 'Can you beat my high score? Tap to play!',
'input_message_content' => [
'message_text' => "🎮 I just scored {$score} points!\n\nThink you can beat me? Play now on Botlify Cloud:",
'parse_mode' => 'HTML'
],
'reply_markup' => [
'inline_keyboard' => [
[
[
'text' => '🕹️ Play Now',
'web_app' => ['url' => 'https://tma.botlify.cloud/game'],
'style' => 'success'
]
]
]
]
]
]);
// Returns prepared_message_id to pass to Mini App frontend
return $res['result']['id'] ?? '';
}
Credentials are auto-injected in
.env — always load them with getenv(): BOT_TOKEN, ADMIN_CHAT_ID, BOT_USERNAME. Main database: database.db (SQLite, WAL mode). Runtime errors: error.log. Quick test: send to $ADMIN_CHAT_ID. Full details: Runtime Reference.