AboutBlogContact
DevelopmentApril 6, 2026 6 min read 169

Telegram Mini App with Payments: Complete Developer Tutorial (2026)

AunimedaAunimeda
📋 Table of Contents

Telegram Mini App with Payments: Complete Developer Tutorial (2026)

Telegram Mini Apps are web applications that run inside Telegram. No app store, no separate install, no new account - users interact with your app inside the messenger they already use daily. With 900M+ monthly active users and Telegram Payments built in, this is one of the fastest ways to ship a transactional product.

This tutorial builds a simple product store Mini App with real payments end-to-end.


What We're Building

A Mini App where users can:

  • Browse a product catalog
  • Add items to cart
  • Pay via Telegram Payments (connected to your payment provider)
  • Receive order confirmation in chat

Stack: React + Vite (frontend), Node.js + TypeScript (bot + backend), grammY (Telegram bot library).


Part 1: Bot Setup

npm init -y
npm install grammy express dotenv
npm install -D typescript tsx @types/node @types/express

Create your bot with @BotFather:

  1. /newbot → set name and username
  2. Copy the token → BOT_TOKEN in .env
  3. /mybots → Bot Settings → Menu Button → configure Web App URL
  4. For payments: /mybots → Payments → connect provider (Stripe, YooKassa, etc.)
// bot.ts
import { Bot, webhookCallback } from 'grammy';
import express from 'express';

const bot = new Bot(process.env.BOT_TOKEN!);

// Command to open the Mini App
bot.command('start', async (ctx) => {
  await ctx.reply('Welcome to our store!', {
    reply_markup: {
      inline_keyboard: [[
        {
          text: '🛍 Open Store',
          web_app: { url: process.env.WEBAPP_URL! } // Your Mini App URL
        }
      ]]
    }
  });
});

// Handle payment pre-checkout (must answer within 10 seconds)
bot.on('pre_checkout_query', async (ctx) => {
  await ctx.answerPreCheckoutQuery(true); // Approve payment
  // Add validation here: check stock, verify price, etc.
});

// Handle successful payment
bot.on('message:successful_payment', async (ctx) => {
  const payment = ctx.message.successful_payment;
  const payload = JSON.parse(payment.invoice_payload);

  await fulfillOrder(payload.orderId, ctx.from.id);

  await ctx.reply(
    `✅ Payment received! Order #${payload.orderId} confirmed.\n` +
    `Amount: ${payment.total_amount / 100} ${payment.currency}`
  );
});

Part 2: React Mini App Frontend

npm create vite@latest mini-app -- --template react-ts
cd mini-app && npm install
// src/App.tsx
import { useEffect, useState } from 'react';

// Telegram WebApp SDK is injected by Telegram
declare global {
  interface Window {
    Telegram: {
      WebApp: TelegramWebApp;
    };
  }
}

const tg = window.Telegram.WebApp;

function App() {
  const [products, setProducts] = useState<Product[]>([]);
  const [cart, setCart] = useState<CartItem[]>([]);

  useEffect(() => {
    // Initialize Telegram WebApp
    tg.ready();
    tg.expand(); // Expand to full screen
    tg.enableClosingConfirmation(); // Ask before closing with items in cart

    fetchProducts();
  }, []);

  // Update MainButton when cart changes
  useEffect(() => {
    if (cart.length > 0) {
      const total = cart.reduce((sum, item) => sum + item.price * item.quantity, 0);
      tg.MainButton.setText(`Checkout - ${total} ₽`);
      tg.MainButton.show();
      tg.MainButton.onClick(handleCheckout);
    } else {
      tg.MainButton.hide();
    }
  }, [cart]);

  async function handleCheckout() {
    tg.MainButton.showProgress(true);

    try {
      // Send cart to backend, get invoice link
      const response = await fetch('/api/create-invoice', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          cart,
          userId: tg.initDataUnsafe.user?.id,
          initData: tg.initData, // For server-side auth verification
        }),
      });

      const { invoiceLink } = await response.json();

      // Open Telegram payment dialog
      tg.openInvoice(invoiceLink, (status) => {
        if (status === 'paid') {
          tg.showAlert('Order confirmed! 🎉', () => tg.close());
        } else if (status === 'failed') {
          tg.showAlert('Payment failed. Please try again.');
        }
        // status: 'paid' | 'cancelled' | 'failed' | 'pending'
      });
    } finally {
      tg.MainButton.hideProgress();
    }
  }

  return (
    <div style={{ padding: 16, backgroundColor: tg.themeParams.bg_color }}>
      <h1 style={{ color: tg.themeParams.text_color }}>Our Store</h1>
      <ProductGrid products={products} onAddToCart={(p) => setCart(prev => addToCart(prev, p))} />
    </div>
  );
}

Part 3: User Authentication

Never trust data sent from the frontend directly. Verify initData server-side:

// auth.middleware.ts
import crypto from 'crypto';

export function verifyTelegramInitData(initData: string): TelegramUser | null {
  const params = new URLSearchParams(initData);
  const hash = params.get('hash');
  params.delete('hash');

  // Sort params and create data check string
  const dataCheckString = [...params.entries()]
    .sort(([a], [b]) => a.localeCompare(b))
    .map(([key, value]) => `${key}=${value}`)
    .join('\n');

  // HMAC-SHA256 with key = HMAC-SHA256("WebAppData", BOT_TOKEN)
  const secretKey = crypto
    .createHmac('sha256', 'WebAppData')
    .update(process.env.BOT_TOKEN!)
    .digest();

  const expectedHash = crypto
    .createHmac('sha256', secretKey)
    .update(dataCheckString)
    .digest('hex');

  if (expectedHash !== hash) return null;

  // Check auth_date is not too old (prevent replay attacks)
  const authDate = parseInt(params.get('auth_date') || '0');
  if (Date.now() / 1000 - authDate > 3600) return null; // 1 hour max

  return JSON.parse(params.get('user') || 'null');
}

Part 4: Create Invoice (Backend)

// invoice.controller.ts
import { Bot } from 'grammy';

const bot = new Bot(process.env.BOT_TOKEN!);

app.post('/api/create-invoice', async (req, res) => {
  const { cart, userId, initData } = req.body;

  // Verify Telegram auth
  const telegramUser = verifyTelegramInitData(initData);
  if (!telegramUser || telegramUser.id !== userId) {
    return res.status(401).json({ error: 'Unauthorized' });
  }

  // Calculate total
  const total = cart.reduce((sum: number, item: CartItem) =>
    sum + item.price * item.quantity, 0
  );

  // Create order in DB
  const order = await db.orders.create({
    data: {
      userId: telegramUser.id,
      items: cart,
      total,
      status: 'pending',
    },
  });

  // Create Telegram invoice link
  const invoiceLink = await bot.api.createInvoiceLink(
    'Your Order',                          // Title
    `Order #${order.id} - ${cart.length} items`,  // Description
    JSON.stringify({ orderId: order.id }), // Payload (returned on payment)
    process.env.PAYMENT_PROVIDER_TOKEN!,   // Provider token from BotFather
    'RUB',                                 // Currency
    [{ label: 'Total', amount: total * 100 }], // Amount in kopecks/cents
    {
      need_name: false,
      need_phone_number: false,
      need_email: false,
      need_shipping_address: false,
      is_flexible: false,
      // Add photo for better UX:
      photo_url: 'https://yoursite.com/store-logo.jpg',
      photo_size: 512,
    }
  );

  res.json({ invoiceLink });
});

Part 5: Telegram Theme Integration

Mini Apps should match Telegram's theme automatically:

// Use CSS variables provided by Telegram
const styles = {
  container: {
    backgroundColor: 'var(--tg-theme-bg-color)',
    color: 'var(--tg-theme-text-color)',
  },
  button: {
    backgroundColor: 'var(--tg-theme-button-color)',
    color: 'var(--tg-theme-button-text-color)',
    border: 'none',
    borderRadius: 8,
    padding: '12px 24px',
  },
  card: {
    backgroundColor: 'var(--tg-theme-secondary-bg-color)',
    borderRadius: 12,
    padding: 16,
  },
};

Payment Providers for Telegram Payments

Provider Regions Notes
Stripe Global (except RU) Best for international
YooKassa Russia Best for RU market
Sberbank Russia High Sber user adoption
Payme Uzbekistan UZ market
Click Uzbekistan UZ market
Robokassa Russia/CIS Wide method support

Connect via BotFather: /mybots → Payments → choose provider → get provider token.


Deployment

The Mini App frontend must be served over HTTPS - Telegram rejects HTTP.

# Build
cd mini-app && npm run build

# Serve with nginx or upload to Vercel/Cloudflare Pages
# Set WEBAPP_URL in your bot .env to the HTTPS URL

Telegram requirement: valid SSL certificate, no self-signed certs.


Useful Telegram WebApp API Methods

tg.ready()                          // Tell Telegram the app is loaded
tg.expand()                         // Full-screen mode
tg.close()                          // Close the app
tg.sendData(data: string)           // Send string to bot (no payment)
tg.openLink(url: string)            // Open external URL
tg.openInvoice(url, callback)       // Open payment dialog
tg.showAlert(message, callback)     // Native alert dialog
tg.showConfirm(message, callback)   // Native confirm dialog
tg.showPopup(params, callback)      // Custom popup
tg.hapticFeedback.impactOccurred()  // Haptic feedback (mobile)
tg.MainButton                       // Bottom action button
tg.BackButton                       // Back navigation button
tg.themeParams                      // Current theme colors
tg.initDataUnsafe.user              // Current user (verify server-side!)
tg.platform                         // 'ios' | 'android' | 'web'

Build your Telegram Mini App with Aunimeda →

Read Also

Telegram Bot vs WhatsApp Bot: Which to Build for CIS Markets (2026)aunimeda
Development

Telegram Bot vs WhatsApp Bot: Which to Build for CIS Markets (2026)

Detailed comparison of Telegram and WhatsApp bots for Russian, Kazakh, and Kyrgyz markets. Audience data, technical capabilities, costs, and when to build each.

2GIS Maps Flutter Integration Guide: Maps for CIS Apps (2026)aunimeda
Development

2GIS Maps Flutter Integration Guide: Maps for CIS Apps (2026)

How to integrate 2GIS Maps SDK into your Flutter app. Map display, markers, routing, search, and geolocation - with full code examples for iOS and Android.

Kaspi Pay API Integration Guide for Web and Mobile Apps (2026)aunimeda
Development

Kaspi Pay API Integration Guide for Web and Mobile Apps (2026)

The complete developer guide to integrating Kaspi Pay in your web or mobile application. Authentication, payment flow, webhooks, and handling edge cases for the Kazakhstan market.

Need IT development for your business?

We build websites, mobile apps and AI solutions. Free consultation.

Get Consultation All articles