🛡

Tech Poly VPN: VPN subscription billing in Telegram

our own product: SBP payments, key issuing in Marzban, a backup VKontakte bot

In production · in-house product
12 h
default trial access: the user gets a key on their own, with no admin involved
2 channels
sales and key issuing in Telegram, backup config delivery in VKontakte
1st of month
reconciliation statements for corporate clients are built and sent automatically

The task

A VPN is sold by subscription, and every step easily turns into manual work: issue a trial key, accept payment, check the receipt, extend the term in the panel, remind about expiry, work out the discount. A mistake at any step means either a customer without access or access without payment.

Companies add a separate pain. A director pays for employees centrally, the employees do not want to create Telegram accounts, and accounting needs a reconciliation statement based on actual charges at the end of the month. Working that out by hand in a spreadsheet is slow and error-prone.

The goal is one service that takes a customer from first contact to renewal and produces the corporate statement. In this setup the administrator only confirms the receipt.

The solution

Tech Poly VPN (the polybot bot) is the studio's own product. It is an asynchronous Telegram bot that sells subscriptions, issues VLESS keys through the Marzban panel and keeps track of money and terms itself. What is implemented:

  • sign-up with an invite code or a corporate promo code, and trial access for a few hours (12 by default);
  • SBP payment requests with manual receipt review by an administrator and an atomic confirmation that cannot credit twice;
  • creating and renewing a user in Marzban, with the request rolled back to pending if the panel does not respond;
  • prices per group, two types of promo codes (percent and days), a referral discount with a cap;
  • a corporate track: groups, a reseller role, employee keys not tied to Telegram, a charge ledger and reconciliation statements;
  • expiry notifications with per-user quiet hours;
  • a backup channel in VKontakte that issues configs when Telegram is unavailable;
  • route monitoring, daily backups, broadcasts, an admin panel, statistics and an action log;
  • a hidden /version command available to administrators only.

How it works

Marzban API: the bot as the source of truth for money

Marzban is a VPN node control panel: it creates users and serves traffic. The bot drives it through a REST API and keeps money, terms, discounts and reports itself. The subscription expiry is stored in two places: expire_date in the bot's database and expire in Marzban, which is what actually cuts off traffic. Writes always go one way: Marzban returns the actual date first, and only that date is saved to the database. So the bot never treats a subscription as active when the traffic is already shut off. In the user's note field in the panel the bot writes @username, so the administrator sees people rather than anonymous identifiers.

Idempotent payment: a row lock on confirmation

Idempotency means that repeating an operation does not change the result. It matters here because two administrators clicking “Confirm” at the same moment must not credit the payment twice. The bot opens a transaction, reads the payment with SELECT ... FOR UPDATE and checks its status under that lock. If the status is no longer “pending”, the transaction is rolled back and the administrator sees who processed the request and when. The race is excluded at the database level, not by a convention in the code.

Rollback on a panel failure, discount after issuing

If a payment is already marked as paid but Marzban did not respond, a dedicated function returns the request to pending. The discount is spent only after the panel has answered successfully, so a failed payment does not burn the accumulated discount. The price is fixed in the ledger at the date of the operation: changing a group's tariff neither rewrites history nor breaks statements that have already been issued.

Corporate groups and reconciliation statements

The reseller role lives in a separate table rather than in a user field: a client's director may not use the service at all. The reseller issues keys to employees with a label such as “Accounting, PC-2”, and no registration in the bot is needed. The charge ledger is append-only, so records are never rewritten. The bot uses it to total a period and close it, and sends statements automatically on the 1st. The manual option is the /act command.

Backup bot in VKontakte

In the extended stack a VKontakte bot runs next to the Telegram bot using LongPoll (receiving messages without a public callback address). The account is linked with a one-time code that expires, used trials are tracked, and access can be restored from an old link. Both bots share the Marzban client and retry-policy modules. The client receives a subscription link rather than a raw key, and the admin panel shows a QR code for the user.

Operations: monitoring, backups, request retries

APScheduler jobs run on Moscow time. Monitoring checks the node's TCP port every 5 minutes and alerts the administrator only after several failed checks in a row, so a single network blip does not wake anyone. A JSON backup is created daily, kept with rotation and can be uploaded to Google Drive. In the extended stack, requests to the database, Marzban, Telegram and VKontakte are retried with a growing delay, and containers shut down gracefully.

Results

  • Trial keys, purchases and renewals need no manual work except confirming the receipt.
  • A second administrator confirming the same payment does not credit it twice.
  • A panel failure does not leave a payment without a key: the request goes back to the queue.
  • A company gets keys for employees without Telegram accounts and a monthly reconciliation statement built from the charge ledger.
  • A customer who cannot reach Telegram gets a config through the backup VKontakte channel.
  • The repository has no automated tests; acceptance is manual.

Technologies and why

  • Python and aiogram 3 — the asynchronous Telegram bot: menus, payments, admin panel.
  • MariaDB (InnoDB) — subscriptions, payments, promo codes and the charge ledger; transactions and row locks protect against double crediting.
  • Marzban API — creating, renewing and subscribing VLESS users; the panel and nodes themselves are deployed separately.
  • APScheduler — expiry checks, monitoring, backups and reconciliation statements.
  • VK API — the backup config-delivery channel.
  • Google Drive (PyDrive2) — optional storage for backups.
  • Docker — the bot image with a fixed time zone; the extended stack uses a shared docker-compose file.

Status

Version 1.0.1 of 30 September 2026: a cleaned-up bot codebase that prints its version to the log on every start. The bot runs in production as our own product. The extended stack with the VKontakte bot and a shared docker-compose file is kept in a separate repository, where polybot 3.5.0 and vkbot 1.2.0 are listed. The README's list of remaining work includes automated tests, an environment template for the VKontakte bot, moving payment details out of the code into environment variables, and a dedicated schema migration tool.

Questions about this project

How can I sell VPN subscriptions through a Telegram bot with SBP payments?
The user taps “Buy”, pays through SBP (Russia's Faster Payments System) and sends the receipt. The receipt goes to the administrators with “Confirm” and “Reject” buttons. After confirmation the bot creates or renews the user in the Marzban panel and sends the key with its expiry date.
Can a payment be credited twice if two admins click at the same moment?
No. The payment status is read inside a database transaction with a row lock (SELECT ... FOR UPDATE). The second administrator sees who processed the request and when, and the subscription is not extended again.
What happens if the Marzban panel does not respond after a payment is confirmed?
The request goes back to the pending state, the money is not lost, and any accumulated discount is spent only after access has been issued successfully. The user is never left with a payment and no key.
How do I set up a corporate VPN for employees who do not use Telegram?
A company gets a corporate group with its own price and a reseller role. The reseller issues keys to employees with a readable label, and the employees do not need to register in the bot. Every charge is written to a ledger, and on the 1st of the month the bot builds a reconciliation statement from it.
What if Telegram is unavailable?
A backup channel issues configs through a VKontakte bot. The account is linked with a one-time code, and a trial period is available there too. The backup bot is part of the extended stack with a shared docker-compose file.
Where is the data stored and what happens when something fails?
Subscriptions, payments and the billing ledger live in MariaDB, and the bot runs in Docker. Backups are made daily locally and, if enabled, to Google Drive, and a final backup is taken on a clean shutdown. Route monitoring alerts the administrator only after several failed checks in a row.

Need something similar?

Tell us about the task — we'll show how we solved it and estimate the scope.