AboutBlogContact
Web DevelopmentApril 6, 2026 6 min read 1241Updated: May 18, 2026

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 →


Aunimeda develops websites and web applications for businesses - corporate sites, e-commerce, portals, and custom platforms.

Contact us to discuss your web project. See also: Web Development, E-commerce Development

Read Also

How to Build an MVP in 2026: A Founder's Guide to Scope, Cost, and Speedaunimeda
Web Development

How to Build an MVP in 2026: A Founder's Guide to Scope, Cost, and Speed

What an MVP actually is (and isn't), how to scope it correctly, how much it costs in 2026, and how to choose the right development approach. Practical advice for founders and product teams.

Outsource Software Development to Kyrgyzstan: A Practical Guide for 2026aunimeda
Web Development

Outsource Software Development to Kyrgyzstan: A Practical Guide for 2026

Why businesses outsource software development to Kyrgyzstan in 2026, how to evaluate and hire a development team in Bishkek, and what the engagement process actually looks like.

SaaS Web App Development Guide 2026: Architecture, Stack, and Pricingaunimeda
Web Development

SaaS Web App Development Guide 2026: Architecture, Stack, and Pricing

How to build a SaaS web application in 2026: architecture decisions, technology stack, multi-tenancy, subscription billing, and realistic development costs. Practical guide from a development studio.

Need IT development for your business?

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

Web Development

Get Consultation All articles