Skip to main content
How-to guides

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

Also available in:ENRUUK
Published on April 24, 20268 min read113 views
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 from successful_payment webhooks (with is_recurring/is_first_recurring flags), persisted via telegram_payment_charge_id, and cleanly canceled or resumed through editUserStarSubscription. 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).
  • createInvoiceLink already 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 minus subscription_period.
  • A persistent store for telegram_payment_charge_id per 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 on pre_checkout_query miss 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 SuccessfulPayment fields.

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:

TierStars / 30 daysUSD-equivalent (≈$0.013/Star)Audience
Supporter100~$1.30"thanks for the work" tippers
Member500~$6.50regular consumers of paid drops
VIP2,000~$26high-engagement, 1:1 access expectations

Hard-coded constraints from the Bot API 8.0 spec (and the Star Subscriptions guide):

  • subscription_period MUST be 2592000 (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) when subscription_period is 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.

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_token must be the empty string for Stars, not omitted, not your fiat token. Passing a fiat token alongside currency="XTR" returns 400 Bad Request: provider_token is invalid.
  • The payload is 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-render t.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_id is the same string across renewals for one subscription. Keep one row per (user_id, tier_id, charge_id); on renewal, update expires_at and clear is_first.
  • is_first_recurring=true fires exactly once per subscription — on the initial charge. is_recurring=true fires 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_period as 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=True does 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, editUserStarSubscription returns 400 Bad Request: subscription not active and 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, call refundStarPayment before canceling — the audit log reads cleaner that way.

Common mistakes

Four mistakes account for most "my subscription bot is broken" tickets:

  1. Treating subscription_period as 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.
  2. Discarding telegram_payment_charge_id. Without it you cannot call editUserStarSubscription later, and the only response to "please cancel me" support tickets becomes "please cancel in app settings."
  3. Re-issuing access on every successful_payment without checking is_first_recurring. If your "welcome" flow sends a free download link, every renewal re-sends it. Gate welcome on is_first_recurring, renewal on is_recurring and not is_first_recurring.
  4. Building tier upgrades as a single API call. There is no API call. Tier change = cancel + resubscribe. Surface this in your /upgrade UX so users do not expect a one-tap upgrade.

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.

Explore Autogram's plans. Connect your first channel to start 7 days of Solo access, with a $1 AI budget and no card required.

Image credits

#telegram star subscriptions#createInvoiceLink#subscription_period#editUserStarSubscription#recurring stars revenue#telegram bot setup#telegram subscription bot

Subscribe to our newsletter

Get the latest Telegram growth tips, automation strategies, and platform updates delivered to your inbox.

Or follow our Telegram channel