On this page
- Post one invite link with creates_join_request on. The bot approves a request only for a Telegram user ID with an active Fianto subscription, so a forwarded link lets nobody in who has not paid.
- The bot puts the Telegram user ID in the checkout's customer_reference; Fianto returns it on every subscription event, and unbanChatMember on subscription.ended removes the member.
- For one-time access, single-use invite links can be sold from a Fianto shop as delivery codes, with no bot at all. A trading-signals group needs Fianto's written approval before the first collection, and approval is not automatic.
What are the ways to charge for a Telegram group?
There are three workable setups, and they differ in who pays how and whether access ever stops. Fianto has no Telegram bot or integration, so two of the three need a bot you run. As of 15 October 2026:
| Setup | Paid in | Renews | A bot of yours | Works for |
|---|---|---|---|---|
| Telegram's own subscription invite link | Telegram Stars | Every 30 days | Not needed: made in channel settings | Channels |
| Single-use invite links sold as Fianto shop delivery codes | USDC | No: one-time access | Not needed | Groups and channels |
| Join-request link, your bot and Fianto webhooks | USDC | Every 30 or 365 days | Approves and removes members | Supergroups and channels |
Telegram's built-in option is honest competition. The Bot API method createChatSubscriptionInviteLink makes "a subscription invite link for a channel chat", priced in Stars, and the period "must always be 2592000 (30 days)", at 1 to 10,000 Stars. Telegram's own announcement of 14 August 2024 puts it in the app, under Channel Settings > Channel Type > Manage Invite Links, with "Require Monthly Fee" turned on. If your readers already buy Stars and you run a channel, it is the shortest path. It is not USDC, it has no yearly price, and it is for channels, so a discussion group or a yearly plan billed in USDC needs the third setup.
Why use a join-request link instead of one invite link per member?
A join-request link ties access to a Telegram account instead of to whoever holds the link. With creates_ set, Telegram does not let people in directly; it sends your bot a ChatJoinRequest with the user who asked, and the bot decides. A link with member_ (the range is 1 to 99,999) is the other common pattern, but whoever opens it first gets in, paid or not. Telegram does not let you combine the two: "If True, member_limit can't be specified."
The join request also solves the hardest part of the build, knowing who paid. The request carries from., the Telegram user ID, and a user_ that lets the bot message that person even if they never opened a chat with it. Telegram says "The bot can use this identifier for 5 minutes to send messages until the join request is processed, assuming no other administrator contacted the user." So the bot should reply with the checkout link straight away.
How do you build the paid group, step by step?
- Turn the group into a supergroup, or use a channel, and add your bot as an administrator with the rights to invite users and to ban users. Join requests reach the bot only with the can_invite_users right.
- Have the bot call createChatInviteLink with creates_join_request set to true and a name of up to 32 characters, and post that link wherever you sell.
- In Fianto, create a product with a monthly or yearly price. Payments start once your merchant account is approved.
- When a join request arrives, approve it at once if that user ID already has an active subscription in your records. Otherwise create a Fianto checkout session for them and message the link through user_chat_id.
- On subscription.created, approve the request. On subscription.ended, remove the member.
import { Fianto } from '@fianto/sdk';
import { db } from './db'; // your own store
const fianto = new Fianto(); // reads FIANTO_APP_ID and FIANTO_APP_SECRET
const TG = `https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}`;
export async function tg(method: string, body: object) {
const res = await fetch(`${TG}/${method}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
return res.json();
}
// Called for each chat_join_request update.
export async function onJoinRequest(req: { chat: { id: number }; from: { id: number }; user_chat_id: number }) {
const userId = String(req.from.id);
if (await db.isActive(userId)) {
return tg('approveChatJoinRequest', { chat_id: req.chat.id, user_id: req.from.id });
}
const orderId = `tg_${userId}_${Date.now()}`;
const session = await fianto.checkoutSessions.create(
{
mode: 'subscription',
order_id: orderId,
price_id: 'fian_price_...',
customer_reference: userId,
metadata: { chat_id: String(req.chat.id) },
success_url: 'https://example.com/paid',
cancel_url: 'https://example.com/join',
},
{ idempotencyKey: orderId },
);
return tg('sendMessage', {
chat_id: req.user_chat_id,
text: `Subscribe in USDC here, then come back to Telegram: ${session.url}`,
});
}The checkout session is open for 30 minutes by default, and its URL comes back only when the session is created, so send it right away. After a member subscribes, Fianto's checkout page offers links to the customer portal rather than opening success_, which is why the message tells them to come back to Telegram.
Members approve once in their wallet and renew every 30 or 365 days. You pay nothing.
Grant access from webhooksWhich Fianto events approve and remove Telegram members?
Approve the join request on subscription. and remove the member on subscription.; both events carry the user ID back as data. and your chat ID in data.. A subscription creates no order, so order. never fires for it.
import { webhooks } from '@fianto/express';
import { tg } from './join-requests';
import { db } from './db';
export const fiantoRoute = webhooks({
secret: process.env.FIANTO_WEBHOOK_SECRET,
onSubscriptionCreated: async (event) => {
const userId = event.data.customer.reference;
const chatId = event.data.metadata.chat_id;
if (userId === null || (await db.seen(event.id))) return;
await db.activate(event.data.id, userId, event.data.current_period_end);
// Fails harmlessly if the request is no longer pending: the member taps the link again and is approved.
await tg('approveChatJoinRequest', { chat_id: chatId, user_id: Number(userId) });
await db.markSeen(event.id);
},
onSubscriptionEnded: async (event) => {
const userId = event.data.customer.reference;
const chatId = event.data.metadata.chat_id;
if (userId === null || (await db.seen(event.id))) return;
await db.deactivate(event.data.id);
// Delivery is unordered: a newer subscription from the same person may already be paying.
if ((await db.isActive(userId)) === false) {
// Without only_if_banned, this removes a current member and leaves them free to rejoin later.
const res = await tg('unbanChatMember', { chat_id: chatId, user_id: Number(userId) });
// Rate limited or a Telegram outage: throw, the route answers 500 and Fianto retries.
if (res.ok === false && (res.error_code === 429 || res.error_code >= 500)) throw new Error(res.description);
}
await db.markSeen(event.id);
},
});The removal is a quirk worth knowing. unbanChatMember on someone who was never banned removes them. Telegram's description says: "By default, this method guarantees that after the call the user is not a member of the chat, but will be able to join it." banChatMember is the stronger tool, for someone who should not come back by any link. Mount the Fianto route before any body parser, record each event id once, and expect events at least once and in no fixed order, which is why the ending checks for a newer subscription first; verifying webhook signatures has the details.
What happens when a member stops paying?
A failed renewal makes the subscription past due, not ended. Fianto tries four times in all: at the period boundary, then about one hour, six hours and a day after it. Your bot gets subscription. on the first counted failure and subscription. on each one; whether you remove the member then or wait is up to you. After the fourth counted failure the subscription ends with PAYMENT_, and the period is never charged later.
- A member cancels in the customer portal at fianto.xyz/my with one signature. The subscription runs to the end of the paid period, so they stay until subscription.ended.
- Removing the approval inside the wallet is not a cancel. The next renewal fails, the subscription goes past due, and it ends after the fourth failed attempt.
- Fianto sends subscribers no renewal reminders. If your members expect a note before a yearly renewal, have the bot send it from current_period_end.
- There is no refund button. A refund is USDC you send back from your own wallet.
Can you sell one-time Telegram access with no bot?
Yes, if access never has to end: create single-use invite links in Telegram and sell them from a Fianto shop as delivery codes. Each paid buyer gets one code, shown on the receipt and in the buyer email, and the product shows as sold out when the list runs out. A code can be up to 200 characters, which fits a t.me link, and a product holds up to 5,000 unused codes. Create each link with a member limit of 1 and no expiry date, since a code may wait weeks before someone buys it. Telegram counts that limit as members who joined through the link at the same time, so if the buyer leaves, the link admits one more person.
Two cautions. A shop receipt page is not private, so anyone with its link sees the code; a single-use link that the buyer has already used is worth nothing to them. And nothing removes the buyer later, so use this only for lifetime access. The shop itself is covered in the no-code USDC shop post. Delivery codes in depth are in selling licence keys for USDC, and the same codes can enrol students, as in selling an online course for USDC.
What does a paid Telegram group cost, and which groups need approval?
You pay nothing, and the price you set lands in your wallet. There is no Fianto fee during the beta; after it, the member pays about 0.1 to 0.5% on top. The member pays a flat 0.01 USDC network fee with each charge today, the first one included, on plans created since 6 October 2026, the Solana fee of the transaction they sign (under $0.01), and account rent: about 0.00144 SOL as of 15 October 2026 for the subscription account, returned when they reclaim it after the subscription ends, and about 0.00119 SOL once for wallet setup on a wallet's first subscription, not returned. The checkout shows the exact live amounts before anything is signed.
Many paid Telegram groups sell trading signals. Fianto's acceptable-use policy lists "Financial or investment education and signals" among the businesses allowed only after Fianto has reviewed them and confirmed in writing, and says Fianto usually asks what you promise customers and what your refund terms are. Approval can come with conditions or not at all, and the same policy prohibits investment schemes and unlicensed financial services outright. Raise it during review, before you post the link. This is not legal advice; the rules that apply to selling signals depend on where you and your members are.
Event names and payloads are in the Fianto webhook docs, and the same pattern for Discord roles is in paid Discord roles with USDC.
- Telegram Bot API: createChatInviteLink, approveChatJoinRequest, banChatMember, unbanChatMember, createChatSubscriptionInviteLink, ChatJoinRequest
- Telegram blog: Super Channels, Star Reactions and Subscriptions (14 August 2024)
- Fianto docs: webhook event types
- Fianto docs: subscriptions via the API
- Fianto docs: buying from a shop