
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
/startshows your plans as buttons. Each tap creates a SnapEX Pay invoice and the bot replies with a Pay button.- 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.
- The bot checks the signature, asks the SnapEX Pay API for the invoice and provisions the user only if the status there is
paidand 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 inMARZBAN_TOKEN).MARZBAN_SUB_URL— only if Marzban returns relative/sub/<token>links (emptyXRAY_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 examplehttps://xui.example.com:2096/sub. Without it the bot sends only thesubId.
Remnawave — PANEL_TYPE=remnawave:
REMNAWAVE_URLandREMNAWAVE_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-Signatureheader: HMAC-SHA256 over{Webhook-Timestamp}.{raw request body}with your secret; unsigned or tampered requests get401; Webhook-Timestampwithin 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
- Set
SNAPEX_ENVIRONMENT=testand restart:docker compose up -d. - Send
/startto your bot from your own Telegram account and tap a plan. - 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.
- 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 atdocker compose logs -f botand the delivery log on the Developers page. - Set
SNAPEX_ENVIRONMENT=liveand 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=nonethe 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_SECRETdoesn'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 failedin 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_PREFIXin Marzban orMARZBAN_SUB_URLin 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_UUIDSto 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.