telegram-miniapps について
telegram-miniappsは、Telegram Mini Appの開発とセキュリティを扱うガイドスキルです。WebViewアプリの開発者向けに、署名付きinitDataの検証、端末のビューポートやセキュアストレージ、ボットへの結果返却を説明します。
主な機能
- 01HMACとEd25519署名の検証
- 02initDataの有効期間と重複項目の確認
- 03ビューポート、安全領域、テーマへの対応
- 04アプリ結果をボットに返す方法の説明
- 05実行可能なPython検証器の提供
活用シーン
- 01Telegram Mini Appのログイン検証実装
- 02initData検証とセッション交換のレビュー
- 03モバイルWebViewのレイアウト問題の修正
- 04Mini Appの結果をボットへ送信
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ ssheleg/ telegram-dev --skill telegram-miniappsインストール後、Claude Code で/telegram-miniappsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
fixtures/verify_initdata.pyreferences/app-to-bot.mdinitdata-verification.mdviewport-and-platform.md詳細
Telegram Mini Apps — 署名付きの単一データがすべてのモデルです
Mini App は、クエリ文字列が付いた WebView 内のウェブページです。そのクエリ文字列だけがユーザーの身元を証明するもので、ボットトークンで署名されています。 サーバーで検証すれば、ボット自体と同じ強度の認証が得られます。SDK が渡す解析済みオブジェクトを信頼すれば、認証は何も得られません。
2026-08-25 時点で core.telegram.org/bots/webapps を確認。Bot API 10.3。
詳細な資料は必要に応じて参照してください。
| 資料 | 参照するタイミング |
|---|---|
references/initdata-verification.md | 検証の実装またはレビュー時 — 失敗ケースも含め、2 つのアルゴリズムを 3 つの言語で解説します |
references/app-to-bot.md | ボットへの結果の返却、インラインクエリ、アプリ内での支払い |
references/viewport-and-platform.md | 実機でアプリの表示がおかしいとき — ビューポート、安全領域、全画面表示、テーマ |
実行可能なファイルが、このファイルとともに提供されています:
fixtures/verify_initdata.py — そのまま使える検証コードと、改ざんされたユーザー ID、古い auth_date、再シリアライズされた user フィールド、逆順の鍵導出を拒否することを確認する 9 つのチェックが含まれています。python3 fixtures/verify_initdata.py の実行に必要なのは標準ライブラリだけです。
initDataUnsafe は意図的にその名前が付けられています
// The client hands you both. One is evidence, the other is a convenience.
const raw = window.Telegram.WebApp.initData; // the signed string — SEND THIS
const unsafe = window.Telegram.WebApp.initDataUnsafe; // already parsed — display only
initDataUnsafe.user.id はユーザーが任意の値に設定できる数値です。ユーザーの端末上で実行されるページから取得されるためです。ユーザーの身元、所有物、閲覧できるものなど、何を決める場合も、サーバーで未加工の文字列を使って改めて判断する必要があります。 initData をバックエンドに送り、そこで検証したうえで、検証済みのデータからユーザーの身元を特定してください。
このスキルが防ぐ唯一の欠陥は、クライアントから { userId } を受け取るエンドポイントです。正常に動作し、デモでも使えますが、誰でも他人の情報を読めるようになってしまいます。
正確に検証する
import hashlib, hmac, time
from urllib.parse import parse_qsl
MAX_AGE = 300 # seconds; a decision, not a constant handed down
def verify(init_data: str, bot_token: str) -> dict:
pairs = dict(parse_qsl(init_data, strict_parsing=True)) # already url-decoded
received = pairs.pop("hash", None)
if not received:
raise ValueError("no hash")
pairs.pop("signature", None) # the OTHER protocol's input — see below
check = "\n".join(f"{k}={pairs[k]}" for k in sorted(pairs))
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 signature")
if time.time() - int(pairs["auth_date"]) > MAX_AGE:
raise ValueError("stale")
return pairs
次の 5 つは、いずれも誰かの環境では「有効」と判定されてしまう失敗例です。
- インターネット上のコード例の半数では、キー導出の順序が逆です。 シークレットは
HMAC_SHA256(key="WebAppData", msg=bot_token)です。定数が キー で、トークンが メッセージ です。順序を逆にすると、常に同じハッシュが生成されますが、一致することはありません。よくある解決策は、検証をやめることです。 - キーでソートし、
\nで結合して、受け取った値をそのまま使います — URL デコードは 1 回だけ行い、再エンコードも再シリアライズもしません。userは JSON 文字列なので、届いた文字列をそのまま保持する必要があります。解析して再ダンプするとキーの順序が変わり、ハッシュが一致しなくなります。 - 文字列を組み立てる前に
hashとsignatureを取り除きます — ただし、理由を正しく理解してください。signatureは不要なデータではありません。Ed25519 の経路への入力であり、別個の正規化処理({bot_id}:WebAppDataのプレフィックスを付け、その後にhashとsignatureを除いたペアをソートする)を持つ別個のプロトコルです。2 つの正規化処理はfixtures/verify_initdata.pyのhmac_check_stringとed25519_check_stringのように、別々の関数にしてください。両方で共用するヘルパーは、正規化のバグをそのまま検証してしまうからです。フィクスチャのゴールデンベクトル(手作業で計算し、オラクルから導出したものではありません)がそのバグを検出します。signatureを含むinitDataが通るのは、正しい経路だけです。 - 重複や不正なデータは、計算の前に拒否します。
parse_qslはリストを返します。これを辞書にまとめると、重複したキーの最後の値が黙って残ります。user=innocent&user=adminは HMAC がどちらの値を対象にしたのかという問題を引き起こします。検証器がそのような問題を抱えるべきではありません。重複フィールドや解析できないデータは、そのまま拒否してください。また、auth_dateには上限時間も設けてください(クロックスキュー、秒単位)。未来のタイムスタンプは新しさを増すばかりです。 auth_dateは省略できません。 有効期間を設けないと、取得されたinitDataは永久に使えるベアラートークンになります。期間を決めて明示し、それより古いものは未認証として扱ってください。ログにエラーを記録して処理を続けるのではありません。- 定数時間で比較します。 16 進数のダイジェストを
==で比較するとタイミングオラクルになります。言語に用意されているcompare_digest相当の関数を使うのに、コストはかかりません。
リクエストごとに検証しない
initData はログインであって、セッションではありません。一度検証したら、独自の短期間有効なトークンを発行し、それ以降は API でそのトークンを使います。
POST /auth/telegram { initData } → verify → your session JWT (short TTL)
GET /api/... Authorization: Bearer <your token>
呼び出しのたびに再検証すると、長いセッションをカバーできるよう MAX_AGE を広く設定せざるを得ません。それでは auth_date をまったく確認しないのと同じです。
Bot トークンがない場合のサードパーティ向け経路
外部サービスは Bot トークンを保持できないため、Telegram は別の方法として Ed25519 の signature で initData に署名します。Telegram の公開鍵を使って検証できます。署名対象の文字列は異なり、<bot_id>:WebAppData と改行を先頭に付け、その後に hash と signature の両方を除いた同じソート済みの key=value ペアを続けます。公開鍵はテスト環境と本番環境で異なります。
検証器が Bot の所有者でない場合に使います。トークンを保持している場合に HMAC の代わりとして使わないでください。HMAC の経路のほうが単純で、変更されるキーも少なくて済みます。コードを含む両方の方式については、こちらを参照してください。
references/initdata-verification.md。
SDK のパッケージ名が変わった一方で、組織のページにはまだ古い名前が記載されています
- 現行のパッケージ:
@telegram-apps/sdk(2026-08-25 時点で 3.11.8)。@telegram-apps/sdk-reactも併せて提供されています。 - GitHub 組織の
Telegram-Mini-Appsでは、今もtma.jsと@tma.js/sdkが案内されています。こちらは旧名称で、最終バージョンは 3.3.0 と、マイナーリリース 8 つ分遅れています。 - この組織のテンプレート(
reactjs-template、nextjs-template、vuejs-template、solidjs-template)は参考になります。ほかの 4 つはアーカイブ済みで、サポート対象外と明記されています。エージェントが検索結果の最初のものをコピーすると、約半分の確率でそのうちの 1 つに当たります。
どちらのライブラリも必須ではありません。Mini App インターフェースは telegram-web-app.js によって提供される window.Telegram.WebApp です。SDK で得られるのは型付きアクセスとプラットフォームのシムであり、機能そのものではありません。
デバイスはあなたのノートパソコンではありません
Mini App は、キーボード、ヘッダー、ホームインジケーターが表示領域を占める WebView 内でレンダリングされます。推測に頼らず、次のビューポートフィールドを参照してください。
viewportHeight、viewportStableHeight、safeAreaInset、
contentSafeAreaInset、isFullscreen、isActive。100vh はどちらのプラットフォームでも正しくなく、その誤り方も異なります。
テーマは themeParams と colorScheme から取得され、あなたの都合ではなくユーザーに応じて変わります。パレットをハードコードせず、テーマに連動させてください。
references/viewport-and-platform.md。
ホストはページの上位にあり、デバイスも同様です
以下の論文とドキュメントは、2026-10-19 時点のものです。
脅威モデル:Telegram クライアントは信頼されています。ページ内の何ものも、クライアントを拒否できないためです。 Priyanka ほかによる Don't Trust the Super-App: A Case Study of Russia's Max(arXiv:2609.11814、2026-09-10)では、MAX スーパーアプリを対象に、ホストが Mini App の UI をキャプチャし、ローカルストレージの読み書きを行い、実行環境に任意の JavaScript を注入し、ネットワークトラフィックを仲介し、認証コンテキストを制御できることを示しています。これらの機能により、気づかれないままなりすましが可能になります。著者らは、こうした機能は、あらゆるスーパーアプリが設計上持つ特権に由来すると論じています。この研究の対象は Telegram ではなく MAX です。境界のあり方を示すものとして捉えてください。署名済みの initData が証明するのは、クライアントが保証した内容であって、クライアントが誠実であることではありません。その前提を脅威モデルに明記し、秘密情報や判断はサーバー側に置き、ページが保持する情報は短期間だけ有効なものにしてください。
デバイス上の秘密情報は SecureStorage に保存し、読み取り可能なストレージには決して保存しないでください。 Ciccotelli、Zappone、Di Pietro による TENET: Telegram Mini App (in)security(arXiv:2608.17538)では、37 個の Mini App を分析し、30 個に 3 段階の欠陥(平文ストレージ、復号可能な暗号化、リプレイ可能なトークン)があり、保存されていた情報にはセッショントークンやウォレットのニーモニックが含まれていたことを明らかにしました。Bot API 9.0 以降、Telegram.WebApp.SecureStorage は iOS Keychain と Android Keystore を使用し、保存時に暗号化されます。1 ユーザー・1 ボットにつき最大 10 項目を保存できます。DeviceStorage は localStorage に相当するもので(1 ユーザー・1 ボットにつき 5 MB)、秘密情報の保存には使えません。短期間だけ有効なセッショントークンは前者に保存し、ニーモニックはいずれにも保存しないでください。
TON Connect 5.0 はバージョン番号の付け替えですが、3.0 からの変更はそれだけではありません。 2026-10-11、@tonconnect/ui と @tonconnect/ui-react は、@tonconnect/sdk とメジャーバージョンの系列をそろえるため、5.0.0 に移行しました。コードは ui 3.1.0 と同じです(5.0.1 は見た目に関するパッチです)。3.1.0 では、エラーハンドラーに渡される内容が変わりました。UI のエラークラスは error.name にクラス名を報告するようになりました(以前は "Error" でした)。また、既知のウォレット拒否は TonConnectUIError: Unhandled error ではなく、UserRejectsError のような型付き SDK エラーとしてアプリに届きます。Mini App の検出(isInTMA、getTmaPlatform など)は @tonconnect/sdk に移され、モバイルのモーダルには Telegram と Gram Wallet がそれぞれ独立したボタンとして表示されます。3.0.x からアップグレードする前に、型付きエラーに応じた処理を追加してください。
ボットに結果を返す方法
sendData(data)— キーボードボタンから開かれたアプリ向けです。アプリを閉じ、web_app_dataをボットに届けます。ボットは、これがユーザーからの入力であることに変わりはないため、引き続きユーザー入力として扱う必要があります。answerWebAppQuery— インラインボタンから開かれたアプリ向けです。検証済みのinitDataに含まれるquery_idを使用します。有効期間は短く、先に時間のかかる処理を行うサーバーでは、期限切れになっているでしょう。- 支払いは、
telegram-botsで説明されているのと同じpre_checkout_query→successful_paymentの手順で、Stars を使ってボット経由で処理します。 Mini App はユーザーがクリックする場所であり、支払いが確定する場所ではありません。
リリース前の確認事項
- サーバーは検証済みの
initDataから ID を特定します。クライアントから送信された ID は使用しません(§initDataUnsafeという名前には理由があります)。 auth_dateは明示された期間と照合され、initDataは独自のセッションと交換されます(§ 正確な検証方法、§ すべてのリクエストで検証しない)。- ハッシュの比較は定時間で行われ、チェック文字列の作成前に
hashとsignatureの両方が削除されます。 - SDK は
@telegram-apps/*です。また、組織からコピーしたテンプレートがアーカイブ済みのものでないことを確認します(§ SDK のパッケージ名が変更されました)。 - レイアウトでは
100vhではなく、安全領域とビューポートのフィールドを読み取ります(§ デバイスはあなたのノートパソコンではありません)。 - 脅威モデルではホストを信頼できるものとして扱い、ページが保持するシークレットは
localStorageやDeviceStorageではなくSecureStorageに保存します(§ ホストはページより上位にあります)。
トリガー
このページに誘導するその他のフレーズ:「miniapp」、「WebAppData」、「@telegram-apps/sdk」。
0.5.4 で説明からこちらに移動しました:「telegram web app」、「телеграм веб-апп」、「валидация initData」。