Guide #68 Bot Engineering Guide

How to Accept Automated UPI Payments in Telegram Bots (Python & Node.js Guide)

By Aryan Gupta September 8, 2026 5 min read

From private paid signals channels and VIP communities to digital software keys and game assets, Telegram has evolved into India's most vibrant hub for indie commerce and automation. However, managing transactions manually by asking buyers to "send a payment screenshot and wait" results in severe buyer friction, delayed fulfillment, and devastating fake UTR fraud. In this comprehensive developer guide, we explore how to accept automated UPI payments in Telegram bots using Python and Node.js with FamGateway's zero-GST, 0% fee API infrastructure.

Direct Answer • AEO Overview

How do Telegram bots accept automated UPI payments in India? Telegram bots automate UPI payments by sending an HTTP POST request to https://famgateway.in/api/create-order with an X-Api-Key header. When a user sends /buy, the bot generates a dynamic UPI order valid for 300 seconds (5 minutes) and renders an inline payment button. The user pays via any UPI app (Google Pay, PhonePe, Paytm, FamPay). Within 1 to 3 seconds, FamGateway sends an HMAC-SHA256 signed webhook to your bot server. The bot validates the signature against your API key, looks up the user's Telegram chat ID, and automatically delivers digital keys or a single-use channel invite link (createChatInviteLink) without any human intervention.

1. Telegram Bot Payment Pipeline Architecture

The diagram below illustrates the exact asynchronous communication flow between Telegram users, your bot daemon, FamGateway API, and the bank settlement tier:

+------------------+ +-----------------------+ +------------------------+ | Telegram User | | Telegram Bot Backend | | FamGateway Cluster | +------------------+ +-----------------------+ +------------------------+ | | | | 1. /buy or /subscribe | | |------------------------------>| | | | 2. POST /api/create-order | | | Headers: X-Api-Key | | | Body: amount, custom_id | | |--------------------------------->| | | | 3. Create Session | | | TTL: Exactly 300s | | 4. Return checkout_url & QR | Unique dynamic QR | |<---------------------------------| | | 5. Save order_id <-> chat_id | | | in SQLite / Redis | | 6. Reply with Inline Button: | | | [Pay INR 199 via UPI] | | |<------------------------------| | | | | 7. User clicks button, scans QR or opens UPI app (GPay/PhonePe) | |----------------------------------------------------------------->| | | 8. Verify Bank Credit | | Extract 12-Digit UTR | | 9. HTTP POST Webhook Callback | Apply Mutex Lock | | Header: X-FamGateway-Signature| | |<---------------------------------| | | | | | 10. Verify HMAC with API Key | | | Check UTR Deduplication | | | Call createChatInviteLink | | 11. "Payment verified! Here | or read key from DB | | is your private link!" | | |<------------------------------| |

2. Production Python Implementation (python-telegram-bot + FastAPI + SQLite)

Below is an enterprise-grade, asynchronous Python implementation combining python-telegram-bot (v20+) and FastAPI with persistent SQLite storage for order state and automated channel invites:

# telegram_shop_bot.py
import os
import hmac
import hashlib
import sqlite3
import httpx
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request, HTTPException, status
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import Application, CommandHandler, ContextTypes

# Configuration
TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN", "YOUR_TELEGRAM_BOT_TOKEN")
FAMGATEWAY_API_KEY = os.getenv("FAMGATEWAY_API_KEY", "YOUR_FAMGATEWAY_API_KEY")
WEBHOOK_HOST = os.getenv("WEBHOOK_HOST", "https://your-bot-domain.com")
PRIVATE_CHANNEL_ID = -1001234567890  # Target VIP Channel ID

# Database Setup
def init_db():
    conn = sqlite3.connect("bot_orders.db")
    cursor = conn.cursor()
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS orders (
            order_id TEXT PRIMARY KEY,
            chat_id INTEGER NOT NULL,
            user_id INTEGER NOT NULL,
            amount REAL NOT NULL,
            utr TEXT DEFAULT NULL,
            status TEXT NOT NULL DEFAULT 'pending',
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    """)
    cursor.execute("CREATE UNIQUE INDEX IF NOT EXISTS idx_orders_utr ON orders(utr) WHERE utr IS NOT NULL")
    conn.commit()
    conn.close()

init_db()

# Lifespan context manager for FastAPI
@asynccontextmanager
async def lifespan(app: FastAPI):
    # Start Telegram Bot Application
    await bot_app.initialize()
    await bot_app.start()
    yield
    await bot_app.stop()
    await bot_app.shutdown()

app = FastAPI(lifespan=lifespan)
bot_app = Application.builder().token(TELEGRAM_BOT_TOKEN).build()

# 1. Telegram /buy Command Handler
async def buy_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    chat_id = update.effective_chat.id
    user_id = update.effective_user.id
    user_name = update.effective_user.first_name or "Subscriber"
    amount = 199.00

    api_endpoint = "https://famgateway.in/api/create-order"
    webhook_url = f"{WEBHOOK_HOST}/api/famgateway-webhook"

    payload = {
        "amount": f"{amount:.2f}",
        "customer_name": user_name,
        "redirect_url": f"https://t.me/{context.bot.username}",
        "webhook_url": webhook_url,
        "custom_id": f"TG_{chat_id}_{user_id}"
    }
    headers = {
        "Content-Type": "application/json",
        "X-Api-Key": FAMGATEWAY_API_KEY
    }

    try:
        async with httpx.AsyncClient(timeout=10.0) as client:
            resp = await client.post(api_endpoint, json=payload, headers=headers)
            data = resp.json()

        checkout_url = data.get("checkout_url") or data.get("payment_url")
        order_id = data.get("order_id")

        if resp.status_code == 200 and checkout_url and order_id:
            # Persist order in local database
            conn = sqlite3.connect("bot_orders.db")
            cursor = conn.cursor()
            cursor.execute(
                "INSERT INTO orders (order_id, chat_id, user_id, amount, status) VALUES (?, ?, ?, ?, 'pending')",
                (order_id, chat_id, user_id, amount)
            )
            conn.commit()
            conn.close()

            # Construct Inline Payment Button
            keyboard = [
                [InlineKeyboardButton("Pay INR 199 via UPI (GPay/PhonePe/FamPay)", url=checkout_url)]
            ]
            reply_markup = InlineKeyboardMarkup(keyboard)

            await update.message.reply_text(
                f"Hello {user_name}! Your VIP access invoice has been generated.\n\n"
                f"Amount: INR {amount:.2f}\n"
                f"Order ID: `{order_id}`\n"
                f"Validity: Strictly 5 Minutes (300 Seconds)\n\n"
                "Click the button below to pay via any UPI application. Once completed, your single-use invite link will be issued automatically!",
                reply_markup=reply_markup,
                parse_mode="Markdown"
            )
        else:
            error_msg = data.get("message", "Unable to create payment invoice")
            await update.message.reply_text(f"Payment initialization failed: {error_msg}")

    except Exception as e:
        await update.message.reply_text("Temporary gateway connection error. Please try again.")

bot_app.add_handler(CommandHandler("buy", buy_command))

# 2. FastAPI Webhook Listener for Instant Automated Fulfillment
@app.post("/api/famgateway-webhook")
async def handle_famgateway_webhook(request: Request):
    raw_body = await request.body()
    received_sig = request.headers.get("X-FamGateway-Signature", "")

    if not received_sig:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Missing signature header")

    # Cryptographic HMAC-SHA256 signature verification with API Key
    expected_sig = hmac.new(FAMGATEWAY_API_KEY.encode(), raw_body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected_sig, received_sig):
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Signature mismatch")

    payload = await request.json()
    if payload.get("status") == "success":
        order_id = payload.get("order_id")
        amount = float(payload.get("amount", 0))
        utr = str(payload.get("utr", ""))

        conn = sqlite3.connect("bot_orders.db")
        cursor = conn.cursor()

        # Query order record
        cursor.execute("SELECT chat_id, user_id, status FROM orders WHERE order_id = ?", (order_id,))
        record = cursor.fetchone()

        if record:
            chat_id, user_id, current_status = record

            if current_status == "pending":
                # Check for duplicate bank UTR reuse
                cursor.execute("SELECT order_id FROM orders WHERE utr = ? AND status = 'completed'", (utr,))
                if cursor.fetchone():
                    conn.close()
                    raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Bank UTR collision")

                # Update order to completed
                cursor.execute(
                    "UPDATE orders SET status = 'completed', utr = ? WHERE order_id = ?",
                    (utr, order_id)
                )
                conn.commit()
                conn.close()

                # Generate single-use Telegram Channel Invite Link
                try:
                    invite_link_obj = await bot_app.bot.create_chat_invite_link(
                        chat_id=PRIVATE_CHANNEL_ID,
                        name=f"Sub_{order_id}",
                        member_limit=1
                    )
                    invite_link = invite_link_obj.invite_link
                except Exception:
                    invite_link = "https://t.me/your_community"

                # Send confirmation message to buyer
                await bot_app.bot.send_message(
                    chat_id=chat_id,
                    text=(
                        f"Payment Verified Successfully!\n\n"
                        f"Amount Paid: INR {amount:.2f}\n"
                        f"Bank UTR: `{utr}`\n"
                        f"Order ID: `{order_id}`\n\n"
                        f"Join your VIP Channel here (single-use):\n{invite_link}\n\n"
                        "Thank you for your purchase!"
                    ),
                    parse_mode="Markdown"
                )

                return {"status": "success", "message": "Order fulfilled"}

        conn.close()

    return {"status": "ignored"}

3. Production Node.js Implementation (Telegraf + Express + SQLite)

For JavaScript and TypeScript developers, here is the complete Node.js equivalent using telegraf, express, and better-sqlite3:

// bot.js
const { Telegraf, Markup } = require('telegraf');
const express = require('express');
const crypto = require('crypto');
const axios = require('axios');
const Database = require('better-sqlite3');

const bot = new Telegraf(process.env.TELEGRAM_BOT_TOKEN);
const app = express();
const db = new Database('bot_orders.db');

// Capture raw body for accurate HMAC verification
app.use(express.json({
    verify: (req, res, buf) => {
        req.rawBody = buf;
    }
}));

// Initialize schema
db.prepare(`
    CREATE TABLE IF NOT EXISTS orders (
        order_id TEXT PRIMARY KEY,
        chat_id INTEGER NOT NULL,
        amount REAL NOT NULL,
        utr TEXT DEFAULT NULL,
        status TEXT NOT NULL DEFAULT 'pending'
    )
`).run();

bot.command('buy', async (ctx) => {
    try {
        const amount = 149.00;
        const res = await axios.post('https://famgateway.in/api/create-order', {
            amount: amount.toFixed(2),
            customer_name: ctx.from.first_name || 'Subscriber',
            redirect_url: `https://t.me/${ctx.botInfo.username}`,
            webhook_url: 'https://your-domain.com/api/webhook',
            custom_id: `TG_${ctx.chat.id}`
        }, {
            headers: { 'X-Api-Key': process.env.FAMGATEWAY_API_KEY }
        });

        const data = res.data;
        const checkoutUrl = data.checkout_url || data.payment_url;

        if (res.status === 200 && checkoutUrl) {
            db.prepare('INSERT INTO orders (order_id, chat_id, amount) VALUES (?, ?, ?)')
              .run(data.order_id, ctx.chat.id, amount);

            await ctx.reply(
                `Payment Invoice Created!\nAmount: INR ${amount}\nOrder ID: ${data.order_id}`,
                Markup.inlineKeyboard([
                    Markup.button.url('Pay with UPI', checkoutUrl)
                ])
            );
        }
    } catch (err) {
        ctx.reply('Failed to create payment session. Try again later.');
    }
});

app.post('/api/webhook', (req, res) => {
    const signature = req.headers['x-famgateway-signature'] || '';
    const expected = crypto.createHmac('sha256', process.env.FAMGATEWAY_API_KEY)
                           .update(req.rawBody)
                           .digest('hex');

    if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
        return res.status(401).send('Signature mismatch');
    }

    const { status, order_id, utr, amount } = req.body;
    if (status === 'success') {
        const order = db.prepare('SELECT * FROM orders WHERE order_id = ? AND status = "pending"').get(order_id);
        if (order) {
            db.prepare('UPDATE orders SET status = "completed", utr = ? WHERE order_id = ?').run(utr, order_id);
            
            // Deliver digital goods to user chat
            bot.telegram.sendMessage(
                order.chat_id,
                `Payment Verified! Bank UTR: ${utr}\n\nHere is your product activation license: PRO-KEY-998822`
            );
        }
    }

    res.status(200).json({ status: 'ok' });
});

bot.launch();
app.listen(3000, () => console.log('Telegram Bot Payment Server listening on port 3000'));

4. Manual Status Fallback Command (/status)

If a buyer experiences mobile network delays, you can empower them to check their payment status on-demand by implementing a /status <order_id> command that queries FamGateway's real-time endpoint GET /api/checkout-status.php?order_id=...:

async def check_status_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    if not context.args:
        await update.message.reply_text("Usage: /status ")
        return

    order_id = context.args[0].strip()
    status_url = f"https://famgateway.in/api/checkout-status.php?order_id={order_id}"

    async with httpx.AsyncClient(timeout=8.0) as client:
        resp = await client.get(status_url)
        data = resp.json()

    if data.get("status") == "success":
        await update.message.reply_text(f"Payment Confirmed! Bank UTR: `{data.get('utr')}`", parse_mode="Markdown")
    elif data.get("status") == "expired":
        await update.message.reply_text("This payment session has expired (300s limit). Please generate a new order via /buy.")
    else:
        await update.message.reply_text("Payment is still pending. Please scan the QR code and complete your UPI transfer.")

5. Frequently Asked Questions (Telegram Bots)

Can Telegram bots in India accept UPI payments without a business bank account?

Yes. By using FamGateway's REST API (POST /api/create-order), your Telegram bot can generate dynamic UPI payment sessions and QR codes that route customer funds directly into your personal UPI ID (@fam). You do not need a corporate current account, GSTIN certificate, or institutional merchant acquiring approval.

How does a Telegram bot know when a customer has paid?

When a customer completes a UPI payment, FamGateway's verification engine ingests the bank receipt and delivers an HTTP POST webhook containing the order ID, amount, and 12-digit bank UTR to your bot server. Your bot verifies the HMAC-SHA256 signature and instantly dispatches the purchased digital product or private channel link directly into the buyer's Telegram chat.

Can a Telegram bot automatically add users to private channels after payment?

Yes. Upon receiving a verified 'success' webhook from FamGateway, your bot calls Telegram's createChatInviteLink API with 'member_limit = 1' and sends the single-use invite link directly to the buyer's chat, preventing link sharing or unauthorized access.

Does FamGateway support inline keyboard payment buttons in Telegram?

Yes. FamGateway's API returns a hosted checkout URL (https://famgateway.in/pay.php?order_id=...) and UPI deep link strings, which your bot can attach as an InlineKeyboardButton (url parameter) directly inside Telegram messages.

What secret key is used to verify Telegram bot webhooks?

FamGateway calculates the X-FamGateway-Signature header using HMAC-SHA256 signed with the merchant's live API Key. Your bot server computes hmac.new(API_KEY.encode(), raw_body, hashlib.sha256).hexdigest() and compares it against the signature header using hmac.compare_digest().

What happens if a customer takes longer than 5 minutes to pay?

Each dynamic UPI order generated by FamGateway has a strict validity window of 300 seconds (5 minutes). If a customer tries to pay after expiry, the checkout page informs them to regenerate a new session via the bot's /buy command to prevent transaction collisions.

Topic Cluster & Series

Related Developer Guides & Resources

View All 70+ Guides →
SMM Panels

How to Integrate FamPay UPI Payment Gateway in SMM Panels (Rental, Perfect Panel & SmartPanel)

Step-by-step developer guide to integrating FamPay UPI payment gateway in SMM panels (Rental, Perfect Panel...

Read Guide →
WooCommerce

How to Accept UPI Payments on WooCommerce Without GST or Current Account (2026 Guide)

Complete tutorial on accepting automated UPI payments on WooCommerce without GST or a commercial current ac...

Read Guide →
Discord & Gaming

Best UPI Payment Gateway for Discord Bots & Gaming Communities (Zero GST & Instant Roles)

Discover the best UPI payment gateway for Discord bots and gaming servers in India. Automate VIP role assig...

Read Guide →

Back to Homepage →

About the Platform

FamGateway is an official unit of ARYANISPE, founded by Aryan Gupta (Aryanispe) and officially registered under the Ministry of Micro, Small and Medium Enterprises (MSME), Government of India (Reg: UDYAM-BR-28-0050000).

All Systems Operational