Telegram Star Subscriptions Setup: A Recurring-Revenue How-To

Telegram Star Subscriptions Setup: A Recurring-Revenue How-To
What you will achieve. A working Telegram Star Subscription that bills users 100, 500, or 2,000 Stars every 30 days for tiered access — created with
createInvoiceLink({subscription_period: 2592000}), captured fromsuccessful_paymentwebhooks (withis_recurring/is_first_recurringflags), persisted viatelegram_payment_charge_id, and cleanly canceled or resumed througheditUserStarSubscription. End state: predictable Stars MRR you can forecast against, with clean off-boarding when subscribers churn.
If you would rather not build the bot from scratch, Autogram's scheduler talks to your subscription bot the same way it talks to your free channel — same calendar, same dashboard.
Prerequisites
Every step below assumes the following:
- A bot created in @BotFather — no extra approval needed for Stars (
XTR). createInvoiceLinkalready working for one-shot Stars payments. If you have not shipped a non-recurring Stars charge end-to-end, do that first — the recurring payload is identical minussubscription_period.- A persistent store for
telegram_payment_charge_idper user per tier. SQLite works for testing; in-memory does not. - A webhook handler for
successful_payment. Every renewal lands there. Handlers that only listen onpre_checkout_querymiss every recurring charge. - A library on Bot API 8.0+ — aiogram ≥ 3.13, python-telegram-bot ≥ 21.7, telegraf ≥ 4.16, gramio ≥ 1.0. Older versions silently drop the new
SuccessfulPaymentfields.
Step 1 — Set the subscription tiers in your bot's catalog
Photo by Matheus Bertelli on Pexels
A tier is a (label, star_amount, payload_prefix) tuple in your code — there is no "tier" concept on Telegram's side; each tier is just a different createInvoiceLink call with a different price. Three tiers is the right starting point:
| Tier | Stars / 30 days | USD-equivalent (≈$0.013/Star) | Audience |
|---|---|---|---|
| Supporter | 100 | ~$1.30 | "thanks for the work" tippers |
| Member | 500 | ~$6.50 | regular consumers of paid drops |
| VIP | 2,000 | ~$26 | high-engagement, 1:1 access expectations |
Hard-coded constraints from the Bot API 8.0 spec (and the Star Subscriptions guide):
subscription_periodMUST be2592000(exactly 30 days). No other period is currently supported. Weekly or annual subscriptions are not on offer — plan around the monthly cycle.- Maximum subscription price is 10,000 Stars per tier. The 2,000-Star VIP tier above leaves plenty of headroom.
- Currency MUST be
XTR(Telegram Stars) whensubscription_periodis set — fiat currencies do not work for recurring. - A user can hold multiple concurrent subscriptions to the same bot. If they upgrade, you start a new subscription and cancel the old one — there is no in-place tier change.
Step 2 — Build the recurring invoice with createInvoiceLink
Photo by Lukas Blazek on Pexels
For each tier, generate one invoice link and reuse it for every subscriber. The link is permanent until you rotate it. The minimal payload (Python + python-telegram-bot 21.7+):
SUB_PERIOD = 2592000 # 30 days, the only allowed value as of Bot API 8.0
async def make_subscription_link(bot, tier_id: str, label: str, stars: int) -> str:
return await bot.create_invoice_link(
title=f"{label} — monthly access",
description="Renews automatically every 30 days. Cancel anytime in chat settings.",
payload=f"sub:{tier_id}", # echoed back in successful_payment.invoice_payload
provider_token="", # MUST be empty string for Stars
currency="XTR", # Telegram Stars
prices=[LabeledPrice(label=label, amount=stars)],
subscription_period=SUB_PERIOD,
)
Three things bite if you skip them:
provider_tokenmust be the empty string for Stars, not omitted, not your fiat token. Passing a fiat token alongsidecurrency="XTR"returns400 Bad Request: provider_token is invalid.- The
payloadis your only out-of-band tier identifier. Encode the tier ID — you will read it back in step 3 and it is the only signal that distinguishes Supporter from VIP. - Send the link via a button or
sendInvoice, not bare text. Telegram does not auto-rendert.me/$invoice/...URLs as a paywall in older clients.
Step 3 — Persist telegram_payment_charge_id from successful_payment
This is the step that determines whether you can manage the subscription later. When the user pays, your successful_payment handler receives a SuccessfulPayment object with the new Bot API 8.0 fields. Capture all four:
@router.message(F.successful_payment)
async def on_payment(message: Message, db):
sp = message.successful_payment
tier_id = sp.invoice_payload.removeprefix("sub:")
await db.upsert_subscription(
user_id=message.from_user.id,
tier_id=tier_id,
charge_id=sp.telegram_payment_charge_id, # NEEDED for editUserStarSubscription
expires_at=sp.subscription_expiration_date, # Unix seconds
is_first=sp.is_first_recurring or False, # only true on the initial charge
)
if sp.is_first_recurring:
await message.answer("Welcome — your subscription is active. Renews in 30 days.")
elif sp.is_recurring:
await message.answer("Renewed for another 30 days. Thanks.")
Three behaviors to internalize:
telegram_payment_charge_idis the same string across renewals for one subscription. Keep one row per(user_id, tier_id, charge_id); on renewal, updateexpires_atand clearis_first.is_first_recurring=truefires exactly once per subscription — on the initial charge.is_recurring=truefires on every charge after. A one-shot Stars payment has both flags missing or false; useful for branching shared handlers.- Telegram does not push a "subscription canceled" update. If the user cancels in Settings → Subscriptions, the next renewal simply does not arrive. Treat absence of a renewal past
expires_at + grace_periodas the cancel signal.
Step 4 — Cancel and resume with editUserStarSubscription
Photo by weCare Media on Pexels
editUserStarSubscription does two things only: cancel future renewals or re-enable a previously canceled subscription. It does not change the price, swap tiers, or refund the current period — those are separate flows.
async def cancel_subscription(bot, user_id: int, charge_id: str):
await bot.edit_user_star_subscription(
user_id=user_id,
telegram_payment_charge_id=charge_id,
is_canceled=True,
)
async def resume_subscription(bot, user_id: int, charge_id: str):
await bot.edit_user_star_subscription(
user_id=user_id,
telegram_payment_charge_id=charge_id,
is_canceled=False,
)
Three behaviors that catch first-time builders:
is_canceled=Truedoes not revoke access immediately — the user keeps full access until the current 30-day period ends. For an immediate revoke, do it in your own access-check layer.- Resume works only while the subscription has not yet expired. Once the 30-day window closes without renewal,
editUserStarSubscriptionreturns400 Bad Request: subscription not activeand the user must subscribe again from scratch. - Tier changes = cancel + resubscribe. Cancel against the old tier's
charge_id, then send a fresh invoice link for the new tier. For a full refund of the current period, callrefundStarPaymentbefore canceling — the audit log reads cleaner that way.
Common mistakes
Four mistakes account for most "my subscription bot is broken" tickets:
- Treating
subscription_periodas configurable. It is fixed at 2,592,000 seconds. Sending 604,800 (one week) returns a generic 400; sending null silently disables the recurring behavior and you ship a one-shot bot. - Discarding
telegram_payment_charge_id. Without it you cannot calleditUserStarSubscriptionlater, and the only response to "please cancel me" support tickets becomes "please cancel in app settings." - Re-issuing access on every
successful_paymentwithout checkingis_first_recurring. If your "welcome" flow sends a free download link, every renewal re-sends it. Gate welcome onis_first_recurring, renewal onis_recurring and not is_first_recurring. - Building tier upgrades as a single API call. There is no API call. Tier change = cancel + resubscribe. Surface this in your
/upgradeUX so users do not expect a one-tap upgrade.
Related reading
- Telegram Stars Monetization: A 2026 Toolkit for Channel Owners — the parent toolkit. This article is the deep dive on the recurring-revenue endpoint it referenced in passing.
- Telegram Channel Bot for Creators: A Five-Hour-a-Week Playbook — how a solo creator wires Stars subscriptions into a five-hour-a-week schedule.
- Telegram Automation ROI: Cost Savings vs Manual Posting (2026) — the cost side of the equation: hours reclaimed by automation are what funds your subscription experiment.
- AI-Powered Telegram Content Bot: Setup Guide for 2026 — once subscriptions ship, AI-generated drops are how you keep VIP tiers worth their price.
FAQ
Can I offer weekly or annual Star Subscriptions?
No. As of Bot API 8.0 the only supported subscription_period is 2592000 (30 days). Plan pricing around the monthly cycle and revisit when Telegram extends the spec.
What is the maximum I can charge per subscription?
10,000 Telegram Stars per 30-day period — about $130 at the creator/withdrawal rate (~$0.013/Star). The same ceiling applies whether the subscription gates a Mini App, a private channel, or bot-mediated content access.
How do I handle subscription upgrades and downgrades?
There is no in-place tier change. Cancel the existing subscription with editUserStarSubscription(is_canceled=True), then send a fresh invoice link for the new tier. The old tier remains active until its current period ends; the new tier starts billing immediately.
Does Telegram notify the user when the bot cancels their subscription?
No. editUserStarSubscription(is_canceled=True) is silent — the user only finds out when the next renewal does not happen. Send your own confirmation, and show a "subscription ends on YYYY-MM-DD" line in any post-cancel UI.
How do I tell the first payment from a renewal in successful_payment?
Bot API 8.0 added is_first_recurring (true exactly once per subscription) and is_recurring (true on every charge after the first). A one-shot Stars payment has both flags missing or false. Branch handlers on these flags, not on invoice_payload matching alone.
Can a user cancel without my bot being involved?
Yes. Subscribers can manage every Star Subscription in Settings → Telegram Stars → Subscriptions. Your bot is not notified — the only signal is that the next renewal does not arrive. Treat any subscription past expires_at + 24h as canceled in your access layer.
Bottom line
Telegram Star Subscriptions are a thin layer on top of createInvoiceLink — three new fields on SuccessfulPayment, one new method to manage the lifecycle, one fixed 30-day cycle. The complexity is operational, not API-level: persist the charge ID, branch your welcome flow on is_first_recurring, and treat tier changes as cancel-and-resubscribe. Ship the Supporter tier first, watch what converts in the first 30 days, then layer Member and VIP on top.
Plug Autogram into your subscription bot and let your scheduler post the paid-tier drops on the same calendar that handles your free content.
Image credits
- Hero image — Photo by Matheus Bertelli on Pexels
- Inline image #1 — Photo by Lukas Blazek on Pexels
- Inline image #2 — Photo by weCare Media on Pexels
Related Posts

How to Add a Telegram Mini App to Your Channel (No-Code Guide)
Learn how to attach a Telegram Mini App to your channel and start accepting Stars payments — without writing a single line of code. A practical operator guide for 2026.

TON to Fiat for Channel Operators: Fragment Withdrawal Walkthrough
Step-by-step guide to withdrawing Telegram channel revenue — ad TON and Stars — via Fragment, converting on a crypto exchange, and banking the proceeds.

Refunding Telegram Stars: How refundStarPayment Actually Works
When to refund a Telegram Stars charge, how to call refundStarPayment, the no-partial-refund rule, /paysupport handling, and clean audit-log reconciliation.
Subscribe to our newsletter
Get the latest Telegram growth tips, automation strategies, and platform updates delivered to your inbox.
Or follow our Telegram channel