StaticGramBot API

Mini Apps

Mini App (Web App) это веб-страница, которая открывается внутри StaticGram и получает данные пользователя, кнопки, хранилище и оплату через JS SDK. Протокол совпадает с Telegram: приложение, написанное для Telegram, работает в StaticGram после замены адреса скрипта.

Что поддерживается

ВозможностьСтатусГде работает
initData с подписью hash (HMAC-SHA256) и signature (Ed25519)дасервер
MainButton, SecondaryButton, BackButton, SettingsButton, HapticFeedback, тема, fullscreen, геолокация, статус-эмодзи, ярлык на рабочий стол, downloadFileдаклиент (Android, Desktop, Web; вибрации в Web нет)
CloudStorageдасервер: до 1024 ключей на пользователя и бота, ключ 1-128 символов A-Z a-z 0-9 _ -, значение до 4096 символов
DeviceStorage, SecureStorageдаклиент, данные хранятся на устройстве
requestContactдаответ подписан так же, как initData
requestWriteAccess, shareMessage (savePreparedInlineMessage), switchInlineQuery, sendDataдасервер + клиент
openInvoice / событие invoiceClosedдасчета только в звёздах (XTR), см. ниже
answerWebAppQuery (ответ из Mini App, открытой кнопкой меню, вложением или inline-кнопкой)дасервер
requestChat (WebApp 9.6)даAndroid и Desktop; в Web вызов ничего не делает

Создание Mini App

Есть три способа открыть веб-приложение, все они совместимы с Telegram:

  • Кнопка меню (главное приложение бота). В @BotFather: /setmenubutton → бот → https://адрес текст кнопки. Из кода: setChatMenuButton с MenuButtonWebApp. Это приложение открывается кнопкой слева от поля ввода, из профиля бота и по ссылке ?startapp.
  • Именованное приложение с прямой ссылкой. /newapp в @BotFather (см. ниже).
  • Кнопки в сообщениях. web_app в InlineKeyboardButton (поддерживает answerWebAppQuery) или в KeyboardButton (поддерживает Telegram.WebApp.sendData, бот получает web_app_data).

@BotFather: /newapp, /myapps, /editapp, /deleteapp

  1. /newapp → выберите бота кнопкой или пришлите его @username.
  2. Название (до 128 символов), затем описание (до 512 символов, /empty чтобы пропустить).
  3. Обложка: фото 640×360, отправленное как фото (не файлом), или /empty.
  4. Адрес приложения: только https://, до 512 символов.
  5. Короткое имя (short_name): 3-30 символов a-z 0-9 _, уникально в пределах бота, main зарезервировано за кнопкой меню.

В ответ BotFather пришлёт прямую ссылку. /myapps (или /editapp) показывает главное приложение и все Mini Apps бота с кнопками «Название», «Описание», «Адрес (URL)», «Фото» и «Удалить». Отдельно удалить можно через /deleteapp. Удалённое имя можно занять снова. Кнопка «Mini Apps» есть и в карточке бота в /mybots. Лимит: 50 приложений на бота.

Прямые ссылки

СсылкаЧто открывает
https://staticgram.top/<bot>/<short_name>Mini App, созданную через /newapp
https://staticgram.top/<bot>/<short_name>?startapp=<параметр>то же, параметр придёт в initDataUnsafe.start_param и в initData как start_param
https://staticgram.top/<bot>?startapp, …?startapp=<параметр>главное приложение бота (кнопка меню)
sg://resolve?domain=<bot>&appname=<short_name>&startapp=<параметр>то же через схему приложения

startapp: до 64 символов A-Z a-z 0-9 _ -. Ссылки вида t.me/<bot>/<app> клиенты StaticGram тоже открывают внутри себя, но делиться лучше ссылками на staticgram.top: в браузере они показывают страницу с кнопкой «Open App».

JS SDK

Подключите скрипт до своего кода:

html
<script src="https://staticgram.top/js/telegram-web-app.js"></script>

Это зеркало официального https://telegram.org/js/telegram-web-app.js с одним изменением: openInvoice, openTelegramLink и обработчик ссылок принимают адреса staticgram.top, а не только t.me. С официальным скриптом приложение тоже работает, но ссылки на счёт ему нужно передавать в виде https://t.me/$<slug> (см. «Оплата»).

Параметры запуска сервер передаёт так же, как Telegram, во фрагменте адреса: #tgWebAppData=…&tgWebAppVersion=…&tgWebAppPlatform=…&tgWebAppThemeParams=…. Версия API: 9.6 для Android и Desktop, 9.5 для Web (tgWebAppPlatform=weba). Если приложение использует hash-роутинг, параметры добавляются после ? или & внутри фрагмента, как это делает официальный клиент.

Проверка initData

Никогда не доверяйте initDataUnsafe на сервере: передайте строку Telegram.WebApp.initData на бэкенд и проверьте подпись. Поля: query_id (если приложение открыто кнопкой меню, вложением или inline-кнопкой), user (JSON с id, first_name, last_name, username, language_code, is_premium, allows_write_to_pm), chat_type, chat_instance, chat (для групп и каналов), start_param, auth_date, signature, hash.

Алгоритм тот же, что в Telegram: secret_key = HMAC_SHA256(key="WebAppData", msg=bot_token), data_check_string это все поля кроме hash, отсортированные по имени и соединённые \n в виде key=value; hash = hex(HMAC_SHA256(key=secret_key, msg=data_check_string)). Также проверяйте auth_date, чтобы не принимать старые данные.

Python
import hashlib, hmac, json, time
from urllib.parse import parse_qsl


def check_init_data(init_data: str, bot_token: str, max_age: int = 86400) -> dict:
    data = dict(parse_qsl(init_data, keep_blank_values=True, strict_parsing=True))
    received = data.pop("hash")
    check = "\n".join(f"{k}={v}" for k, v in sorted(data.items()))
    secret = hmac.new(b"WebAppData", bot_token.encode(), hashlib.sha256).digest()
    expected = hmac.new(secret, check.encode(), hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, received):
        raise ValueError("bad hash")
    if time.time() - int(data["auth_date"]) > max_age:
        raise ValueError("init data is too old")
    data["user"] = json.loads(data["user"])
    return data
Node.js
import crypto from "node:crypto";

export function checkInitData(initData, botToken, maxAge = 86400) {
  const params = new URLSearchParams(initData);
  const received = params.get("hash");
  params.delete("hash");
  const check = [...params.entries()]
    .sort(([a], [b]) => a.localeCompare(b))
    .map(([k, v]) => `${k}=${v}`)
    .join("\n");
  const secret = crypto.createHmac("sha256", "WebAppData").update(botToken).digest();
  const expected = crypto.createHmac("sha256", secret).update(check).digest("hex");
  if (!received || !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received))) {
    throw new Error("bad hash");
  }
  if (Date.now() / 1000 - Number(params.get("auth_date")) > maxAge) {
    throw new Error("init data is too old");
  }
  return { ...Object.fromEntries(params), user: JSON.parse(params.get("user")) };
}

Библиотеки, которые проверяют initData по алгоритму Telegram (например, aiogram.utils.web_app.safe_parse_webapp_init_data, @telegram-apps/init-data-node), работают без изменений: они проверяют hash токеном бота.

Проверка без токена (signature, Ed25519)

Сторонний сервис, у которого нет токена бота, может проверить поле signature публичным ключом сервера StaticGram:

Публичный ключ Ed25519 (hex)
8a46bf36bbf37596cf69ad66305bf0fc1f2177ffd50063d0f45d364e4726278f

Подписывается строка <bot_id>:WebAppData\n<data_check_string>, где data_check_string собирается из всех полей, кроме hash и signature; signature закодирована в base64url без =. Это тот же формат, что у Telegram, отличается только ключ (ключи Telegram для StaticGram не подходят).

Python (cryptography)
import base64
from urllib.parse import parse_qsl
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey

STATICGRAM_PUBLIC_KEY = bytes.fromhex("8a46bf36bbf37596cf69ad66305bf0fc1f2177ffd50063d0f45d364e4726278f")


def check_signature(init_data: str, bot_id: int) -> bool:
    data = dict(parse_qsl(init_data, keep_blank_values=True))
    sig = data.pop("signature")
    data.pop("hash", None)
    check = f"{bot_id}:WebAppData\n" + "\n".join(f"{k}={v}" for k, v in sorted(data.items()))
    try:
        Ed25519PublicKey.from_public_bytes(STATICGRAM_PUBLIC_KEY).verify(
            base64.urlsafe_b64decode(sig + "=" * (-len(sig) % 4)), check.encode())
        return True
    except Exception:
        return False
Node.js
import crypto from "node:crypto";

const PUBLIC_KEY = crypto.createPublicKey({
  // SPKI-обёртка для сырого 32-байтного ключа Ed25519
  key: Buffer.concat([Buffer.from("302a300506032b6570032100", "hex"),
    Buffer.from("8a46bf36bbf37596cf69ad66305bf0fc1f2177ffd50063d0f45d364e4726278f", "hex")]),
  format: "der", type: "spki",
});

export function checkSignature(initData, botId) {
  const params = new URLSearchParams(initData);
  const sig = Buffer.from(params.get("signature") ?? "", "base64url");
  params.delete("signature");
  params.delete("hash");
  const check = `${botId}:WebAppData\n` + [...params.entries()]
    .sort(([a], [b]) => a.localeCompare(b))
    .map(([k, v]) => `${k}=${v}`)
    .join("\n");
  return crypto.verify(null, Buffer.from(check), PUBLIC_KEY, sig);
}

Оплата в Mini App

  1. Бэкенд создаёт счёт в звёздах: createInvoiceLink с currency: "XTR". Ответ: https://staticgram.top/invoice/<slug>.
  2. Приложение открывает его: Telegram.WebApp.openInvoice(link, status => …). Клиент показывает форму оплаты звёздами поверх приложения.
  3. Бот получает pre_checkout_query и отвечает answerPreCheckoutQuery, после оплаты приходит successful_payment (см. Оплата звёздами).
  4. Приложение получает invoiceClosed со статусом paid, cancelled, failed или pending.
JavaScript
const link = await fetch("/api/invoice").then(r => r.text()); // createInvoiceLink на бэкенде
Telegram.WebApp.openInvoice(link, (status) => {
  if (status === "paid") showThanks();
});
Официальный telegram-web-app.js

Он принимает в openInvoice только ссылки t.me. Если вы подключили скрипт с telegram.org, замените адрес: https://staticgram.top/invoice/AbC → https://t.me/$AbC. Клиент передаёт серверу только slug, оплата пройдёт в StaticGram.

Ответ в чат и shareMessage

  • answerWebAppQuery: если приложение открыто кнопкой меню, вложением или inline-кнопкой, в initData есть query_id. Бэкенд отправляет по нему InlineQueryResult, и сообщение уходит в чат от имени пользователя через бота.
  • savePreparedInlineMessage + Telegram.WebApp.shareMessage(id): пользователь сам выбирает, в какой чат отправить подготовленное сообщение.
  • Telegram.WebApp.sendData(data) работает только для приложений с кнопки web_app обычной клавиатуры. Бот получает сообщение с полем web_app_data.

Описания методов и типов взяты из официальной документации Telegram Bot API (Bot API 10.3). Адрес API: https://api.staticgram.top