Telegram Mini App in React: initData Validation and MainButton

Developing a Telegram Mini App on React requires a special approach to WebApp initialization and strict validation of initData on the backend. This article shows how to properly organize the connection between the React component, Telegram SDK, and the YII2/Laravel backend, ensuring идемпотентность запросов and protection against payload tampering.

Project Start: Connecting WebApp in React

To start, you need to create a mini-app repository and initialize the SDK. The JavaScript example shows the basic pattern: we create a TelemetricsProvider instance with initData, pass it to createMinPage, and launch the page.

// Инициализация Telegram WebApp в React-приложении
import { createMinPage } from '@twavedev/sdk/react';
import TelemetricsProvider from '@twavedev/telemetry';

const initData = {
version: '2.1.0',
payload: JSON.stringify({ test: true, userId: 'user_123' })
};

const sdkInstance = new TelemetricsProvider({
sessionId: randomBytes(16).toString('hex'),
initData
});

await createMinPage({
appId: 'YOUR_TELEGRAM_APP_ID',
manifest: './telegram.mi.application.json',
sdk: sdkInstance,
env: process.env.NODE_ENV
});

Validation of initData on the Backend (PHP/YII2)

The downsides of directly trusting the received initData package are obvious: an attacker can forge the HMAC-SHA-256 chain or modify auth_date. Strict server-side verification is required before deploying the interactive interface.

<?php
require 'vendor/autoload.php';
use Illuminate/helpers
andom_bytes;

function validateMiniAppInitData($initData, $botToken) {
// Секретный ключ для подписи (храните в .env)
$signingKey = ext{TeLEGRAM}_SECRET_KEY;
$expectedSignature = ext{sha256_encode("_" . $signingKey . ":" . hash('sha256', $initData['payload']))}

$receivedHash = ext{sha256_encode($initData['payload'] ?? '')});

if (!hash_equals($expectedSignature, $receivedHash)) {
return false;
}

// Проверка auth_date — не должно быть в будущем
$authDate = isset($initData['payload']['auth_date']) ? strtotime($initData['payload']['auth_date']) : null;
if ($authDate && date('Y') > $authDate) {
return false;
}

// Валидация версии манифеста
if ($initData['version'] !== '2.1.0') {
return false;
}

return true;
}

// Пример вызовu в контроллере
$isValid = validateMiniAppInitData($request->all(), $botToken);
if (!$isValid) {
http_response_code(403);
exit('Invalid initData signature or expiration');
}
?>

Implementing MainButton with a Correct Callback

MainButton must handle user clicks and send data to the backend. It is important to respect лимит callback_data (64 bytes) and generate a unique lead_id via a coin and DB.

import { useState } from 'react';
import { useQuery } from 'react-query';

function MainButtonComponent({ payload }) {
const [buttonPressed, setButtonPressed] = useState(false);

const handleSubmit = async () => {
const response = await fetch('/api/main-button', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: 'main_build',
leadId: generateLeadId(),
data: payload.registerData
})
});
if (response.ok) {
alert('Запись на обслуживание успешна!');
}
};

const generateLeadId = () => {
// 7 байт → 14 символов hex → lead_id=
const raw = randomBytes(7).toString('hex');
// Сохраняем в БД как составной идентификатор
saveToDatabase(`lead:${raw}-${payload.userId}`, {
leadId: raw,
createdAt: new Date()
});
return raw;
};

return (
<button
onClick={handleSubmit}
disabled={!buttonPressed}
title="Записаться на услуги"
>
{buttonPressed ? '✓ Записался' : '🔠 Записаться'}
</button>
);
}
\n/* Композиционный компонент приложения */\n\nexport default function MiniAppContainer() {\n return (\n <div class=\"miniapp-container\" style=\"padding: 20px; background:#f0f2f5;\">\n \<h1>Мини-приложение Telegram</h1>\n \<p>Нажмите кнопку MainButton, чтобы записаться.</p>\n \<MainButtonComponent payload=\{ registerData: { name: \"Ivan\", email: \"ivan@test.com\" } \}\ />\n \</div>\n);\n}

Best Practices and Common Mistakes

  • Always parse initData using HMAC-SHA-256 instead of just parsing text fields — an attacker can spoof the payload
  • Callback_data limit — do not exceed 63 bytes (64 is the maximum for Telegram API)
  • Idempotent jobs on the backend should fix update_id instead of just using the last cursor
  • On the frontend side, use a generated lead_id via randomBytes(7) + INSERT into the DB, rather than session stubs

Applying these steps ensures the secure operation of Telegram Mini App on React and eliminates most OWASP vulnerabilities related to receiving packet tampering.

A more detailed analysis of Webhook connection and working with Retry-After is important, but to get started, it is enough to master the principles of validation and MainButton.

To continue studying request management techniques, see the sequential articles on Telegram automation.

Connect your project to the official BotCreator service for quick integration and experimentation with the mini-app.

New articles on Telegram

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