EN

Selling VPN subscriptions through a Telegram bot for USDT: setup step by step

Selling VPN subscriptions through a Telegram bot for USDT

Many VPN services sell through Telegram: the customer is already in the chat, so the purchase should happen there too. This guide shows how to launch a ready-made bot on your server: the customer picks a plan, pays in USDT on TRC20, ERC20 or BEP20 or in another coin, and the bot creates or extends the user in your Marzban, 3x-ui or Remnawave panel and sends the subscription link to the same chat. You don't run a wallet or a blockchain node — the money lands in your SnapEX Pay merchant balance.

How a sale goes

customer → /start → taps a plan
bot → SnapEX Pay: invoice → "Pay" button
customer → pays on the SnapEX Pay page
SnapEX Pay → signed webhook → bot
bot → GET invoice from the API → paid?
bot → panel: create or extend user
bot → customer: subscription link
  1. /start shows your plans as buttons. Each tap creates a SnapEX Pay invoice and the bot replies with a Pay button.
  2. The customer chooses a coin on the payment page and pays. When the invoice is paid, SnapEX Pay sends a signed webhook to the bot.
  3. The bot checks the signature, asks the SnapEX Pay API for the invoice and provisions the user only if the status there is paid and the invoice belongs to an order the bot created.

The panel username is tg<telegram_id>, so each Telegram account maps to exactly one VPN user.

What you need

  • A bot token from @BotFather.
  • Admin access to a Marzban, 3x-ui (MHSanaei) or Remnawave (2.x or 3.x) panel. Without a panel the bot can only take payments (PANEL_TYPE=none), and you hand out access yourself.
  • A server with Docker Compose (or Python 3.12).
  • A public HTTPS address for the webhook: SnapEX Pay delivers webhooks only to https:// URLs.
  • A SnapEX Pay merchant account: sign up at pay.snapex.pro.

The bot uses long polling, so Telegram doesn't need an open port on your server — only the webhook port behind HTTPS has to be reachable.

Step 1. Download the bot

Download the archive snapexpay-telegram-vpn-bot-1.1.0.zip (MIT, free) and create the settings file:

curl -fsSLO https://pay.snapex.pro/downloads/telegram-vpn-bot/snapexpay-telegram-vpn-bot-1.1.0.zip
unzip snapexpay-telegram-vpn-bot-1.1.0.zip
cd snapexpay-telegram-vpn-bot-1.1.0
cp .env.example .env

All settings live in .env. Put the bot token in TELEGRAM_BOT_TOKEN and your shop name in SHOP_NAME — it is shown in the /start message and in the invoice description.

Step 2. Get an API key

In the SnapEX Pay merchant cabinet open Developers and issue an API key. It is shown once — copy it right away into SNAPEX_API_KEY. A key issued in the cabinet gets every Merchant API permission, including the invoices.read and invoices.write the bot needs. Reissuing a key immediately revokes the old one, so update .env at the same moment.

Two more payment settings:

  • SNAPEX_FEE_ON — merchant (default): the customer pays exactly the plan price, and the fees come out of what you receive; customer: the fees are added on top. More: Who pays the fees on a crypto invoice.
  • SNAPEX_EXPIRES_MINUTES — how long an invoice stays payable, 60 minutes by default (from 1 to 1440).

Step 3. Describe your plans

Plans are one line in PLANS: id:days:price_usd:traffic_bytes, separated by commas. 0 bytes means unlimited traffic.

PLANS=week:7:2.00:0,month:30:5.00:0,quarter:90:12.00:107374182400

Here quarter is 90 days with 100 GiB of traffic. The id is shown on the button ("month — $5.00 / 30d"), so keep it short: Latin letters, digits, _ and -. Prices are in USD; the minimum invoice is 0.50 USD.

Each order keeps a copy of its plan, so you can change prices later without touching orders that are already paid.

Step 4. Connect the panel

Set PANEL_TYPE and the access details of your panel.

Marzban — PANEL_TYPE=marzban:

  • MARZBAN_URL, MARZBAN_USERNAME, MARZBAN_PASSWORD — the panel address and an admin login (or a ready admin token in MARZBAN_TOKEN).
  • MARZBAN_SUB_URL — only if Marzban returns relative /sub/<token> links (empty XRAY_SUBSCRIPTION_URL_PREFIX): the public base for the link.

3x-ui — PANEL_TYPE=3xui:

  • XUI_URL — the panel address including the web base path, if you set one (https://xui.example.com:2053/secretpath).
  • XUI_USERNAME, XUI_PASSWORD — two-factor authentication must be off for this account.
  • XUI_INBOUND_ID — the inbound new clients are added to (vless, vmess, trojan or shadowsocks).
  • XUI_SUB_URL — the subscription base, for example https://xui.example.com:2096/sub. Without it the bot sends only the subId.

Remnawave — PANEL_TYPE=remnawave:

  • REMNAWAVE_URL and REMNAWAVE_TOKEN — the panel address and an API token from API Tokens.
  • REMNAWAVE_SQUAD_UUIDS — the internal squads for new users, comma-separated. Without a squad a new user has no servers.

Step 5. Start the bot behind HTTPS

docker compose up -d --build
docker compose logs -f bot

The bot listens on port 8080: /webhook for SnapEX Pay and /health for checks. Put it behind a reverse proxy with TLS, for example Caddy:

pay-bot.example.com {
    reverse_proxy 127.0.0.1:8080
}

The proxy must pass the request body unchanged — the signature is computed over the raw body.

Step 6. Set up the webhook

In the cabinet, on the Developers page, enter the webhook URL https://pay-bot.example.com/webhook and save. The signing secret whsec_… is shown only once, right after the first save — copy it into SNAPEX_WEBHOOK_SECRET and restart the bot: docker compose up -d.

The cabinet has one webhook URL per account, and every event goes to it. The bot acts only on invoice events and acknowledges the rest.

How the bot checks a notification:

  • the Webhook-Signature header: HMAC-SHA256 over {Webhook-Timestamp}.{raw request body} with your secret; unsigned or tampered requests get 401;
  • Webhook-Timestamp within 5 minutes of the server clock — keep NTP running;
  • the invoice itself, re-read from the SnapEX Pay API with your key: the user is provisioned only if the status there is paid.

If the panel or the SnapEX Pay API is unavailable at that moment, the bot answers 502, and SnapEX Pay retries the delivery (up to 5 attempts). Repeated and concurrent deliveries provision an invoice once. More about signatures and retries: Crypto payment webhooks.

Step 7. A test purchase without real money

  1. Set SNAPEX_ENVIRONMENT=test and restart: docker compose up -d.
  2. Send /start to your bot from your own Telegram account and tap a plan.
  3. Press Pay. The SnapEX Pay page shows a Test badge. Pick any coin and network: the page shows a test address, and about 10 seconds later the payment confirms by itself. Nothing has to be sent.
  4. The bot should send "Payment received" and the subscription link, and the tg<your telegram id> user should appear in the panel. If not, look at docker compose logs -f bot and the delivery log on the Developers page.
  5. Set SNAPEX_ENVIRONMENT=live and restart.

While the bot is in test, every plan bought through it is provisioned without money — keep the test short. More: How to test crypto payments without real money.

What the customer sees

  • /start — your shop name, a line about paying with USDT (TRC20 / ERC20 / BEP20) or other coins, and the plan buttons.
  • After a tap — the plan, its days, price and invoice number, plus a "Pay $5.00 with crypto" button.
  • The SnapEX Pay page with a choice of coins: USDT on TRC20, ERC20 and BEP20 and other coins (50+ in total) when SnapEX Pay offers them for the invoice amount.
  • After payment — "Payment received" and the subscription link in the same chat. With PANEL_TYPE=none the message says the admin will send access shortly.

The bot's own messages are in English; only the shop name and plan names are yours.

A returning customer pays from the same Telegram account, and their user is extended from the current expiry (or from now if it has already passed); a plan with a traffic limit starts a fresh quota. Details: VPN subscription renewals paid in crypto.

If the customer sends less than the total, the user is not provisioned: the bot gives access only for a paid invoice. The received amount goes to your SnapEX Pay balance, and you settle the rest with the customer. How underpayments work: The customer sent less than the invoice.

If something doesn't work

  • "Payment is unavailable right now" after tapping a plan. The bot couldn't create an invoice: check SNAPEX_API_KEY (wrong or revoked key) and the logs.
  • The webhook gets 401 "bad webhook signature". SNAPEX_WEBHOOK_SECRET doesn't match, or the proxy changes the request body. Also check that the server clock is in sync.
  • The customer paid but got nothing. Look for provision failed in the logs. SnapEX Pay keeps retrying the delivery; once the panel works again, the next delivery provisions the user, or you can replay the delivery from the log in the cabinet.
  • Marzban sends a link without a host. Set XRAY_SUBSCRIPTION_URL_PREFIX in Marzban or MARZBAN_SUB_URL in the bot.
  • 3x-ui: "login failed". Check XUI_URL (with the web base path) and the login, and turn off two-factor authentication for that account.
  • Remnawave: the user is created but the subscription is empty. Set REMNAWAVE_SQUAD_UUIDS to the squads with your nodes.
  • Several copies of the bot. Run one instance per DATA_DIR: duplicate protection works within one process and its SQLite file.

Cost

1% per payment, no setup fee and no monthly fee. The bot is free.

Bot page: Telegram VPN bot. Selling through a panel's own shop instead? See Crypto payments in Xboard and V2Board. Overview for VPN services: Crypto payments for VPN services. Sign-up: pay.snapex.pro.

More guides

VPN subscription renewals paid in crypto: extend users without duplicates or lost days How the SnapEX Pay modules for Marzban, 3x-ui, Remnawave and the Telegram VPN bot handle a returning customer: which expiry the new days count from, what happens to traffic, configs and repeated webhooks. Crypto payments in Xboard and V2Board: accept USDT for VPN subscriptions step by step How to connect SnapEX Pay to an Xboard or V2Board panel: install the plugin, add the payment method, set up the webhook, convert CNY or other prices to USD and run a test order without real money. Crypto payment webhooks: verify the signature, handle retries and duplicates How SnapEX Pay webhooks are signed and delivered, how to verify them in PHP and Node.js, and how to make your handler safe against retries, duplicates and out-of-order events. The customer sent less than the invoice: how crypto underpayments work Exchanges often deduct their withdrawal fee from the amount, so a crypto invoice arrives short. What SnapEX Pay does with an underpayment, which webhooks you get and what to do with the order. How to choose a crypto payment gateway for a hosting company: 8 questions to ask A checklist for hosting and VPS providers choosing a crypto payment gateway: billing module, how paid invoices get marked paid, underpayments, fees, settlement, coins, test mode and multi-currency billing. SnapEX Pay vs NOWPayments, Cryptomus and CoinGate: fees and ready-made modules compared How SnapEX Pay compares with NOWPayments, Cryptomus and CoinGate on the published fee, monthly fees and ready-made modules for hosting billing, VPN panels and stores. Competitor data from their own websites, 11 October 2026. How to test crypto payments without real money Test invoices in SnapEX Pay go through the same statuses and send the same webhooks as live ones, but confirm by themselves and never touch your balance. Here is how to use them. Who pays the fees on a crypto invoice: you or your customer SnapEX Pay lets you choose per invoice who covers the service fee and the network allowance. What fee_on does, how it changes the amount the customer pays and the amount you receive.

Ready to accept crypto?

Create a merchant account and get an API key. 1% per payment, no monthly fee.