Integrate Telegram WebApp in Vue 3: Theme, ViewportStable, and Secure Data Transfer

## Introduction We will build a Vue 3 single‑file component that loads the Telegram WebApp library, reads the initData passed in the URL, applies the current theme, locks the viewport, and sends a custom payload to the bot using window.Telegram.WebApp.sendData. The bot receives the data via a regular webhook and validates the initData string to ensure it originates from Telegram. We do not cover payment handling, inline keyboards, or long‑polling; the focus is strictly on the Mini App integration flow.

Main path

1. Vue component

<template>
<div id="app">
<h1>Telegram Mini App</h1>
<button @click="sendPayload">Send data</button>
<pre>{{ response }}</pre>
</div>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import axios from 'axios';

const response = ref('');

function initWebApp() {
if (!window.Telegram?.WebApp) {
console.warn('Telegram WebAPI not available');
return;
}
const tg = window.Telegram.WebApp;

// Expand to full height
tg.expand();

// Apply the current theme (optional but recommended)
tg.ready();
tg.themeChanged(); // triggers if theme already set

// Lock viewport to prevent scaling on iOS/Android
if (tg.viewportStable) {
tg.viewportStable();
}

// Expose initData for later use
tg.initDataUnsafe && console.log('initDataUnsafe', tg.initDataUnsafe);
}

function sendPayload() {
if (!window.Telegram?.WebApp) {
alert('WebApp not ready');
return;
}
const payload = { action: 'demo', ts: Date.now() };
window.Telegram.WebApp.sendData(JSON.stringify(payload));
}

// Listen for data sent from the WebApp (when the bot replies via web_app_data)
function listenForBotData() {
if (!window.Telegram?.WebApp) return;
window.Telegram.WebApp.onEvent('web_app_data', (event) => {
response.value = event.data;
});
}

onMounted(() => {
initWebApp();
listenForBotData();
});
</script>

<style scoped>
#app { font-family: sans-serif; margin: 2rem; }
button { padding: 0.5rem 1rem; font-size: 1rem; }
pre { background: #f4f4f4; padding: 1rem; }
</style>

2. HTML entry point

Create public/index.html (or adjust your build) and add the Telegram script before the Vue app mounts:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Telegram Mini App Vue</title>
<!-- Telegram WebApp SDK -->
<script src="https://telegram.org/js/telegram-web-app.js"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>

The script makes window.Telegram.WebApp available as soon as the page loads inside the Telegram client.

3. PHP backend – validate initData and handle web_app_data

When the Mini App calls sendData, the bot receives an update of type web_app_data. The payload is in update.web_app_data.data. Before trusting it, verify the accompanying initData (available as update.web_app_data.init_data or passed separately) using the bot token.

<?php
// validate_initdata.php
$initData = $_POST['initData'] ?? $_GET['initData'] ?? '';
$data = $_POST['data'] ?? '';

if (!$initData || !$data) {
http_response_code(400);
exit('Missing parameters');
}

$botToken = getenv('TELEGRAM_BOT_TOKEN');
if (!$botToken) {
http_response_code(500);
exit('Bot token not configured');
}

/**
* Compute the SHA256 HMAC of the data check string.
* See https://core.telegram.org/bots/webapps#validating-data-received-via-the-mini-app
*/
function checkInitData(string $token, string $initData): bool {
// Parse key=value pairs
parse_str($initData, $params);
$hash = $params['hash'] ?? '';
unset($params['hash']);

// Build data check string sorted alphabetically
ksort($params);
$lines = [];
foreach ($params as $key => $value) {
$lines[] = "$key=$value";
}
$dataCheckString = implode("\n", $lines);

$secretKey = hash('sha256', $token, true);
$calculatedHash = hash_hmac('sha256', $dataCheckString, $secretKey);

return hash_equals($hash, $calculatedHash);
}

if (!checkInitData($botToken, $initData)) {
http_response_code(403);
exit('Invalid initData');
}

// At this point $data is safe to use
error_log("Received valid data: $data");
// Process $data as needed (e.g., store, forward, reply)
echo json_encode(['status' => 'ok']);

4. Wiring the Vue component to the backend

Replace the sendPayload function with an AJAX call that forwards the payload together with the initData:

async function sendPayload() {
if (!window.Telegram?.WebApp) {
alert('WebApp not ready');
return;
}
const payload = { action: 'demo', ts: Date.now() };
try {
const resp = await axios.post('https://your-domain.com/validate_initdata.php', {
initData: window.Telegram.WebApp.initDataUnsafe,
data: JSON.stringify(payload)
});
response.value = JSON.stringify(resp.data);
} catch (e) {
response.value = 'Error: ' + e.message;
}
}

Production notes

- HTTPS: The Mini App must be served over a valid TLS certificate; otherwise Telegram blocks the WebApp SDK. - CSP: If you use a Content‑Security‑Policy, allow script src="https://telegram.org" and connect-src to your backend. - Idempotency: Store a hash of received initData (or the update_id from the webhook) in Redis or a DB to prevent processing the same update twice. - Error handling: Always check the HTTP status of requests to Telegram and log ok:false responses from getMe or sendMessage when you later reply to the user. - Viewport: Calling viewportStable() after expand() prevents layout jumps on devices with unsafe‑area insets. - Theme updates: Re‑call themeChanged() whenever you receive a theme_changed event if you need to adapt colors dynamically.

This completes a minimal but functional Vue 3 Telegram Mini App that safely exchanges data with a bot. For further reading on the Bot API, see https://botservice.biz/telegram-bot-api.

BotCreator — studio that ships Telegram bots / Mini Apps.

New articles on Telegram

We explain what to automate in your business and how it works in practice. No spam.