Live Dispatch
⚡ US (+1) WhatsApp OTP verified in 1.4s 🚀 @growth_agency bought 2,500 Instagram Followers ⚡ UK (+44) Telegram SMS received (Code: 839-10) ⚡ Germany (+49) Google activation completed 🔥 10,000 TikTok Views delivered in 45s ⚡ Nigeria (+234) WhatsApp OTP verified in 1.8s
Get Started arrow_forward
sell Pricing Plans
home Home / AI Automation & Developer /
smart_toy Telegram Reseller Bot • REST API • Webhooks

AI Automation & Developer API Documentation

Turnkey Code & Bot Blueprints for Virtual SMS OTP Numbers, Social Media SMM Panels & VTU utility automation.

key Get Secret API Key
smart_toy AI Automation & Developer Telegram Bot Guide v3.2

Turnkey Telegram Bot & Wholesale API Documentation

Welcome to the official developer documentation for the VansOTP wholesale platform. Build automated Telegram bot stores, SMM reseller portals, and VTU utility billing platforms with sub-50ms order creation and guaranteed carrier delivery.

Virtual SMS OTP

140+ Countries

WhatsApp, Telegram, Google, ChatGPT, Instagram, etc.

Social Marketing (SMM)

1,000+ Services

Followers, Likes, Views for TikTok, IG, YouTube, X.

Instant Refund Guarantee

100% Automated

If no SMS arrives in 15 mins, wallet is refunded automatically.

verified Key Reseller Architecture Overview

  • • No Inventory Cost: You don't need physical SIM cards or modem pools. Connect via API to our global carrier pool.
  • • Set Your Own Prices: We charge you wholesale prices (e.g. $0.60 per WhatsApp number). You charge your customers retail prices (e.g. ₦1,500 or $1.50) and keep 100% of the profit.
  • • Fully Turnkey: We provide the complete Python bot code with SQLite database, admin approval buttons, and customer payment methods.
lightbulb Fundamental Architecture

How a Telegram Bot Actually Works

Understanding the difference between the Telegram App (the Shop Window) and bot.py (the Brain/Server).

⚠️ Critical Concept to Understand:

You do NOT copy or paste the bot.py code inside the Telegram app on your phone. Telegram is only a messaging app — it does not execute Python code.

storefront

1. Telegram App = The SHOP FRONT

This is what you and your customers see on phones or desktops.

  • ✓ Create the bot handle with @BotFather
  • ✓ Get your BOT TOKEN (a password string)
  • ✓ Customers browse menus, choose countries, and receive SMS OTPs here
memory

2. bot.py = The BRAIN / ENGINE

This is the Python script that you save and run on a computer or cloud VPS server.

  • ✓ Listens for customer button clicks & orders
  • ✓ Calls VansOTP Wholesale API in real-time
  • ✓ Manages customer wallet balances in local SQLite database
  • ✓ Sends automated SMS OTP codes back to the customer

End-to-End Automation Flow:

[Customer in Telegram App]
         ⬇ (types /start, clicks "Buy WhatsApp USA")
[Your bot.py running on VPS Server / PC]
         ⬇ (checks customer balance in sqlite db, calls VansOTP API)
[VansOTP Direct Wholesale Carrier API]
         ⬇ (provisions number & reads incoming SMS in sub-50ms)
[bot.py delivers SMS OTP Code to Customer in Telegram automatically! 🎉]
monetization_on Reseller Revenue Architecture

How to Set Your Profit Margins & Earn via API

VansOTP acts as your silent wholesale provider. You decide the retail selling price to charge your customers on your Telegram bot or website.

Live Profit Simulator 100% You Keep All Profit
Wholesale Cost
Your Retail Price
Profit Per Order
Est. Monthly Profit
rocket_launch 10-Minute Beginner Blueprint

Step-by-Step Telegram Bot Build Guide

Follow these 5 simple steps to get your automated Telegram bot selling numbers and services.

1

Create Your Telegram Bot & Get Bot Token

  • Open Telegram and search for @BotFather (the official verified Telegram bot creator).
  • Send /newbot.
  • Give your bot a friendly name (e.g. Fast OTP Store) and a unique username ending in bot (e.g. FastOtpStore_bot).
  • @BotFather will give you an API Token like: 7123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ. Copy this token.
2

Get Your VansOTP Wholesale API Key

  • Log in to your Developer API Dashboard.
  • Click Generate API Key (requires 2FA Google Authenticator enabled for security).
  • Copy your secret API key (e.g. otp_live_abc123...).
  • Go to Wallet → Deposit and fund your balance (e.g. $10 or ₦5,000) so your bot can buy numbers.
3

Save and Configure `bot.py`

Open any code editor (VS Code, Notepad, etc.), create a file named bot.py, paste the code from the next section, and edit these 4 lines at the top:

BOT_TOKEN = "7123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ"  # From @BotFather
API_KEY = "otp_live_your_secret_key_here"             # From VansOTP Dashboard
BASE_URL = "https://vansotp.com/api/v1"                     # Platform API Endpoint
ADMIN_TELEGRAM_ID = 123456789                         # Your Telegram numeric ID (get from @userinfobot)
4

Option A: Run Locally on Your PC/Mac (Free)

Install dependencies and run the bot directly in your command prompt or terminal:

pip install requests python-telegram-bot
python bot.py

Note: The bot only runs while your computer is turned on and connected to the internet.

5

Option B & C: Run 24/7 on Cloud VPS (Recommended)

To keep your bot selling 24 hours a day without keeping your computer on, run it on a Linux cloud server:

# 1. Connect via SSH
ssh ubuntu@your_vps_ip

# 2. Run bot in the background (stays online after closing terminal)
nohup python3 bot.py > bot.log 2>&1 &

💡 Don't want to manage Linux servers? Get our Turnkey Managed VPS (₦5,000 / $3.50 for 3 months, or 100% FREE with the Reseller SMPP plan).

code v3.2 Full Production Release

Production Telegram Bot Script (`bot.py`)

Complete with SQLite customer database, 1-tap admin deposit approval buttons, `/fund`, `/deduct`, and automated OTP polling.

database SQLite Database
touch_app 1-Tap Admin Approval
autorenew Auto SMS Polling
campaign /broadcast Command
bot.py (Python 3.10+) Requires: python-telegram-bot requests
import time
import requests
import sqlite3
import logging
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import (
    ApplicationBuilder, CommandHandler, CallbackQueryHandler, 
    ContextTypes, MessageHandler, filters
)

logging.basicConfig(
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", 
    level=logging.INFO
)

# ==========================================
# ⚙️ CONFIGURATION SETTINGS (EDIT THESE)
# ==========================================
BOT_TOKEN = "YOUR_TELEGRAM_BOT_TOKEN_FROM_BOTFATHER"
API_KEY = "YOUR_VansOTP_API_KEY"
BASE_URL = "https://vansotp.com/api/v1"
ADMIN_TELEGRAM_ID = 123456789  # Replace with your numeric Telegram User ID (from @userinfobot)

# Retail Prices in NGN (You set these retail prices - keep 100% markup profit!)
RETAIL_PRICES = {
    "whatsapp_us": 1200.0,
    "whatsapp_uk": 1400.0,
    "telegram_us": 1000.0,
    "google_us": 800.0,
    "tiktok_views_1k": 500.0,
    "ig_followers_1k": 1800.0
}

# Your Bank Account for Manual Customer Transfers
BANK_NAME = "Opay / Moniepoint / Palmpay"
ACCOUNT_NUMBER = "1234567890"
ACCOUNT_NAME = "VansOTP Reseller Store"

# ==========================================
# 🗄️ SQLITE DATABASE INITIALIZATION
# ==========================================
def init_db():
    conn = sqlite3.connect("bot_store.db")
    cursor = conn.cursor()
    # Users table
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS users (
            telegram_id INTEGER PRIMARY KEY,
            username TEXT,
            full_name TEXT,
            balance REAL DEFAULT 0.0,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    """)
    # Deposit requests table
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS deposit_requests (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            telegram_id INTEGER,
            username TEXT,
            amount REAL,
            status TEXT DEFAULT 'pending',
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    """)
    # Transactions log (purchases & deposits)
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS transactions (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            telegram_id INTEGER,
            type TEXT,
            amount REAL,
            description TEXT,
            status TEXT,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    """)
    conn.commit()
    conn.close()

init_db()

def get_or_create_user(user):
    conn = sqlite3.connect("bot_store.db")
    cursor = conn.cursor()
    cursor.execute("SELECT balance FROM users WHERE telegram_id = ?", (user.id,))
    row = cursor.fetchone()
    if not row:
        cursor.execute(
            "INSERT INTO users (telegram_id, username, full_name, balance) VALUES (?, ?, ?, ?)",
            (user.id, user.username or "", user.full_name or "", 0.0)
        )
        conn.commit()
        balance = 0.0
    else:
        balance = row[0]
        cursor.execute(
            "UPDATE users SET username = ?, full_name = ? WHERE telegram_id = ?",
            (user.username or "", user.full_name or "", user.id)
        )
        conn.commit()
    conn.close()
    return balance

def update_user_balance(telegram_id, delta, tx_type="admin", description=""):
    conn = sqlite3.connect("bot_store.db")
    cursor = conn.cursor()
    cursor.execute("UPDATE users SET balance = balance + ? WHERE telegram_id = ?", (delta, telegram_id))
    cursor.execute(
        "INSERT INTO transactions (telegram_id, type, amount, description, status) VALUES (?, ?, ?, ?, ?)",
        (telegram_id, tx_type, delta, description, "completed")
    )
    conn.commit()
    cursor.execute("SELECT balance FROM users WHERE telegram_id = ?", (telegram_id,))
    new_bal = cursor.fetchone()[0]
    conn.close()
    return new_bal

# ==========================================
# 🚀 USER COMMANDS & HANDLERS
# ==========================================
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user = update.effective_user
    bal = get_or_create_user(user)
    keyboard = [
        [InlineKeyboardButton("📱 Buy WhatsApp USA (₦1,200)", callback_data="buy:whatsapp:US:1200")],
        [InlineKeyboardButton("✈️ Buy Telegram USA (₦1,000)", callback_data="buy:telegram:US:1000")],
        [InlineKeyboardButton("🔍 Buy Google/Gmail USA (₦800)", callback_data="buy:google:US:800")],
        [InlineKeyboardButton("💳 Fund Wallet", callback_data="menu_deposit"), InlineKeyboardButton("💰 My Balance", callback_data="check_bal")],
        [InlineKeyboardButton("📜 My Transactions", callback_data="my_tx"), InlineKeyboardButton("📞 Support", url="https://t.me/YourSupportUsername")]
    ]
    await update.message.reply_text(
        f"👋 Welcome {user.first_name} to **VansOTP Automated Store**!\n\n"
        f"💰 Your Balance: **₦{bal:,.2f}**\n\n"
        f"Select a service below for instant delivery:",
        reply_markup=InlineKeyboardMarkup(keyboard),
        parse_mode="Markdown"
    )

async def handle_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
    query = update.callback_query
    await query.answer()
    user = query.from_user
    data = query.data

    # Check Balance
    if data == "check_bal":
        bal = get_or_create_user(user)
        await query.message.reply_text(f"💳 Your Wallet Balance is: **₦{bal:,.2f}**", parse_mode="Markdown")

    # View User Transactions
    elif data == "my_tx":
        conn = sqlite3.connect("bot_store.db")
        c = conn.cursor()
        c.execute("SELECT type, amount, description, created_at FROM transactions WHERE telegram_id = ? ORDER BY id DESC LIMIT 5", (user.id,))
        rows = c.fetchall()
        conn.close()
        if not rows:
            await query.message.reply_text("ℹ️ You have no transaction history yet.")
            return
        tx_text = "📜 **Your Recent Transactions:**\n\n"
        for r in rows:
            sign = "+" if r[1] > 0 else ""
            tx_text += f"• `{r[3][:16]}` | {r[0].upper()} | **{sign}₦{r[1]:,.2f}**\n  _{r[2]}_\n\n"
        await query.message.reply_text(tx_text, parse_mode="Markdown")

    # Deposit Menu
    elif data == "menu_deposit":
        keyboard = [
            [InlineKeyboardButton("₦1,000", callback_data="req_dep:1000"), InlineKeyboardButton("₦2,000", callback_data="req_dep:2000")],
            [InlineKeyboardButton("₦5,000", callback_data="req_dep:5000"), InlineKeyboardButton("₦10,000", callback_data="req_dep:10000")]
        ]
        await query.message.reply_text(
            f"🏦 **BANK TRANSFER DEPOSIT INSTRUCTIONS**\n\n"
            f"• Bank: **{BANK_NAME}**\n"
            f"• Account Number: `{ACCOUNT_NUMBER}`\n"
            f"• Account Name: **{ACCOUNT_NAME}**\n\n"
            f"Select the amount you want to transfer below:",
            reply_markup=InlineKeyboardMarkup(keyboard),
            parse_mode="Markdown"
        )

    # Customer Logs Deposit Request
    elif data.startswith("req_dep:"):
        amount = float(data.split(":")[1])
        conn = sqlite3.connect("bot_store.db")
        cursor = conn.cursor()
        cursor.execute(
            "INSERT INTO deposit_requests (telegram_id, username, amount) VALUES (?, ?, ?)",
            (user.id, user.username or user.first_name, amount)
        )
        req_id = cursor.lastrowid
        conn.commit()
        conn.close()

        await query.message.reply_text(
            f"✅ **Deposit Request #{req_id} Submitted!**\n\n"
            f"Please transfer **₦{amount:,.2f}** to `{ACCOUNT_NUMBER}` ({BANK_NAME}).\n"
            f"Our admin is verifying your transfer and will approve your balance shortly!",
            parse_mode="Markdown"
        )

        # Send 1-Tap Approval + Rejection to Admin
        admin_markup = InlineKeyboardMarkup([
            [InlineKeyboardButton(f"✅ Approve ₦{amount:,.0f}", callback_data=f"adm_app:{req_id}:{user.id}:{amount}")],
            [InlineKeyboardButton("❌ Reject", callback_data=f"adm_rej:{req_id}:{user.id}:{amount}")]
        ])
        await context.bot.send_message(
            chat_id=ADMIN_TELEGRAM_ID,
            text=f"🔔 **NEW DEPOSIT ALERT!**\n\n"
                 f"👤 User: {user.full_name} (@{user.username})\n"
                 f"🆔 Telegram ID: `{user.id}`\n"
                 f"💰 Amount: **₦{amount:,.2f}**\n"
                 f"📋 Request ID: `#{req_id}`\n\n"
                 f"Tap below to approve or reject:",
            reply_markup=admin_markup,
            parse_mode="Markdown"
        )

    # Admin 1-Tap APPROVE Handler
    elif data.startswith("adm_app:"):
        if user.id != ADMIN_TELEGRAM_ID:
            await query.answer("❌ Unauthorized!", show_alert=True)
            return
        _, req_id, cust_id, amt = data.split(":")
        cust_id, amt, req_id = int(cust_id), float(amt), int(req_id)
        
        conn = sqlite3.connect("bot_store.db")
        c = conn.cursor()
        c.execute("UPDATE deposit_requests SET status = 'approved' WHERE id = ?", (req_id,))
        conn.commit()
        conn.close()

        new_bal = update_user_balance(cust_id, amt, tx_type="deposit", description=f"Deposit Request #{req_id}")
        await query.edit_message_text(f"✅ Approved Request #{req_id}. User `{cust_id}` credited ₦{amt:,.2f}. New balance: ₦{new_bal:,.2f}")
        
        try:
            await context.bot.send_message(
                chat_id=cust_id,
                text=f"🎉 **PAYMENT CONFIRMED & CREDITED!**\n\n"
                     f"₦{amt:,.2f} has been added to your wallet!\n"
                     f"💰 New Balance: **₦{new_bal:,.2f}**\n\n"
                     f"You can now order OTP numbers using /start.",
                parse_mode="Markdown"
            )
        except Exception as e:
            logging.error(f"Failed to notify user {cust_id}: {e}")

    # Admin 1-Tap REJECT Handler
    elif data.startswith("adm_rej:"):
        if user.id != ADMIN_TELEGRAM_ID:
            await query.answer("❌ Unauthorized!", show_alert=True)
            return
        _, req_id, cust_id, amt = data.split(":")
        cust_id, amt, req_id = int(cust_id), float(amt), int(req_id)
        
        conn = sqlite3.connect("bot_store.db")
        c = conn.cursor()
        c.execute("UPDATE deposit_requests SET status = 'rejected' WHERE id = ?", (req_id,))
        conn.commit()
        conn.close()

        await query.edit_message_text(f"❌ Rejected Deposit Request #{req_id} (₦{amt:,.2f}) for user `{cust_id}`.")
        try:
            await context.bot.send_message(
                chat_id=cust_id,
                text=f"❌ **Deposit Request #{req_id} Rejected**\n\n"
                     f"We could not verify your transfer of ₦{amt:,.2f}.\n"
                     f"If you made this payment, please contact support.",
                parse_mode="Markdown"
            )
        except Exception as e:
            pass

    # Order Virtual Number Handler
    elif data.startswith("buy:"):
        _, service_slug, country_iso, price = data.split(":")
        price = float(price)
        bal = get_or_create_user(user)

        if bal < price:
            await query.message.reply_text(
                f"❌ Insufficient balance (₦{bal:,.2f}). This number costs ₦{price:,.2f}.\n"
                f"Please click 'Fund Wallet' to top up.",
                parse_mode="Markdown"
            )
            return

        # Deduct wallet & call VansOTP Wholesale API
        update_user_balance(user.id, -price, tx_type="purchase", description=f"{service_slug.upper()} ({country_iso}) Number")
        msg = await query.message.reply_text("🔄 Ordering virtual number from wholesale pool...")

        headers = {"X-API-KEY": API_KEY, "Accept": "application/json"}
        resp = requests.post(f"{BASE_URL}/numbers/order", json={"service_slug": service_slug, "country_iso": country_iso}, headers=headers)
        
        if resp.status_code != 200 or not resp.json().get("success"):
            update_user_balance(user.id, price, tx_type="refund", description=f"Refund: {service_slug} unavailable")
            await msg.edit_text("❌ Carrier pool busy or no numbers available. Wallet refunded 100%.")
            return

        order_data = resp.json()["data"]["order"]
        order_id = order_data["order_id"]
        phone_num = order_data["phone_number"]

        await msg.edit_text(
            f"🎉 **NUMBER READY!**\n\n"
            f"📞 Number: `{phone_num}`\n"
            f"📱 Service: **{service_slug.upper()} ({country_iso})**\n\n"
            f"⏳ Waiting for incoming SMS code (will auto-poll for 15 minutes)...",
            parse_mode="Markdown"
        )

        # Auto-poll for SMS OTP code (up to 5 minutes / 60 iterations of 5s)
        for _ in range(60):
            time.sleep(5)
            chk = requests.get(f"{BASE_URL}/numbers/{order_id}/sms", headers=headers)
            if chk.status_code == 200:
                sms_res = chk.json()["data"]
                if sms_res.get("sms_received"):
                    code = sms_res.get("latest_code")
                    full_text = sms_res.get("latest_message")
                    await query.message.reply_text(
                        f"📬 **YOUR VERIFICATION CODE HAS ARRIVED!**\n\n"
                        f"🔑 Code: `{code}`\n\n"
                        f"Full Message:\n_{full_text}_",
                        parse_mode="Markdown"
                    )
                    return

        # Timeout: Auto-cancel and auto-refund
        requests.post(f"{BASE_URL}/numbers/{order_id}/cancel", headers=headers)
        update_user_balance(user.id, price, tx_type="refund", description=f"Refund: OTP Timeout on {phone_num}")
        await query.message.reply_text("⚠️ No SMS received within timeout. Your wallet has been 100% refunded.")

# ==========================================
# 👑 ADMIN COMMANDS (Full Control Panel)
# ==========================================
async def admin_fund(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Credit a user's wallet: /fund  """
    if update.effective_user.id != ADMIN_TELEGRAM_ID: return
    try:
        target_id, amount = int(context.args[0]), float(context.args[1])
        new_bal = update_user_balance(target_id, amount, tx_type="admin_credit", description="Manual Admin Credit")
        await update.message.reply_text(f"✅ Credited `{target_id}` with ₦{amount:,.2f}. New Balance: ₦{new_bal:,.2f}")
        await context.bot.send_message(chat_id=target_id, text=f"🎉 Admin credited your wallet with ₦{amount:,.2f}!\n💰 Balance: ₦{new_bal:,.2f}")
    except Exception as e:
        await update.message.reply_text("Usage: `/fund  `\nExample: `/fund 123456789 5000`", parse_mode="Markdown")

async def admin_deduct(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Deduct from a user's wallet: /deduct  """
    if update.effective_user.id != ADMIN_TELEGRAM_ID: return
    try:
        target_id, amount = int(context.args[0]), float(context.args[1])
        new_bal = update_user_balance(target_id, -amount, tx_type="admin_debit", description="Manual Admin Debit")
        await update.message.reply_text(f"✅ Deducted ₦{amount:,.2f} from `{target_id}`. New Balance: ₦{new_bal:,.2f}")
        await context.bot.send_message(chat_id=target_id, text=f"⚠️ Admin deducted ₦{amount:,.2f} from your wallet.\n💰 Balance: ₦{new_bal:,.2f}")
    except Exception as e:
        await update.message.reply_text("Usage: `/deduct  `\nExample: `/deduct 123456789 1000`", parse_mode="Markdown")

async def admin_transactions(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """View recent transactions: /transactions"""
    if update.effective_user.id != ADMIN_TELEGRAM_ID: return
    conn = sqlite3.connect("bot_store.db")
    c = conn.cursor()
    c.execute("SELECT id, telegram_id, type, amount, description, created_at FROM transactions ORDER BY id DESC LIMIT 10")
    rows = c.fetchall()
    conn.close()
    if not rows:
        await update.message.reply_text("No transactions found.")
        return
    text = "📊 **Recent 10 Transactions:**\n\n"
    for r in rows:
        sign = "+" if r[3] > 0 else ""
        text += f"• `#{r[0]}` | User: `{r[1]}` | {r[2].upper()}\n  Amount: **{sign}₦{r[3]:,.2f}** | _{r[4]}_\n  Date: `{r[5][:16]}`\n\n"
    await update.message.reply_text(text, parse_mode="Markdown")

async def admin_users(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """View registered users: /users"""
    if update.effective_user.id != ADMIN_TELEGRAM_ID: return
    conn = sqlite3.connect("bot_store.db")
    c = conn.cursor()
    c.execute("SELECT telegram_id, username, full_name, balance FROM users ORDER BY balance DESC LIMIT 15")
    rows = c.fetchall()
    c.execute("SELECT COUNT(*), SUM(balance) FROM users")
    total_users, total_bal = c.fetchone()
    conn.close()
    
    text = f"👥 **Bot User Directory:**\nTotal Users: **{total_users}** | Total User Balances: **₦{(total_bal or 0):,.2f}**\n\n"
    for r in rows:
        text += f"• `{r[0]}` | @{r[1] or 'NoUser'} ({r[2]})\n  💰 Balance: **₦{r[3]:,.2f}**\n\n"
    await update.message.reply_text(text, parse_mode="Markdown")

async def admin_broadcast(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Broadcast announcement to all users: /broadcast """
    if update.effective_user.id != ADMIN_TELEGRAM_ID: return
    msg = " ".join(context.args)
    if not msg:
        await update.message.reply_text("Usage: `/broadcast `", parse_mode="Markdown")
        return
    conn = sqlite3.connect("bot_store.db")
    c = conn.cursor()
    c.execute("SELECT telegram_id FROM users")
    users = c.fetchall()
    conn.close()
    
    sent = 0
    for (u_id,) in users:
        try:
            await context.bot.send_message(chat_id=u_id, text=f"📢 **ANNOUNCEMENT:**\n\n{msg}", parse_mode="Markdown")
            sent += 1
        except Exception:
            pass
    await update.message.reply_text(f"✅ Broadcast sent to **{sent}/{len(users)}** users!")

# ==========================================
# 🏁 MAIN ENTRY POINT
# ==========================================
if __name__ == "__main__":
    print("🤖 VansOTP Reseller Telegram Bot Engine is running...")
    app = ApplicationBuilder().token(BOT_TOKEN).build()
    
    # User Handlers
    app.add_handler(CommandHandler("start", start))
    app.add_handler(CallbackQueryHandler(handle_callback))
    
    # Admin Handlers
    app.add_handler(CommandHandler("fund", admin_fund))
    app.add_handler(CommandHandler("deduct", admin_deduct))
    app.add_handler(CommandHandler("transactions", admin_transactions))
    app.add_handler(CommandHandler("users", admin_users))
    app.add_handler(CommandHandler("broadcast", admin_broadcast))
    
    app.run_polling()
cloud_done Zero-Maintenance Hosting

24/7 Turnkey Managed Cloud VPS

Keep your Telegram reseller bot selling 24/7 on dedicated cloud servers with 99.9% uptime, auto-restarts, and zero coding required.

Managed Server

3-Month Turnkey Bot Cloud VPS

₦5,000 ($3.50) / 90 Days
workspace_premium

PRO RESELLER PERK: This 3-Month Managed VPS is included 100% FREE when you subscribe to the Reseller SMPP Plan!

✓ 100% Zero Terminal We setup your script and connect your token.
✓ Auto-Restart Daemon If the bot crashes, it restarts in seconds.
✓ Live Expiry Countdown Track remaining days in your dashboard.
language Website & SMM Panel Integration

Connecting Your Website or SMM Panel to VansOTP API

Step-by-step guide for resellers who want to integrate our Virtual Number, OTP, and Social SMM services directly into their own websites, apps, or SMM reseller panels.

key

What is the API Key and How Does It Work?

Think of your Secret API Key as your website's password identity card to our server. Every time your website needs to order a virtual number or check an OTP, it sends this key along with the request. Our server reads it, verifies your wallet has funds, fulfills the order, deducts the cost — and replies with the result in under 1 second.

person
Step 1: Your Customer
Visits your website, chooses a virtual number or service, clicks "Buy"
dns
Step 2: Your Website
Sends the request to our API with your Secret Key — automatically in the background
check_circle
Step 3: VansOTP
Fulfills the order instantly, replies with phone number + OTP code, deducts wholesale cost from your wallet

4-Step Website Setup Plan

1

Fund Your Wallet on VansOTP

Before any API call can succeed, your wallet must have a balance. Log in → Dashboard → Wallet → Add Funds (Paystack / Bank Transfer / Crypto). This balance is your wholesale stock — you will charge your customers a higher retail price and keep the difference as profit.

2

Generate Your Secret API Key

Go to Dashboard → Developer API. Enable 2FA (Google Authenticator), then click "Generate API Key". Copy the key — it looks like:

otp_live_xxxxxxxxxxxxxxxxxxxxxxxx

⚠️ Keep this key secret! Never share it publicly or put it in front-end JavaScript.

3

Add the API Key to Your Website's Config

On your own website/server, store the key in a safe config file — never in public HTML or JavaScript. Common methods:

Option A — PHP (.env or config file)
VansOTP_API_KEY=otp_live_xxxxxxxxxxxxxxxxxxxxxxxx
VansOTP_BASE_URL=https://vansotp.com/api/v1
Option B — Node.js (.env file)
VansOTP_API_KEY=otp_live_xxxxxxxxxxxxxxxxxxxxxxxx
VansOTP_BASE_URL=https://vansotp.com/api/v1
Option C — Ready-made SMM Panel Script

Go to your SMM panel admin → Settings → API Providers → Add Provider. Fill in the API URL and paste your key. Done!

4

Test With a Live API Call

Make your first API call to confirm everything is connected. The quickest test is checking your wallet balance:

curl -H "X-API-KEY: otp_live_xxxxx" \
     https://vansotp.com/api/v1/user/balance

✅ If you get back a JSON with your balance, your website is now fully connected to VansOTP!

What Can You Sell on Your Website Using This API?

sim_card
Virtual Phone Numbers
WhatsApp, Telegram, Gmail, Facebook verification numbers from 180+ countries
trending_up
Social Media SMM
Instagram followers, TikTok views, YouTube likes, Twitter/X followers
bolt
VTU Utilities
Airtime top-up, data bundles, electricity tokens, cable TV subscriptions
php PHP Backend Integration

PHP Website Integration (Laravel / Plain PHP)

Complete production-ready PHP code to buy virtual numbers, check OTP codes, and list services from your own website backend.

VansOTP_client.php Plain PHP 7.4+ / Laravel compatible
<?php
/**
 * ============================================================
 * VansOTP PHP Integration Client
 * Drop this file into your project and use the functions below
 * ============================================================
 */

define('VansOTP_API_KEY', 'otp_live_YOUR_SECRET_KEY_HERE');
define('VansOTP_BASE_URL', 'https://vansotp.com/api/v1');

/**
 * Make authenticated GET or POST request to VansOTP API
 */
function VansOTP_request(string $endpoint, array $body = [], string $method = 'GET'): array {
    $url = VansOTP_BASE_URL . '/' . ltrim($endpoint, '/');
    $ch  = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 30,
        CURLOPT_HTTPHEADER     => [
            'X-API-KEY: ' . VansOTP_API_KEY,
            'Accept: application/json',
            'Content-Type: application/json',
        ],
    ]);
    if ($method === 'POST') {
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
    }
    $raw  = curl_exec($ch);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    return ['code' => $code, 'data' => json_decode($raw, true)];
}

// ─────────────────────────────────────────────
// 1. CHECK YOUR WALLET BALANCE
// ─────────────────────────────────────────────
function VansOTP_get_balance(): float {
    $res = VansOTP_request('/user/balance');
    return (float) ($res['data']['balance'] ?? 0);
}

// ─────────────────────────────────────────────
// 2. LIST ALL AVAILABLE SERVICES (Virtual Numbers)
// ─────────────────────────────────────────────
function VansOTP_get_services(): array {
    $res = VansOTP_request('/numbers/services');
    return $res['data']['services'] ?? [];
}

// ─────────────────────────────────────────────
// 3. ORDER A VIRTUAL NUMBER (e.g. for WhatsApp US)
// ─────────────────────────────────────────────
function VansOTP_order_number(string $country_iso, string $service_slug): array {
    $res = VansOTP_request('/numbers/order', [
        'country_iso'   => $country_iso,   // e.g. "US", "NG", "GB"
        'service_slug'  => $service_slug,  // e.g. "whatsapp", "telegram", "google"
    ], 'POST');
    return $res['data']['order'] ?? [];
}

// ─────────────────────────────────────────────
// 4. CHECK FOR RECEIVED SMS / OTP CODE
// ─────────────────────────────────────────────
function VansOTP_check_sms(string $order_id): array {
    $res = VansOTP_request("/numbers/{$order_id}/sms");
    return $res['data'] ?? [];
}

// ─────────────────────────────────────────────
// 5. CANCEL A NUMBER (triggers auto-refund)
// ─────────────────────────────────────────────
function VansOTP_cancel_number(string $order_id): bool {
    $res = VansOTP_request("/numbers/{$order_id}/cancel", [], 'POST');
    return ($res['code'] === 200);
}

// ─────────────────────────────────────────────
// 6. ORDER SOCIAL SMM SERVICE
// ─────────────────────────────────────────────
function VansOTP_order_social(int $service_id, string $link, int $quantity): array {
    $res = VansOTP_request('/social/order', [
        'service_id' => $service_id,
        'link'       => $link,      // e.g. Instagram profile URL
        'quantity'   => $quantity,  // e.g. 1000 followers
    ], 'POST');
    return $res['data'] ?? [];
}


// ─────────────────────────────────────────────
// ✅ EXAMPLE USAGE ON YOUR WEBSITE PAGE:
// ─────────────────────────────────────────────
// 1. Show balance:
$balance = VansOTP_get_balance();
echo "My Reseller Wallet: $" . number_format($balance / 100, 2);

// 2. Order a WhatsApp number for a customer:
$order = VansOTP_order_number('US', 'whatsapp');
$phone  = $order['phone_number'] ?? '';
$order_id = $order['id'] ?? '';
echo "Customer's number: " . $phone;

// 3. Poll for the OTP code (check every 5 seconds):
for ($i = 0; $i < 60; $i++) {
    sleep(5);
    $sms = VansOTP_check_sms($order_id);
    if (!empty($sms['latest_code'])) {
        echo "OTP Code: " . $sms['latest_code'];
        break;
    }
}

// 4. If no OTP arrived, cancel and auto-refund customer:
VansOTP_cancel_number($order_id);
echo "No OTP — number cancelled and customer refunded.";

Laravel-Specific Setup (if you use Laravel)

For Laravel apps, add to your .env file, then use Laravel's built-in Http facade:

# .env
VansOTP_API_KEY=otp_live_xxxxxxxxxxxxxxxxxxxxxxxx
VansOTP_BASE_URL=https://vansotp.com/api/v1

# Then in your Controller:
use Illuminate\Support\Facades\Http;

$response = Http::withHeaders([
    'X-API-KEY' => env('VansOTP_API_KEY'),
    'Accept'    => 'application/json',
])->post(env('VansOTP_BASE_URL') . '/numbers/order', [
    'country_iso'  => 'US',
    'service_slug' => 'whatsapp',
]);

$order = $response->json('order');
echo "Phone: " . $order['phone_number'];
javascript Node.js / React Integration

Node.js / Express / React Website Integration

Complete Node.js backend client for Express API servers and Next.js/React apps. Use this on your server-side code only — never expose your API key in front-end browser code.

warning

⚠️ IMPORTANT — Never Put Your API Key in React/Vue/Angular Front-End Code!

Your API key must only exist in your server-side Node.js/Express code — not in any front-end JavaScript that runs in the browser. Anyone who sees your front-end code can steal the key and drain your wallet. Always call VansOTP from your backend, never directly from React components.

VansOTP.js (Node.js / Express Backend) npm install axios dotenv
// ============================================================
// VansOTP Node.js Integration Client
// Save as: lib/VansOTP.js   (SERVER SIDE ONLY!)
// ============================================================
require('dotenv').config();
const axios = require('axios');

const API_KEY  = process.env.VansOTP_API_KEY;    // from .env
const BASE_URL = process.env.VansOTP_BASE_URL || 'https://vansotp.com/api/v1';

const client = axios.create({
    baseURL: BASE_URL,
    timeout: 30000,
    headers: {
        'X-API-KEY': API_KEY,
        'Accept':    'application/json',
        'Content-Type': 'application/json',
    },
});

// ─────────────────────────────────────────────
// 1. CHECK WALLET BALANCE
// ─────────────────────────────────────────────
async function getBalance() {
    const { data } = await client.get('/user/balance');
    return data.balance; // in minor units (cents/kobo)
}

// ─────────────────────────────────────────────
// 2. GET AVAILABLE SERVICES (Virtual Numbers)
// ─────────────────────────────────────────────
async function getServices() {
    const { data } = await client.get('/numbers/services');
    return data.services;
}

// ─────────────────────────────────────────────
// 3. ORDER A VIRTUAL NUMBER
// ─────────────────────────────────────────────
async function orderNumber(countryIso, serviceSlug) {
    // countryIso: 'US', 'NG', 'GB', 'IN' etc.
    // serviceSlug: 'whatsapp', 'telegram', 'google', 'facebook' etc.
    const { data } = await client.post('/numbers/order', {
        country_iso:  countryIso,
        service_slug: serviceSlug,
    });
    return data.order; // { id, phone_number, expires_at, ... }
}

// ─────────────────────────────────────────────
// 4. POLL FOR SMS OTP CODE
// ─────────────────────────────────────────────
async function checkSms(orderId) {
    const { data } = await client.get(`/numbers/${orderId}/sms`);
    return data; // { sms_received: bool, latest_code: '123456', latest_message: '...' }
}

// ─────────────────────────────────────────────
// 5. CANCEL NUMBER (auto-refunds wallet)
// ─────────────────────────────────────────────
async function cancelNumber(orderId) {
    await client.post(`/numbers/${orderId}/cancel`);
    return true;
}

// ─────────────────────────────────────────────
// 6. WAIT FOR OTP (helper — polls until code arrives or timeout)
// ─────────────────────────────────────────────
async function waitForOtp(orderId, timeoutSeconds = 300) {
    const deadline = Date.now() + timeoutSeconds * 1000;
    while (Date.now() < deadline) {
        await new Promise(r => setTimeout(r, 5000)); // wait 5s
        const sms = await checkSms(orderId);
        if (sms.sms_received && sms.latest_code) {
            return sms.latest_code;
        }
    }
    // Timeout — cancel and get refund
    await cancelNumber(orderId);
    return null;
}

module.exports = { getBalance, getServices, orderNumber, checkSms, cancelNumber, waitForOtp };


// ─────────────────────────────────────────────
// ✅ EXAMPLE: Express.js Route on Your Website
// ─────────────────────────────────────────────
// File: routes/numbers.js
const express  = require('express');
const router   = express.Router();
const otp      = require('../lib/VansOTP');

// Your customer clicks "Buy WhatsApp Number" on your website:
router.post('/buy-number', async (req, res) => {
    const { country, service } = req.body;
    try {
        const order = await otp.orderNumber(country, service);
        const code  = await otp.waitForOtp(order.id, 300);
        if (code) {
            res.json({ success: true, phone: order.phone_number, otp: code });
        } else {
            res.json({ success: false, message: 'No OTP received. Refund issued.' });
        }
    } catch (err) {
        res.status(500).json({ error: err.message });
    }
});

module.exports = router;


// ─────────────────────────────────────────────
// .env file for your Node.js server:
// ─────────────────────────────────────────────
// VansOTP_API_KEY=otp_live_xxxxxxxxxxxxxxxxxxxxxxxx
// VansOTP_BASE_URL=https://vansotp.com/api/v1

Using a Ready-Made SMM Panel Script? (No Coding Needed)

If you purchased or are using a ready-made SMM reseller panel (like Perfect Panel, SMMKing, or a custom PHP SMM script), integration is even simpler — no coding required:

1

Log in to your SMM Panel Admin Dashboard

2

Go to Settings → API Providers → Add New Provider

3

Enter API URL: https://vansotp.com/api/v1

4

Enter API Key: paste your otp_live_xxxx key from your dashboard

✓

Click Save — your panel now imports and sells our services automatically at your markup prices!

payments Monetization Options

How Customers Pay You on Telegram & Web

Choose the payment method that best fits your customers. All revenue goes 100% to your accounts.

1

Manual Bank Transfer + 1-Tap Admin Approval (Included in bot.py)

Zero Transaction Fees

The customer sends money to your bank account (Opay, Moniepoint, GTBank, etc.) and clicks their deposit amount in the bot. You receive an instant Telegram alert with an [Approve] button. When you tap Approve, the customer's bot balance is credited instantly!

2

Automated Card & USSD Gateway (Paystack / Flutterwave)

100% Hands-Free

Your bot generates a dynamic Paystack payment checkout link. Once the customer completes payment with debit card or USSD, the webhook automatically credits their bot balance without any admin action.

3

Telegram Stars (Native In-App Currency)

Native In-App

Customers pay using Apple Pay, Google Pay, or Telegram Stars directly inside the chat interface without leaving Telegram.

4

Automated Cryptocurrency (USDT TRC-20 / TON)

Global Borderless

Ideal for international customers. Provide your USDT address or use CryptoPay bot for instant block-explorer payment verification.

admin_panel_settings Bot Administration

Admin Deposit Approvals & Management

How you (the bot owner) manually credit users, approve transfers, and manage your customer database.

touch_app 1. One-Tap Inline Approval Buttons (Easiest)

Whenever a customer sends a deposit request in the bot, bot.py forwards an alert directly to your Telegram chat with two buttons:

[✅ Approve ₦5,000] [❌ Reject]

Simply tap Approve. The customer is credited immediately in SQLite, and the bot sends them a celebratory notification.

terminal 2. Telegram Admin Slash Commands

/fund <USER_TELEGRAM_ID> <AMOUNT>

Example: /fund 987654321 5000 — Credits ₦5,000 to user wallet.

/deduct <USER_TELEGRAM_ID> <AMOUNT>

Example: /deduct 987654321 1000 — Deducts ₦1,000 from user.

/transactions

View the last 10 purchases, deposits, and refunds across all users.

/users

List all bot users, their usernames, and their current wallet balances.

/broadcast <MESSAGE>

Send a live marketing push notification / announcement to all bot users.

key REST API Specification

Authentication & Base URL

All endpoints require standard HTTPS and API Key authentication.

Base API URL https://vansotp.com/api/v1
Required Headers X-API-KEY: otp_live_...
Sample cURL Request
curl -X GET "https://vansotp.com/api/v1/user/balance" \
  -H "X-API-KEY: YOUR_SECRET_API_KEY" \
  -H "Accept: application/json"
GET /api/v1/user/balance

Check Wallet Balance & Tier

Returns your live USD balance, formatted string, and discount percentages.

JSON Response (200 OK)
{
  "success": true,
  "balance_usd": 45.50,
  "balance_minor": 4550,
  "currency": "USD",
  "formatted_balance": "$45.50",
  "discounts": {
    "numbers_discount_percent": 20,
    "social_discount_percent": 15,
    "vtu_discount_percent": 2
  }
}
GET /api/v1/countries & /api/v1/services

Countries & Services Catalog

Fetch carrier country codes, flags, service slugs, and real-time inventory.

GET /api/v1/countries Response
{
  "success": true,
  "data": [
    { "id": 1, "name": "United States", "iso": "US", "code": "+1" },
    { "id": 2, "name": "United Kingdom", "iso": "GB", "code": "+44" },
    { "id": 3, "name": "Nigeria", "iso": "NG", "code": "+234" }
  ]
}
POST /api/v1/numbers/order

Order Virtual SMS OTP Number

Instant provisioning of real carrier number. Debits wholesale cost from wallet.

Request Body (JSON)
{
  "country_iso": "US",
  "service_slug": "whatsapp"
}
Response (200 OK)
{
  "success": true,
  "message": "Number provisioned successfully",
  "data": {
    "order": {
      "order_id": "8c459fa8-9b88-4bf6-90da-8d769dfb40d1",
      "phone_number": "+12025550198",
      "country": "United States",
      "service": "WhatsApp",
      "expires_at": "2026-10-05T14:45:00Z"
    }
  }
}
GET /api/v1/numbers/{order_id}/sms

Poll for Inbound SMS Verification Code

Call this every 3-5 seconds to check if the SMS OTP code has arrived.

Response When SMS Arrived (200 OK)
{
  "success": true,
  "data": {
    "sms_received": true,
    "latest_code": "847291",
    "latest_message": "Your WhatsApp code is 847-291. Do not share this code.",
    "status": "completed"
  }
}
POST /api/v1/numbers/{order_id}/cancel

Cancel Order & 100% Instant Refund

If no SMS was received, release the number and instantly credit your wallet.

Response (200 OK)
{
  "success": true,
  "message": "Order cancelled successfully. 100% refunded to wallet.",
  "data": {
    "refund_amount_usd": 0.60,
    "new_balance_usd": 45.50
  }
}
POST /api/v1/social/order

Social Media SMM Boost API

Create follower, like, view, and comment orders for Instagram, TikTok, YouTube, and X.

POST Request Body (JSON)
{
  "service_id": 14,
  "target_url": "https://instagram.com/your_profile",
  "quantity": 1000
}
terminal Multi-Language Integration

Code SDKs & Website Integration

Connect VansOTP API directly from Python, Node.js, PHP, or cURL on your web servers.

import requests

API_KEY = "otp_live_your_api_key"
headers = {"X-API-KEY": API_KEY, "Accept": "application/json"}

# 1. Order Number
res = requests.post("https://vansotp.com/api/v1/numbers/order", json={"country_iso": "US", "service_slug": "whatsapp"}, headers=headers)
order = res.json()["data"]["order"]
print(f"Number provisioned: {order['phone_number']}")
const axios = require('axios');

const client = axios.create({
    baseURL: 'https://vansotp.com/api/v1',
    headers: { 'X-API-KEY': 'otp_live_your_api_key' }
});

async function orderNumber() {
    const res = await client.post('/numbers/order', { country_iso: 'US', service_slug: 'whatsapp' });
    console.log('Phone Number:', res.data.data.order.phone_number);
}
orderNumber();
<?php
$ch = curl_init("https://vansotp.com/api/v1/numbers/order");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode(['country_iso' => 'US', 'service_slug' => 'whatsapp']),
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json',
        'X-API-KEY: otp_live_your_api_key'
    ]
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
echo "Phone Number: " . $response['data']['order']['phone_number'];
curl -X POST "https://vansotp.com/api/v1/numbers/order" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: YOUR_API_KEY" \
  -d '{"country_iso":"US","service_slug":"whatsapp"}'
help Troubleshooting

HTTP Status Codes, Rate Limits & FAQ

Complete reference for standard HTTP response codes and platform guarantees.

Rate Limits & Concurrency

Standard plans allow 10 requests per second. Reseller SMPP Enterprise accounts have unlimited concurrent connections (up to 100 req/sec).

HTTP 402 Insufficient Balance

Your account wallet balance is too low for the wholesale cost. Top up via Dashboard → Wallet → Deposit.

HTTP 404 No Numbers Available

The selected carrier pool is temporarily out of fresh stock. Retry after 60 seconds or choose another country.