# Проверка initData в Telegram Mini App на Python: код и ошибки

> initData из Mini App нужно проверять на сервере: секрет вычисляется как HMAC-SHA256 токена бота с ключом WebAppData, затем сравнивается подпись строки данных. Обязательны сравнение через compare_digest и проверка auth_date, чтобы отсечь подделку и старые данные.

Дата: 2026-10-30 · Источник: https://oneswitch.ru/blog/proverka-initdata-telegram-mini-app-python/

Когда Mini App обращается к вашему серверу, вам нужно убедиться, что запрос действительно пришёл из Telegram от конкретного пользователя, а не от злоумышленника, который подставил чужой `user_id`. Для этого Telegram подписывает строку **initData**, а сервер проверяет подпись.

## Как устроена проверка

Mini App получает `Telegram.WebApp.initData` — строку вида `query_id=...&user=...&auth_date=...&hash=...`. Проверка делается так:

1. Разобрать строку на пары ключ-значение (значения декодируются из URL).
2. Убрать поле `hash`, остальные пары отсортировать по ключу и склеить в строку `ключ=значение`, разделяя **переводом строки**.
3. Вычислить **секретный ключ**: HMAC-SHA256, где ключ — строка `WebAppData`, а сообщение — токен бота.
4. Вычислить HMAC-SHA256 строки из шага 2 с этим секретом и сравнить результат (в hex) с полученным `hash`.
5. Проверить, что `auth_date` не старше разумного срока.

## Рабочий код

```python
import hashlib
import hmac
import json
import time
from urllib.parse import parse_qsl


def validate_init_data(init_data: str, bot_token: str, max_age: int = 3600) -> dict:
    """Возвращает данные Mini App, если подпись верна и данные свежие, иначе бросает ValueError."""
    pairs = dict(parse_qsl(init_data, keep_blank_values=True))
    received_hash = pairs.pop("hash", None)
    if not received_hash:
        raise ValueError("нет hash")

    check_string = "\n".join(f"{k}={v}" for k, v in sorted(pairs.items()))
    secret_key = hmac.new(b"WebAppData", bot_token.encode(), hashlib.sha256).digest()
    expected = hmac.new(secret_key, check_string.encode(), hashlib.sha256).hexdigest()

    if not hmac.compare_digest(expected, received_hash):
        raise ValueError("подпись не совпала")
    if time.time() - int(pairs.get("auth_date", 0)) > max_age:
        raise ValueError("данные устарели")

    if "user" in pairs:
        pairs["user"] = json.loads(pairs["user"])
    return pairs
```

Мы прогнали этот код на подписанном образце, на подмене данных, на чужом токене и на устаревшем `auth_date`: корректные данные принимаются, остальные отклоняются.

## Как передать initData на сервер

Фронтенд отправляет строку в заголовке запроса, а сервер проверяет её на каждом обращении:

```javascript
fetch("/api/me", { headers: { Authorization: "tma " + Telegram.WebApp.initData } });
```

Префикс `tma` — распространённая договорённость, а не требование Telegram. Пример проверки в aiohttp:

```python
from aiohttp import web


async def me(request: web.Request) -> web.Response:
    auth = request.headers.get("Authorization", "")
    if not auth.startswith("tma "):
        raise web.HTTPUnauthorized()
    try:
        data = validate_init_data(auth[4:], BOT_TOKEN)
    except ValueError:
        raise web.HTTPUnauthorized()
    return web.json_response({"user_id": data["user"]["id"]})
```

В FastAPI та же функция подключается как зависимость (`Depends`), которая читает заголовок и вызывает `validate_init_data`.

## Типичные ошибки

- **Проверка на клиенте.** Подпись должна проверяться только на сервере: токен бота нельзя отдавать во фронтенд.
- **Сравнение через `==`.** Используйте `hmac.compare_digest`, чтобы не открыть атаку по времени ответа.
- **Забытый `auth_date`.** Без проверки срока старые перехваченные данные работают вечно.
- **Неправильный порядок ключей** или лишние поля в строке проверки. Поле `hash` в строку не входит, остальные идут отсортированными по алфавиту.
- **Не тот токен.** Подпись привязана к токену конкретного бота. Если у вас несколько ботов, храните токен вместе с приложением.
- **Доверие к `initDataUnsafe`.** Это те же данные без проверки. Использовать их для авторизации нельзя.

## Где это пригодится

Проверка `initData` нужна любому Mini App, который хранит данные пользователя: избранное, подписки на уведомления, сохранённые поиски. Так устроены Mini App в наших проектах: [Da Nang House](/raboty/danang-house/) и [HotTrip](/raboty/hottrip/). Если вы строите бота или Mini App, посмотрите услугу [«Telegram-боты и Mini App»](/uslugi/telegram-boty-i-mini-app/).

## Частые вопросы

### Можно ли доверять user_id из initDataUnsafe?

Нет. `initDataUnsafe` — это распарсенные данные без проверки подписи. Для авторизации используйте только данные, которые прошли `validate_init_data`.

### Как долго initData действителен?

Telegram передаёт время создания в `auth_date`, а срок годности определяете вы. Для обычных запросов хватает часа, для чувствительных операций берут меньше.

### Что делать при ошибке «подпись не совпала»?

Проверьте, что токен принадлежит тому боту, из которого открыт Mini App, что `hash` исключён из строки проверки, что пары отсортированы по ключу и склеены переводом строки.

### Нужна ли проверка, если Mini App только читает публичные данные?

Если данные публичны и не привязаны к пользователю, проверка не обязательна. Как только появляются личные данные или действия от имени пользователя, она обязательна.
