Sitemap

From a Button to Production: How We Build Telegram Mini Apps

18 min readJun 24, 2025

Hi! We’re Dima and Ilya, developers on the TMA team at Doubletapp, and in this tutorial, we’ll show you how to build a Telegram Mini App using a React + Python stack.

Telegram Mini Apps are a powerful tool for creating interactive web applications that run directly inside the messenger. They’re perfect for games, marketplaces, booking services, and much more.

Press enter or click to view image in full size

In this tutorial, we’ll walk through the process of building a Mini App:

  • setting up the project
  • building the frontend in React with Telegram SDK integration
  • implementing the backend with Python (Django)
  • connecting all components and deploying the application.

Who is this guide for?

This guide is aimed at experienced developers who want to build a production-ready Mini App using the React + Python stack. We won’t cover basic concepts like deploying a standard web app on this stack — instead, we’ll focus on building a fully functional, optimized, and secure solution.

Purpose of the article

Our goal is not just to walk through the theory, but to guide you through the entire development process — from setting up the environment to deploying a working application. This guide will help you avoid common pitfalls and significantly speed up your development.

Key concepts and terminology

In this section, we’ll explain what Telegram Mini Apps are, what capabilities they offer to developers and users, and how they differ from traditional Telegram bots. Understanding these distinctions will help you decide when Mini Apps are a better solution for your project.

What are Telegram Mini Apps?

Telegram Mini Apps (TMAs) are web applications that run inside Telegram, allowing users to interact with services without installing separate mobile apps. They work within Telegram’s built-in browser and provide seamless authorization, a smooth user experience, and deep integration with the messenger. TMAs are supported across all major platforms: Android, iOS, Windows, macOS, and Linux.

Mini Apps use standard web technologies such as HTML, CSS, and JavaScript, and communicate with Telegram through the Web Apps API. This enables developers to build flexible interfaces, access user data (e.g., name and contact information), and interact via bots.

In short, Mini Apps are a powerful way to create interactive services that run directly in Telegram, simplifying user interactions with digital products.

How are Mini Apps different from regular bots?

While both Telegram Mini Apps and bots operate within the messenger, they have different interaction models and use cases.

1. Interface and user experience

Telegram bots interact with users through text commands and inline buttons. Their interface is limited by the capabilities of the messenger.
Mini Apps, on the other hand, offer a full graphical interface, enabling rich and interactive web applications directly inside Telegram.

2. Technical implementation

Bots operate through the Telegram Bot API, which manages messages, commands, and buttons.
Mini Apps use the Web Apps API, which allows launching web applications that communicate with Telegram via JavaScript.

3. Use cases

Bots are typically used for task automation, sending broadcasts, customer support, and basic chatbot workflows.
Mini Apps are better suited for more complex services such as e-commerce and delivery platforms, fintech and payment applications, games and entertainment services, CRMs, and business tools.

In essence, Mini Apps extend the functionality of bots, providing a more flexible and user-friendly experience within Telegram.

Preparing for development

Before building a Telegram Mini App, it’s important to properly set up your environment and define the project’s architectural foundation. In this section, we’ll walk through each preparation step:

  • configure a local environment for isolated and convenient development
  • organize the project as a monorepo to manage frontend and backend in a single repository
  • create a Telegram bot that will be used to launch the Mini App
  • initialize a frontend application using Vite + React
  • set up Docker to unify the development and production environments
  • use proxy tools for seamless Mini App testing
  • configure Nginx
  • implement user authentication via Init Data
  • connect the Telegram Web Apps SDK (@telegram-apps/sdk-react)

Setting up the local environment

The first step is to prepare a complete local environment that runs both the frontend and the backend simultaneously. This is critical not only for testing application logic, but also for ensuring proper authentication through Telegram.

To isolate the environment and streamline the launch of all components, we’ll use Docker — this allows you to standardize the setup on both local machines and production servers, eliminating the need for manual configuration.

One key aspect of Mini Apps is that user authentication is handled via Init Data validation using a specific Telegram bot token. This means the backend must verify the Init Data signature against the token of the bot used to launch the Mini App.

As a result, each developer needs to create their own Telegram bot and use its token during local development. Even frontend developers must run a local backend to enable full authentication and test app behavior in production-like conditions.

We also use a monorepo approach for our projects: both frontend and backend live in the same repository but are split into separate folders (/frontend, /backend). This structure significantly simplifies:

  • collaborative development
  • CI/CD configuration
  • API version synchronization
  • making coordinated changes
  • centralized dependency and config management

This approach is particularly effective for Mini Apps, where the frontend and backend are tightly coupled.

Creating a bot

A Telegram Mini App is launched via a Telegram bot. The bot acts as an entry point that allows users to open a web application inside the messenger. Therefore, having a properly configured bot is a prerequisite for running a Mini App.

If you don’t have a bot yet, you can create one using @BotFather. We won’t go into detail here, as the process is standard and well-documented in the official Telegram documentation.

Here’s what you need to do:

  • Obtain the bot token.
  • Set the Mini App URL.
    In a conversation with @BotFather, navigate to:
    Bot Settings → Configure Mini App → Edit Mini App URL
  • Configure the Mini App launch button.
    For better usability, you can add a button that opens the Mini App directly. Do this via @BotFather:
    Bot Settings → Edit Menu Button

Creating a user on first interaction

In a Mini App, it’s essential that a user is created in your database on any interaction with the bot — not just when the /start command is triggered. Telegram includes the from object (representing the user) in nearly all update types, which makes it possible to handle user creation in a centralized way.

We’ll implement middleware that:

  • extracts Telegram user data from any incoming update
  • creates a user in the database if they’re not already registered
  • stores the user in the request context, making them easily accessible in the rest of the logic.

import typing

import structlog
from dependency_injector.wiring import Provide
from dependency_injector.wiring import inject
from telegram import Update
from telegram.ext import CallbackContext

from project.containers import Container

if typing.TYPE_CHECKING:
from users.services import UserService

logger = structlog.get_logger(__name__)


@inject
def update_or_create_user(
update: Update,
context: CallbackContext,
user_service: 'UserService' = Provide[Container.user_service],
) -> None:
if update.message is None:
return

tg_user = update.message.from_user
if not tg_user:
logger.debug('No Telegram user found in the update message')
return

user, created = user_service.update_or_create(tg_user)

structlog.contextvars.bind_contextvars(
user_id=user.id,
messenger_user_id=update.message.from_user.id,
)

if created:
logger.info('New user created')

context.user_data['user'] = user

Done! Now, in every handler, we’ll have access to the current user.

Once the bot is configured, we can move on to the next step — creating the frontend application that will run inside Telegram.

Initializing the React application

We’ve previously described how to set up a standard React app. In this article, we’ll focus on the specifics of building a TMA project. The configuration of a Telegram Mini App project includes a dedicated initialization function.

import { retrieveLaunchParams } from '@telegram-apps/sdk-react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import 'config/i18n/i18n.ts'
import { init } from './utils/init.ts'

const root = ReactDOM.createRoot(document.getElementById('root')!)

try {
// Configure all application dependencies.
init(retrieveLaunchParams().startParam === 'debug' || import.meta.env.DEV)
root.render(<App />)
} catch (e) {
console.error(e)

The Mini App initialization takes place before rendering the React application.

import {
backButton,
viewport,
themeParams,
miniApp,
initData,
$debug,
init as initSDK
} from '@telegram-apps/sdk-react'

export function init(debug: boolean): void {
$debug.set(debug)

initSDK()

if (!backButton.isSupported() || !miniApp.isSupported()) {
throw new Error('ERR_NOT_SUPPORTED')
}

backButton.mount()
miniApp.mount()
themeParams.mount()
initData.restore()
void viewport
.mount()
.catch(e => {
console.error('Something went wrong mounting the viewport', e)
})
.then(() => {
viewport.bindCssVars()
})

miniApp.bindCssVars()
themeParams.bindCssVars()
}

In this project, we used version 2 of the @telegram-apps/sdk-react package. The SDK is not initialized by default — all of its components remain unmounted. To start using the SDK and access its components (such as the back button, mini app object, theme parameters, viewport, and others), we need to call the init function first. Only after that can we safely use its features throughout the application.

Docker setup

Since our project follows a monorepo structure, we use a dedicated Docker configuration for local development. This setup supports hot reload, which is essential for frontend development.

For the development environment, we added a special docker-compose.local.yml configuration. It includes:

  • an additional Vite service running the frontend in development mode
  • a modified Nginx config that proxies requests to the Vite service
services:
...
vite:
build:
context: .
dockerfile: ./docker/vite/Dockerfile
args:
API_BASE_URL: ${API_BASE_URL}
volumes:
- ./frontend:/app
- /app/node_modules
restart: always
ports:
- "5173:5173"

nginx:
image: "${IMAGE_NGINX}"
build:
context: .
dockerfile: docker/nginx-local/Dockerfile
...

Docker-compose.local.yml

http {
...
server {
...
location / {
proxy_pass http://vite:5173;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
}

nginx.conf

The Vite application is launched in development mode as follows:

FROM node:21-alpine as build

ARG API_BASE_URL

ENV VITE_API_BASE_URL=$API_BASE_URL

WORKDIR /app

COPY frontend/package*.json ./
RUN npm install

COPY ./frontend .

EXPOSE 5173

CMD ["npm", "run", "start", "--", "--host", "0.0.0.0"]

Frontend proxy tools

During development, Telegram Mini Apps require a valid public URL to render and test the app inside the Telegram client. Localhost URLs are incompatible with BotFather’s Mini App settings due to Telegram’s strict requirement for HTTPS.

To bridge this gap, developers typically use services that expose local applications to the internet via secure tunnels.

We’ve tried a few tools in our work:

  • ngrok — works only with a VPN in some regions. It offers a free plan with limitations, including one static domain, 20,000 HTTP requests per month, and 1 GB of outbound traffic. When limits are reached, you can create a new account.
  • localtunnel — a completely free alternative to ngrok, providing similar functionality. Easily installable via npm (npm install -g localtunnel). In our experience, it sometimes required frequent restarts, which also meant constantly updating the Mini App URL in BotFather and in environment variables.
  • Tuna — a paid solution available in Russia. It allows you to generate multiple domains and has proven to be stable in our usage.

User authentication via Init Data

One of the key aspects of Telegram Mini App development is user authentication. Telegram provides a mechanism for securely passing user data through Init Data, which can be used to identify and verify users. However, it’s important for developers to validate this data correctly and to consider third-party integrations when necessary.

Verifying Init Data from TMA

When a Mini App is launched, the client stores Init Data in the browser’s Telegram.WebApp.initData object. (If the app is launched via a keyboard button or inline mode, the object may be empty.)

Init Data is passed as a string containing parameters such as user info (user), a hash (hash), authentication timestamp (auth_date), and others. You can find a full description of this object in the official documentation.

The Init Data should be sent in the HTTP headers of each request. The backend is then responsible for validating the authenticity of the data and implementing the authentication/authorization mechanism. The verification process includes the following steps:

  1. Extract parameters: Parse the Init Data string to obtain the individual key-value pairs.
  2. Compute the verification signature:
  • Exclude the hash field.
  • Sort the remaining parameters alphabetically and concatenate them in the form key=value.
  • Compute an HMAC-SHA256 hash using a secret key derived from the bot token.

3. Compare signatures:

  • If the computed hash matches the provided hash, the data is considered valid.

4. Validate auth_date:

  • If the timestamp is too old (e.g., older than a few hours), the data should be considered expired.

You can find a detailed explanation of this algorithm in the Telegram docs.

To simplify working with Init Data in TMAs, we developed a small internal library at our company. It helps implement the authentication and authorization mechanism on the backend with minimal effort.

Django example

Let’s take a look at an example implementation in Django. We’ll create middleware for user authentication that will:

  • validate the Init Data signature
  • extract user information (ID, name, username, etc.)
  • and — most importantly — create a new user in the database if they don’t already exist

This step is essential because a Mini App can be opened not only via the /start command in the bot, but also directly via a link. In such cases, the user lands straight in the frontend, bypassing any initial setup logic on the bot’s side.

We’ll start by defining the necessary variables in settings.py

from telegram_webapp_auth.auth import generate_secret_key

# other settings…

TELEGRAM_BOT_TOKEN = env.str('TELEGRAM_BOT_TOKEN')
TELEGRAM_SECRET_KEY = generate_secret_key(TELEGRAM_BOT_TOKEN)

Implementing the Middleware:

from django.conf import settings
from django.http import HttpRequest
from django.http import HttpResponse
from telegram_webapp_auth.auth import TelegramAuthenticator
from telegram_webapp_auth.errors import InvalidInitDataError
from users.services import UserService


class TMAAuthorizationMiddleware:
def __init__(self, get_response) -> None:
self.get_response = get_response
self._telegram_authenticator = TelegramAuthenticator(settings.TELEGRAM_SECRET_KEY)
self._user_service = UserService()

def __call__(self, request: HttpRequest) -> HttpResponse:
auth_cred = request.headers.get('Authorization')

try:
init_data = self._telegram_authenticator.validate(auth_cred)
except InvalidInitDataError:
# TODO: handle exception ExpiredInitDataError
# TODO: handle case if Init Data is invalid
pass

if not init_data.user:
# TODO: handle case if Init Data does not contain user information
pass

current_user = self._user_service.update_or_create(init_data.user)
request.user = current_user # add user to request object

response = self.get_response(request)

return response

Add the created middleware to the MIDDLEWARE settings in settings.py:

MIDDLEWARE = [
# other middlewares
'path.to.TMAAuthorizationMiddleware',
]

Done! We can now authenticate our user from the TMA.

Verifying Third-Party Data

Telegram also introduced a feature for validating Init Data from a third-party bot. To do this, you only need the ID of the desired bot. This is useful when you want to integrate between the backends of different bots.

The integrity of the data can be verified using the signature parameter, which is a base64url-encoded Ed25519 signature. The verification is done using the public key provided by Telegram.

This algorithm is also implemented in our internal library.

Exchanging Init Data for a JWT

Some developers prefer to exchange Init Data for a JWT token after the initial initialization. The process looks like this:

  1. The frontend sends Init Data to the backend.
  2. The backend validates it and generates a JWT.
  3. The JWT is returned to the client and stored (e.g., in localStorage).
  4. All subsequent frontend requests use this token.

Pros:

  • Allows flexible session expiration management
  • Versatile — you can pass custom data inside the token
  • Cross-domain — suitable for microservices

Cons:

  • localStorage may not persist in Telegram Mini Apps: On certain platforms (iOS, Linux Desktop), Telegram WebView is sandboxed. Restarting the Mini App can clear the cache and storage. Unfortunately, there’s no workaround for this.
  • Losing the token means re-authentication, leading to extra requests

Because Telegram apps don’t always persist localStorage, we prefer using Init Data-based authentication for simpler projects. It’s secure, straightforward, and doesn’t rely on WebView behavior.

Integrating the Telegram React SDK

The @telegram-apps/sdk-react package provides a set of components and methods that allow developers to interact seamlessly with Telegram’s native UI elements and platform features.

SDK Capabilities Overview

A few examples:

BackButton — probably the most commonly used component, manages the “Back” button in the Mini App’s header. Its main purpose is to aid app navigation, but note: the click event must be handled manually, as it doesn’t trigger any default actions. We created a custom hook to simplify using this button:

import { backButton } from '@telegram-apps/sdk-react'
import { useEffect } from 'react'

export const useAppBackButton = (handler: () => void) => {
useEffect(() => {
backButton.onClick(handler)

return () => {
backButton.offClick(handler)
}
}, [backButton])

return {
isVisible: backButton.isVisible,
showButton: backButton.show.bind(backButton),
hideButton: backButton.hide.bind(backButton)
}
}

Viewport gives access to the Mini App’s display state within the Telegram client. It provides useful properties like width, height, isStable (whether a resize is expected), isExpanded, and fullscreen mode status. You can programmatically expand the app using:

 if (viewport.expand.isAvailable()) {
viewport.expand()
}

or request fullscreen mode:

if (viewport.requestFullscreen.isAvailable()) {
await viewport.requestFullscreen();
}

which is particularly useful for games or media apps. The viewport also exposes some CSS variables:

if (viewport.bindCssVars.isAvailable()) {
viewport.bindCssVars();
// Creates CSS variables:
// --tg-viewport-height: 675px
// --tg-viewport-width: 320px
// --tg-viewport-stable-height: 675px
}

ThemeParams enables the app to adapt its styles to the user’s current Telegram theme. Calling bindCssVars() makes CSS variables available:

if (themeParams.bindCssVars.isAvailable()) {
themeParams.bindCssVars();
// Creates CSS variables:
// --tg-theme-button-color: #aabbcc
// --tg-theme-accent-text-color: #aabbcc
// --tg-theme-bg-color: #aabbcc
// ...
}

This helps the Mini App feel more native and integrated with the platform.

The miniApp object allows setting the Mini App’s header and background colors:

if (
miniApp.setHeaderColor.isAvailable()
&& miniApp.setHeaderColor.supports('rgb')
) {
miniApp.setHeaderColor('#aabbcc');
miniApp.headerColor(); // '#aabbcc'
}
if (miniApp.setBackgroundColor.isAvailable()) {
miniApp.setBackgroundColor('#ffffff');
miniApp.backgroundColor(); // '#ffffff'
}

It also has close() and ready() methods — the former closes the app, while the latter notifies Telegram that your web app is initialized and ready to be displayed.

Telegram provides a hapticFeedback API for device vibration. The impactOccurred() method supports various styles like light, medium, heavy, rigid, and soft. This can significantly improve the app’s UX:

if (hapticFeedback.impactOccurred.isAvailable()) {
hapticFeedback.impactOccurred('medium');
}

The swipeBehavior object lets you control whether the Mini App can be dismissed via swipe gesture. If your app uses vertical scrolling, it’s better to disable this gesture:

if (swipeBehavior.disableVertical.isAvailable()) {
swipeBehavior.disableVertical()
}

There are also other native components available in the SDK — such as popup, mainButton, settingsButton, qrScanner, and more — that help deliver a native-like experience in your Mini App.

Key SDK Features and Init Data Authorization

Client-side request authorization in our app is performed using the initDataRaw object from the retrieveLaunchParams method:

const { initDataRaw } = retrieveLaunchParams()

export const http = axios.create({
baseURL: appConfig.api.baseUrl,
headers: {
Authorization: initDataRaw,
'Content-Type': 'application/json'
}
})

Testing and Debugging

Before launching your Mini App to production, it’s essential to ensure stable and predictable behavior across all layers — from backend logic to the Telegram UI. Since Mini Apps operate in an environment where frontend and backend are tightly coupled — and behavior can vary across platforms (Android, iOS, Desktop, Web) — debugging requires a specific approach.

In this section, we’ll cover:

  • how to effectively debug the backend, even when Init Data isn’t available yet
  • how to test the Mini App across various devices to ensure SDK functionality, responsive design, and behavioral consistency

Backend Debugging

Debugging the API effectively is crucial when developing a Telegram Mini App. Telegram passes a special Init Data parameter to the Mini App, which is used to authenticate the user on the server. However, during development, using this directly is inconvenient:

  • Init Data can only be retrieved from a running frontend within Telegram
  • You have to manually copy it each time — which slows down development and debugging

To speed up local API development, it’s helpful to implement an alternate dev-only authorization mode where you can pass user_id directly, bypassing Init Data.

How to implement it:

  • In your middleware, check if development mode is enabled (e.g., via settings.DEBUG)
  • If the X-Dev-User-ID header is present in the request — use that instead of Init Data
  • Find or create the user by this ID and assign them to request.user

Example middleware:

from django.conf import settings
from django.http import HttpRequest
from django.http import HttpResponse
from users.services import UserService

class DevAuthMiddleware:
def __init__(self, get_response) -> None:
self.get_response = get_response
self._user_service = UserService()

def __call__(self, request: HttpRequest) -> HttpResponse:
auth_cred = request.headers.get('X-Dev-User-ID')
if settings.DEBUG and auth_cred:
user, _ = user_service.get_or_create(user_id=auth_cred)
request.user = user

return self.get_response(request)

Now you can use Postman, cURL, or Swagger UI to send requests by simply providing the user ID.

Cross-Device Testing

For Android, the main debugging interface is Chrome DevTools, which can be accessed via chrome://inspect/#devices in a Chrome browser on a connected machine. This tool lets you inspect DOM elements, monitor network activity, view console logs, and more.

For the iOS Telegram client, Safari’s Web Inspector on macOS provides similar debugging capabilities. After connecting your iOS device to a Mac, you can use the “Develop” menu in Safari to inspect the app’s WebView with functionality similar to Chrome DevTools.

In situations where platform-specific debugging tools aren’t available — especially for iOS devices without access to a Mac — the eruda npm package offers a lightweight in-app debugging alternative. Eruda is a simple web console you can embed directly into your Mini App. After adding the library to your project, initialize it using eruda.init(). Once deployed, the app UI will display an Eruda icon. Clicking it opens the console, enabling basic debugging tools like logging, DOM inspection, and network monitoring.

Production Deployment

Once development is complete, it’s time to deploy the Mini App to production. In this section, we’ll cover core best practices for deployment, security, performance, and ensuring your Telegram Mini App runs reliably in production — most of which also apply to general web development.

General Production Recommendations

  • Isolate environments: Local dev, staging, and production should be completely separated to avoid unintended interactions.
  • Monitor logs and errors using tools like Sentry, Prometheus + Grafana + Loki, or similar observability stacks.
  • Use Webhook mode for your Telegram bot in production:

It’s more efficient

It has lower latency (updates arrive instantly)

It scales more easily

Security Recommendations

  • Limit the lifetime of Init Data — never issue long-lived tokens
  • Restrict access to admin interfaces — via IP allowlists, VPN, or secure authentication
  • Limit CORS and CSP sources — to Telegram WebView, your domain, and required APIs
  • Keep dependencies up-to-date and monitor for CVE vulnerabilities
  • Protect against DDoS attacks — bad actors never sleep
  • Set rate limits on all API endpoints — don’t let clients overload your server

Static Asset Caching and Compression

  • Minify and hash JS/CSS — saves bandwidth and improves load times
  • Enable Gzip or Brotli compression — further reduces asset size

Backend API Caching

To improve response times and reduce backend load:

  • Cache public and frequently repeated GET requests — and ensure they are idempotent
  • Use Redis or Memcached to cache the requests
  • Enable Cache-Control headers and leverage browser caching

Case Studies

Here are a few real-world projects we’ve delivered using Telegram Mini Apps — from games to e-commerce and service platforms:

  • Doubletapalka — We designed the mechanics, built the economy model, and handled everything from UI/UX to backend logic. The result: a fast-paced game with monetization potential.
  • Merch Store — A fully functional e-commerce experience inside Telegram, complete with native payments via credit cards and cryptocurrency. We handled the design, frontend, and backend.
  • Car Rental Service — We created a clean, UX-focused interface for booking vehicles directly within a Mini App. Emphasis was on intuitive booking flow and visual clarity.
  • CryptoFlow — A card-based decision game in the style of Reigns. Includes swipe mechanics, story saving, achievements, collectible cards, and in-app purchases using Telegram Stars. We built both the frontend and backend.

Conclusion

Telegram Mini Apps are a powerful tool for building fully functional web applications inside the messenger. In this guide, we walked through the key steps needed to create a production-ready solution.

Quick Recap

  • We explored what Mini Apps are and how they differ from traditional bots
  • Set up the development environment using Docker, Nginx, and a monorepo
  • Created and configured a bot to work with a Mini App
  • Implemented authorization via Init Data and user registration
  • Configured the frontend with Vite and the backend with Django
  • Covered debugging, testing, and production deployment best practices

If you’ve made it this far — you now have everything you need to launch your own Telegram Mini App.
Good luck in production! 🚀

Want to integrate a Telegram Mini App into your business?

If you see the potential in TMAs and want to use them strategically, we’re here to help. Our team builds solutions that drive results — from intuitive interfaces to deep integration with your business processes.

Let’s talk — email us at hi@doubletapp.ai or reach out via our Telegram bot.
It all starts with a short conversation. We’ll take care of the rest.

Additional Resources

Read our other stories:

Linkedin.com

--

--

No responses yet