Белый экран в Telegram Mini App: почему приложение не открывается на iPhone и ПК и как это починить
Запустили Telegram Mini App, а у пользователей на iPhone белый экран, на ПК выбрасывает в браузер или падает ошибка initData? Разбираем 6 фатальных инженерных ошибок TMA: от заголовков CSP и строгой песочницы WebKit до проверки хэша HMAC и багов BotFather.

Почему в браузере всё работает, а в Telegram — белый лист?
Типичная история из практики разработки 2026 года: команда создаёт Telegram Mini App на современном стеке (React, Next.js, Vue или Svelte), запускает локальный сервер и тестирует интерфейс в десктопном Chrome с открытым эмулятором мобильных устройств (Device Toolbar). В браузере всё летает: анимации плавные, компоненты монтируются моментально, API возвращает ответы за 40–50 миллисекунд.
Затем проект выкатывают на тестовый стенд, подключают к боту через BotFather и запускают первый поток пользователей или рекламу. И тут начинается лавина жалоб:
- До 30–40% владельцев iPhone при нажатии на кнопку видят чистый белый экран. Приложение даже не начинает показывать индикатор загрузки (скелетон или спиннер).
- Пользователи десктопного клиента Telegram Desktop или браузерных версий (Web K и Web A) получают пустое серое окно, либо их выбрасывает во внешний браузер, где интерфейс полностью ломается.
- Пользователи, свернувшие мессенджер на полчаса и вернувшиеся обратно, натыкаются на бесконечный прелоадер или ошибки 401 Unauthorized.
Причина кроется в фундаментальном заблуждении: Telegram Mini App — это не «обычный адаптивный сайт, открытый внутри мессенджера». Это гибридное веб-приложение, запущенное в изолированном системном контейнере WebView, управляемом мостом Telegram WebApp SDK.
При этом среда исполнения на разных платформах кардинально отличается:
- iOS (iPhone и iPad): приложение рендерится через движок Apple WebKit (
WKWebView) со строгой политикой защиты конфиденциальности (Intelligent Tracking Prevention), специфическим поведением песочницы для cookie и виртуальной экранной клавиатуры. - Android: используется системный компонент Android System WebView на базе Chromium со своими ограничениями кэширования и разрешений.
- Telegram Desktop и Telegram Web: приложение загружается внутри изолированного HTML-элемента
<iframe>, подчиняющегося жестким правилам безопасности междоменного встраивания.
Ниже мы подробно разберём 6 фатальных инженерных ошибок, из-за которых Telegram Mini App падает в белый экран, и покажем проверенные решения для каждой из них.
Сводная матрица: где и почему падает Mini App
В таблице ниже собраны основные симптомы сбоев, уязвимые платформы и быстрые способы диагностики:
| Ошибка | Платформа сбоя | Внешний симптом | Первопричина | Способ устранения |
|---|---|---|---|---|
| Заголовки CSP и X-Frame-Options | Telegram Desktop, Web (K / A) | Белый прямоугольник, ошибки в консоли | X-Frame-Options: SAMEORIGIN или DENY | Директива frame-ancestors https://*.telegram.org |
| Краш localStorage в WebKit | iOS (iPhone, iPad) | Белый экран до отрисовки первого кадра | Исключение SecurityError: The operation is insecure | Safe-storage полифилл с in-memory fallback и CloudStorage |
| Сбои валидации initData | Все платформы | Ошибка 401 Unauthorized, вечный лоадер | Неверный HMAC-SHA256, сбой сортировки, протухание даты | Эталонная валидация подписи на бэкенде с допуском по времени |
| Сдвиг вьюпорта клавиатурой | iOS (iPhone) | Контент улетает вверх, кнопка вне экрана | Специфика ресайза WebKit при скрытии клавиатуры | Вызов expand(), обработка viewportChanged, --tg-viewport-height |
| Неверная ссылка в BotFather | Desktop, Mobile | Выброс во внешний браузер вместо WebApp | Обычный URL вместо объекта WebAppInfo | Настройка Web App кнопки в BotFather и inline-клавиатуре |
| Неполная цепочка SSL (CA) | iOS (Safari WebKit) | Белый экран без единого сетевого запроса | Отсутствие промежуточного сертификата в Nginx | Установка полного бандла сертификатов fullchain.pem |
Ошибка №1. Заголовки CSP и X-Frame-Options: когда браузер молча блокирует iframe
В десктопном приложении (Telegram Desktop для Windows и macOS) и в браузерных клиентах (web.telegram.org) Mini App открывается внутри стандартного HTML-фрейма <iframe>.
Большинство современных веб-серверов (Nginx, Caddy, Apache), облачных прокси (Cloudflare) и фреймворков (Next.js, Remix, Nuxt) по умолчанию настроены на максимальную безопасность и отдают заголовки, предотвращающие атаки кликджекинга (Clickjacking):
X-Frame-Options: DENYилиX-Frame-Options: SAMEORIGINContent-Security-Policy: frame-ancestors 'none'илиframe-ancestors 'self'
Как только браузер видит такой заголовок в ответе страницы, загружаемой внутри <iframe>, он мгновенно и безусловно блокирует рендеринг документа. В консоли десктопного браузера появляется красная ошибка Refused to display in a frame because it set 'X-Frame-Options' to 'sameorigin', а пользователь видит абсолютно чистый белый прямоугольник.
При этом на мобильном телефоне (где используется нативный WebView без родительского фрейма) приложение может открываться без проблем. Разработчик проверяет телефон, видит работающий интерфейс и не понимает, почему клиенты на ноутбуках жалуются на белый экран.
Как починить
Заголовок X-Frame-Options является устаревшим стандартом: он не поддерживает указание нескольких разрешённых доменов. Современный стандарт W3C требует использования директивы frame-ancestors в заголовке Content-Security-Policy.
Для веб-сервера Nginx конфигурация виртуального хоста Mini App должна выглядеть следующим образом:
server {
server_name app.vash-domen.ru;
# Отключаем устаревший заголовок X-Frame-Options, если он выставлен бэкендом
proxy_hide_header X-Frame-Options;
# Разрешаем встраивание во фрейм только нашему сайту и клиентам Telegram
add_header Content-Security-Policy "frame-ancestors 'self' https://web.telegram.org https://*.telegram.org https://telegram.org;" always;
# Базовые заголовки защиты от подмены типов контента
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}Если ваше приложение работает на Next.js без проксирующего веб-сервера, скорректируйте заголовки в файле next.config.js:
// next.config.js
module.exports = {
async headers() {
return [
{
source: '/:path*',
headers: [
{
key: 'Content-Security-Policy',
value: "frame-ancestors 'self' https://web.telegram.org https://*.telegram.org https://telegram.org;",
},
],
},
];
},
};Ошибка №2. Строгая песочница Apple WebKit и внезапный краш localStorage на iPhone
Это самая частая и коварная причина «белого экрана на iPhone», на которую приходится до 70% всех инцидентов в мобильном трафике.
В операционной системе iOS браузерный движок WebKit (WKWebView) работает в режиме повышенной изоляции персональных данных. Если у пользователя в настройках устройства включена опция «Блокировка всех cookie» (Block All Cookies), активирован приватный режим (Private Browsing) или сработал алгоритм защиты от отслеживания Intelligent Tracking Prevention (ITP), WebKit полностью блокирует доступ к синхронному веб-хранилищу:
window.localStoragewindow.sessionStorage
При любой попытке прочитать или записать значение:
const token = localStorage.getItem('auth_token');WebKit не просто возвращает null — он выбрасывает фатальное исключение уровня браузера: DOMException: The operation is insecure. (или SecurityError: The operation is insecure).
Почему приложение падает намертво
В 9 из 10 современных одностраничных приложений (SPA) популярные менеджеры состояния (Zustand с мидлваром persist, Redux-Persist, Pinia Persistedstate) вызывают обращение к localStorage синхронно на этапе импорта и инициализации JavaScript-модулей.
Это происходит до того, как отработает React-компонент <ErrorBoundary> и до того, как фреймворк смонтирует первый DOM-узел в страницу. В результате происходит неперехваченное исключение (Uncaught Exception), выполнение JavaScript-бандла аварийно прекращается, рендерер не стартует, а пользователь остаётся один на один с белым экраном.
Как починить
Шаг 1. Безопасная обёртка SafeStorage. Никогда не обращайтесь к window.localStorage напрямую. Создайте модуль безопасного хранилища, который перехватывает ошибки песочницы iOS и прозрачно переключается на оперативную память (in-memory Map):
// safeStorage.ts — безопасная работа с хранилищем в iOS WebKit
class SafeStorage implements Storage {
private memory = new Map<string, string>();
private isAvailable: boolean;
constructor() {
this.isAvailable = this.checkStorage();
}
private checkStorage(): boolean {
try {
if (typeof window === 'undefined' || !window.localStorage) return false;
const testKey = '__tma_storage_test__';
window.localStorage.setItem(testKey, '1');
window.localStorage.removeItem(testKey);
return true;
} catch {
// WebKit заблокировал доступ к хранилищу из-за настроек приватности
return false;
}
}
get length(): number {
return this.isAvailable ? window.localStorage.length : this.memory.size;
}
getItem(key: string): string | null {
if (this.isAvailable) {
try {
return window.localStorage.getItem(key);
} catch {
return this.memory.get(key) ?? null;
}
}
return this.memory.get(key) ?? null;
}
setItem(key: string, value: string): void {
if (this.isAvailable) {
try {
window.localStorage.setItem(key, value);
return;
} catch {
// Fallback в память, если квота переполнена или права отозваны
}
}
this.memory.set(key, String(value));
}
removeItem(key: string): void {
if (this.isAvailable) {
try {
window.localStorage.removeItem(key);
} catch {}
}
this.memory.delete(key);
}
clear(): void {
if (this.isAvailable) {
try {
window.localStorage.clear();
} catch {}
}
this.memory.clear();
}
key(index: number): string | null {
if (this.isAvailable) {
try {
return window.localStorage.key(index);
} catch {}
}
return Array.from(this.memory.keys())[index] ?? null;
}
}
export const safeStorage = new SafeStorage();Шаг 2. Переход на нативный Telegram CloudStorage. Для долговременного сохранения пользовательских настроек и состояния корзины используйте нативный API Telegram: Telegram.WebApp.CloudStorage. Он хранит данные на серверах Telegram, привязан к идентификатору аккаунта, не зависит от настроек кук Safari и синхронизируется между смартфоном и десктопом пользователя.
Ошибка №3. Сбои валидации initData: почему бэкенд возвращает 401 и вешает фронтенд
При запуске Mini App клиент Telegram передаёт приложению строку инициализации: window.Telegram.WebApp.initData.
Она содержит профиль пользователя (id, имя, username, язык интерфейса), время генерации строки auth_date и криптографическую подпись hash. Бэкенд обязан проверить подлинность этих данных, чтобы злоумышленник не мог подделать чужой user_id.
Если валидация завершается неудачей, бэкенд возвращает статус 401 Unauthorized. Если на фронтенде не предусмотрен явный экран ошибки авторизации, пользователь видит вечный скелетон или пустой экран.
4 подводных камня валидации HMAC-SHA256
- Неверный секретный ключ. Распространённая ошибка новичков — вычисление HMAC от токена бота напрямую. По спецификации Telegram секретный ключ вычисляется как HMAC-SHA256 от токена бота, где ключом хеширования выступает фиксированная константная строка
"WebAppData":secret_key = HMAC_SHA256(key="WebAppData", msg=BOT_TOKEN). - Алфавитная сортировка параметров. Перед склеиванием параметров в строку
data_check_stringчерез символ переноса строки\n, все парыkey=valueобязаны быть отсортированы строго лексикографически по имени ключа. Параметрhashпри этом обязательно исключается. - Протухание сессии по
auth_date. Telegram генерируетinitDataодин раз при первом открытии Mini App. Если пользователь оставил приложение свернутым на 2 часа и вернулся к оформлению заказа, таймштампauth_dateустаревает. Если на бэкенде установлен слишком жесткий таймаут (например, 15 или 30 минут), все последующие API-запросы будут отвергнуты. - Кодирование спецсимволов и кириллицы. Поле
userпередаётся внутриinitDataв виде экранированной JSON-строки. Если ваш HTTP-фреймворк производит автоматическое двойное URL-декодирование пробелов, кавычек или эмодзи, исходная строка проверки изменится, и рассчитанный хэш не сойдётся с оригиналом.
Эталонный алгоритм валидации на Python
Ниже приведена надежная реализация проверки подписи для FastAPI или любого другого Python-бэкенда:
import hmac
import hashlib
import json
import time
from urllib.parse import parse_qsl
def validate_telegram_init_data(init_data: str, bot_token: str, max_age_seconds: int = 86400) -> dict:
"""
Проверяет подлинность initData от Telegram Mini App.
Возвращает словарь с параметрами при успехе.
Выбрасывает ValueError при фальсификации данных или протухании сессии.
"""
if not init_data:
raise ValueError("Пустая строка initData")
# 1. Разбираем параметры строки запроса
parsed_data = dict(parse_qsl(init_data, keep_blank_values=True))
if "hash" not in parsed_data:
raise ValueError("В параметрах initData отсутствует обязательное поле hash")
received_hash = parsed_data.pop("hash")
# 2. Проверяем срок жизни данных
auth_date = int(parsed_data.get("auth_date", 0))
if time.time() - auth_date > max_age_seconds:
raise ValueError("Сессия устарела (auth_date expired). Требуется перезапуск приложения.")
# 3. Собираем data_check_string с алфавитной сортировкой ключей
check_items = [f"{k}={v}" for k, v in sorted(parsed_data.items())]
data_check_string = "\n".join(check_items)
# 4. Вычисляем секретный ключ: HMAC-SHA256("WebAppData", bot_token)
secret_key = hmac.new(b"WebAppData", bot_token.encode("utf-8"), hashlib.sha256).digest()
# 5. Вычисляем контрольный хэш от собранной строки
calculated_hash = hmac.new(secret_key, data_check_string.encode("utf-8"), hashlib.sha256).hexdigest()
# 6. Защищенное от атак по времени сравнение хэшей
if not hmac.compare_digest(calculated_hash, received_hash):
raise ValueError("Фальсификация подписи: контрольный хэш не совпадает")
# 7. Преобразуем JSON-поле user в словарь
if "user" in parsed_data:
parsed_data["user"] = json.loads(parsed_data["user"])
return parsed_dataОшибка №4. Вьюпорт iOS, сдвиг экранной клавиатуры и заблокированный скролл
В веб-разработке традиционно используется правило height: 100vh или 100% для задания высоты экрана. Однако внутри браузерного движка WebKit на iPhone это правило приводит к двум фатальным проблемам интерфейса:
- Сдвиг экрана при открытии клавиатуры. Когда пользователь ставит курсор в поле ввода (
<input>или<textarea>), операционная система iOS поднимает виртуальную клавиатуру и сдвигает контейнер WebView вверх. Когда клавиатура закрывается (по нажатию «Готово» или тапу вне поля), WebKit не возвращает вьюпорт в исходное положение. В результате нижняя навигационная панель и кнопка «Оплатить заказ» физически улетают за пределы экрана. - Паразитный вертикальный скролл (Rubber Banding). Высота
100vhв Safari включает высоту строки состояния и системной панели Home Bar. Приложение не помещается в экран, и при попытке прокрутки интерфейс дёргается и пружинит вверх-вниз.
Как починить
1. Вызов метода expand(). Сразу после вызова Telegram.WebApp.ready() обязательно вызовите метод expand(). Он разворачивает Mini App на всю доступную высоту экрана смартфона:
if (window.Telegram?.WebApp) {
const tg = window.Telegram.WebApp;
tg.ready();
tg.expand();
}2. Использование CSS-переменных Telegram. Telegram WebApp SDK автоматически передаёт в стили документа вычисленные значения высоты: --tg-viewport-height и --tg-viewport-stable-height.
Настройте корневой контейнер вашего приложения в CSS:
/* Контейнер приложения Telegram Mini App */
.tma-wrapper {
min-height: var(--tg-viewport-stable-height, 100vh);
height: var(--tg-viewport-height, 100vh);
width: 100%;
overflow-x: hidden;
overflow-y: auto;
/* Блокируем паразитную оттяжку страницы в WebKit */
overscroll-behavior-y: none;
/* Учитываем системную плашку iPhone Home Bar */
padding-bottom: env(safe-area-inset-bottom, 16px);
}3. Корректный мета-тег Viewport. В файле index.html или в метаданных корневого лейаута задайте жесткий вьюпорт без возможности случайного масштабирования жестами:
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">Ошибка №5. Баги BotFather и почему приложение открывается во внешнем браузере
Распространённая жалоба пользователей: «Я нажимаю кнопку в боте, но вместо аккуратного окна внутри Telegram у меня открывается Safari или Chrome, а там сайт выдаёт ошибку».
Причина банальна: кнопка в Telegram настроена как обычный переход по ссылке (url), а не как вызов Web App.
Когда кнопка передаёт обычный URL:
# ОШИБКА: открывает системный браузер вместо Mini App
InlineKeyboardButton(text="Открыть каталог", url="https://app.site.ru")Telegram открывает внешний браузер операционной системы. В нём полностью отсутствует объект window.Telegram.WebApp (он равен undefined). Если ваш код обращается к Telegram.WebApp.ready() без проверки существования объекта, скрипт падает с ошибкой TypeError: Cannot read properties of undefined (reading 'ready').
Как настроить кнопки правильно
В коде бота (на примере библиотеки aiogram 3.x) кнопка должна быть явно типизирована через объект WebAppInfo:
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton, WebAppInfo
# ПРАВИЛЬНО: открывает Mini App внутри Telegram
keyboard = InlineKeyboardMarkup(inline_keyboard=[
[
InlineKeyboardButton(
text="🛍️ Открыть магазин",
web_app=WebAppInfo(url="https://app.vash-domen.ru")
)
]
])Для настройки постоянной кнопки меню в левом нижнем углу чата откройте @BotFather:
- Отправьте команду
/setmenubutton. - Выберите вашего бота.
- Отправьте прямую HTTPS-ссылку на веб-приложение.
Как подключить отладчик прямо на смартфоне
Если на iPhone приложение всё равно не открывается, а подключать телефон к компьютеру через кабель и настраивать Safari Web Inspector нет времени, используйте мобильную консоль Eruda.
Добавьте в точку входа вашего приложения скрипт условного подключения отладчика:
// Подключение мобильного отладчика по секретному параметру ?debug=1
if (typeof window !== 'undefined' && new URLSearchParams(window.location.search).has('debug')) {
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/eruda';
script.onload = () => {
if (window.eruda) window.eruda.init();
};
document.head.appendChild(script);
}Теперь, если в ссылке на Web App передать параметр https://app.vash-domen.ru/?debug=1, прямо поверх интерфейса на экране смартфона появится плавающая кнопка консоли разработчика, в которой видны все сетевые запросы, ошибки в консоли и дерево DOM.
Ошибка №6. Неполная цепочка SSL (Intermediate CA) и строгий WebKit
Симптомы этой проблемы сбивают с толку даже опытных системных администраторов:
- На ноутбуке в Chrome сайт открывается с зелёным замочком.
- На Android-смартфоне всё работает быстро и без ошибок.
- На iPhone внутри Telegram Mini App — вечный белый экран, а в логах веб-сервера
access.logнет вообще ни одного запроса.
Почему это происходит
Десктопные браузеры умеют самостоятельно восстанавливать неполную цепочку доверия сертификатов с помощью механизма AIA (Authority Information Access): если веб-сервер не отдал промежуточный сертификат (Intermediate CA), браузер докачивает его самостоятельно.
Движок Apple WebKit на iOS не делает фоновую дозагрузку сертификатов ради экономии трафика и безопасности. Если в конфигурации веб-сервера Nginx указан только листовой сертификат домена (cert.pem), а не полная цепочка (fullchain.pem), iOS разрывает TLS-рукопожатие ещё до отправки HTTP-запроса.
Как проверить и исправить
Проверьте ваш домен через терминал с помощью утилиты openssl:
openssl s_client -connect app.vash-domen.ru:443 -servername app.vash-domen.ruЕсли в выводе присутствует строка Verify return code: 21 (unable to verify the first certificate) — цепочка сертификата разорвана.
В конфигурационном файле Nginx обязательно укажите файл с полной цепочкой сертификатов:
# ОШИБКА: листовой сертификат вызовет белый экран на iOS
# ssl_certificate /etc/letsencrypt/live/app.vash-domen.ru/cert.pem;
# ПРАВИЛЬНО: полная цепочка доверия для всех устройств
ssl_certificate /etc/letsencrypt/live/app.vash-domen.ru/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.vash-domen.ru/privkey.pem;Чек-лист проверки Telegram Mini App перед запуском трафика
Используйте этот контрольный чеклист перед релизом или запуском рекламной кампании:
- Тестирование на 4 средах исполнения: проверено открытие в iOS (WebKit), Android (Chromium), Telegram Desktop и Telegram Web.
- Корректные заголовки фрейма: в
Content-Security-Policyпрописана директиваframe-ancestors https://*.telegram.org, заголовокX-Frame-Optionsотключен. - Безопасная работа с хранилищем: вызовы
localStorageобёрнуты в безопасный полифиллtry/catch, долговременные данные переведены наCloudStorage. - Валидация initData на сервере: ключ считается через HMAC-SHA256("WebAppData", bot_token), параметры отсортированы по алфавиту, таймаут сессии адекватен поведению пользователей.
- Адаптация вьюпорта: вызван
Telegram.WebApp.expand(), для корневого контейнера задана высотаvar(--tg-viewport-height, 100vh), сброшен скроллoverscroll-behavior-y: none. - Проверка SSL-цепочки: на веб-сервере установлен бандл
fullchain.pem, тест SSL Labs показывает рейтинг A/A+. - Мобильный отладчик: внедрена возможность запуска консоли Eruda по секретному параметру для оперативной диагностики на реальных устройствах.
Когда разработку и аудит стоит доверить инженерам «2САЙТ»
Telegram Mini App сегодня — это мощнейший инструмент продаж и обслуживания клиентов, позволяющий бизнесу отказаться от дорогой разработки отдельных мобильных приложений под iOS и Android. Однако за кажущейся простотой веб-страницы скрываются жесткие технические ограничения гибридных систем, специфики Apple WebKit и строгой политики безопасности мессенджера.
Если вы планируете запуск или ваш текущий Mini App работает нестабильно, теряет пользователей на этапе входа и сливает рекламный бюджет:
- Команда студии «2САЙТ» выполняет профессиональную разработку Telegram Mini Apps под ключ: от проектирования отказоустойчивой архитектуры до бесшовной интеграции с CRM, платежными системами и 1С.
- Если вашему бизнесу требуются интеллектуальные сценарии общения и автоматические продажи внутри бота, изучите наши решения по внедрению AI-консультантов для бизнеса.
- А если вы столкнулись с проблемами в работе серверной части и логики самого бота, ознакомьтесь с нашим практическим руководством о том, почему чат-бот не отвечает и как провести экспресс-диагностику сбоев.
Для консультации и проведения технического аудита вашего Telegram Mini App свяжитесь с нашими инженерами через форму на сайте или Telegram-бота студии. Мы выявим скрытые уязвимости архитектуры до того, как они повлияют на ваших клиентов.