Skip to main content
How-to guides

Refunding Telegram Stars: How refundStarPayment Actually Works

Also available in:ENRUUK
Published on April 24, 20269 min read289 views
Refunding Telegram Stars: How refundStarPayment Actually Works

Refunding Telegram Stars: How refundStarPayment Actually Works

What you will ship. A working /paysupport flow that lets a Telegram Stars customer ask for a refund inside the chat, an audited refundStarPayment call against the original telegram_payment_charge_id, an operator-side ledger entry that survives the App Store/Google Play clawback risk, and a clean rule for refunding subscription charges (which never auto-cancels the subscription — that is a separate editUserStarSubscription call). Two API arguments, one decision tree, and the cleanest customer-support story in the Stars ecosystem.

If you have a paid Stars surface live and no refund handling, you are already accruing technical debt — Autogram's scheduler can hand off the refund webhook to your bot the same way it hands off paid drops.

Prerequisites

Every step below assumes you already have:

  • A bot that has accepted at least one Stars payment. No charge means no telegram_payment_charge_id to refund against.
  • A persistent record of every successful_payment keyed by telegram_payment_charge_id → (user_id, tier_id, amount, paid_at). SQLite is fine; in-memory state is not.
  • A /paysupport command handler. Telegram's Stars ToS explicitly tells users to send /paysupport to your bot when they want a refund. Bots that ignore it lose the dispute escalation path automatically.
  • A library on Bot API 7.4+ — refundStarPayment shipped with the Stars currency itself (June 2024), so anything that supports XTR supports refunds.

Step 1 — Decide if a refund is the right answer

Photo by Yan Krukau on Pexels

A refund is the right answer when one of the following is true. Anything else is a candidate for a credit, an apology, or a feature-request log entry — not a refund:

TriggerRefund?Why
Promised content was not delivered (404, broken link, missing video)Yes, fullTelegram ToS section 3.1 explicitly covers this case
User accidentally bought twice within a short windowYes, the duplicate onlyGoodwill move; the dupe-charge has no business value
Subscription renewed and user did not realize it was recurringYes, last period onlyRefund the most recent renewal; do not chase older periods
Content delivered but user changed their mindNoPer ToS: "All sales of Telegram Stars are final" once the digital good is delivered
User wants a partial refund of a multi-item paid postNo (impossible)refundStarPayment takes no amount parameter — refunds are full or none
Charge is older than ~6 months (no documented limit, but practical)MaybeAPI does not block it, but Telegram may have already processed a clawback for it

Useful internal rule: a refund makes the customer whole; a credit makes the next purchase nicer. When unsure, default to a credit — it preserves the audit log and keeps LTV intact. Refunds are reserved for "we did not deliver".

Step 2 — Wire up /paysupport so users can ask cleanly

When a user opens Settings → Telegram Stars → Transactions and taps any bot transaction, Telegram's UI shows a "Contact bot for refund" button that fires /paysupport. If your bot does not handle /paysupport, the user lands in a dead chat — and Telegram's escalation flow notes that "the developer refused to process a legitimate refund". That escalation can result in Telegram debiting Stars from your bot's balance later. Handle the command:

@router.message(Command("paysupport"))
async def on_paysupport(message: Message, db):
    user_id = message.from_user.id
    recent = await db.last_charges(user_id, limit=5)
    if not recent:
        return await message.answer(
            "We do not have a recent Stars charge from you on file. "
            "Open Settings → Telegram Stars → Transactions to see the bot transaction ID."
        )
    buttons = [
        [InlineKeyboardButton(
            text=f"Refund {c.amount}⭐ from {c.paid_at:%Y-%m-%d}",
            callback_data=f"refund:{c.charge_id}",
        )] for c in recent
    ]
    await message.answer(
        "Pick the charge you would like a refund for. "
        "Refunds return the full original amount; partial refunds are not supported.",
        reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons),
    )

Two nuances to internalize:

  • Always show amount and date before taking action. Stars purchases blur together and a one-tap refund of the wrong charge spawns a second ticket.
  • Never auto-refund without inline confirmation. Operators who ship "every /paysupport = immediate refund" find their bot drained inside a week by users testing the flow.

Step 3 — Call refundStarPayment

Photo by Towfiqu barbhuiya on Pexels

Two arguments, one Bot API call, one Boolean response. The minimal handler for the inline button from Step 2:

@router.callback_query(F.data.startswith("refund:"))
async def on_refund_confirm(query: CallbackQuery, bot: Bot, db):
    charge_id = query.data.removeprefix("refund:")
    charge = await db.get_charge(charge_id)
    if not charge or charge.user_id != query.from_user.id:
        return await query.answer("Charge not found.", show_alert=True)
    if charge.refunded_at:
        return await query.answer("Already refunded.", show_alert=True)
    try:
        await bot.refund_star_payment(
            user_id=charge.user_id,
            telegram_payment_charge_id=charge_id,
        )
    except TelegramBadRequest as e:
        # CHARGE_ALREADY_REFUNDED, USER_ID_INVALID, CHARGE_ID_EMPTY, USER_BOT_REQUIRED
        await db.log_refund_failure(charge_id, str(e))
        return await query.answer(f"Refund failed: {e.message}", show_alert=True)
    await db.mark_refunded(charge_id, refunded_by=query.from_user.id)
    await query.message.edit_text(
        f"Refunded {charge.amount}⭐ for charge {charge_id[:12]}…. "
        f"The Stars are back in your wallet immediately."
    )

Behavior to plan around:

  • Immediate, synchronous. User's Stars wallet credited and bot's balance debited atomically. No pending state.
  • Four documented errors: CHARGE_ALREADY_REFUNDED, CHARGE_ID_EMPTY, USER_BOT_REQUIRED, USER_ID_INVALID. Anything else is transport or rate-limit — retry with backoff.
  • No partial refunds. Sold a 1,000-Star bundle and user disputes 200 Stars? Refund full + new charge for the kept items. Plan SKU sizing accordingly.
  • Refunding a subscription charge does not cancel the subscription. Next 30-day renewal still fires unless you also call editUserStarSubscription(is_canceled=True) against the same telegram_payment_charge_id. Refund first, cancel second.

Step 4 — Reconcile the refund in your books

Photo by Mikhail Nilov on Pexels

The bot side is one call; the books side is three things to keep aligned:

  1. Your internal ledger. Mark the row refunded_at = now(), refunded_by = <admin user_id>. Never delete the original charge — refunds are debits against an existing credit.
  2. The Stars dashboard balance. Refunds appear immediately in Stars-out. If you withdraw to TON on a schedule, a refund can push withdrawable balance below the 500-Star floor.
  3. The clawback risk. If the user later disputes via App Store or Google Play, Telegram retains the right to debit additional Stars from your balance to cover the platform refund. Keep float against high-value charges.

For any finance handoff, the row your accountant needs is: refund_id, original_charge_id, user_id, amount, original_paid_at, refunded_at, reason_code. The Bot API does not return a refund_id — synthesize one from (charge_id + refunded_at).

Common mistakes

Four mistakes that turn a clean refund into a ticket spiral:

  1. Refunding without persisting the result. Call refundStarPayment but skip writing refunded_at, and the user's next /paysupport shows the same charge as refundable. Second call returns CHARGE_ALREADY_REFUNDED and the user sees an error.
  2. Refunding a subscription charge and forgetting editUserStarSubscription. User expected "stop billing me"; you delivered "last month back" but left the subscription armed. Next renewal in 30 days = angrier ticket.
  3. Auto-refunding from /paysupport with no inline confirmation. Some users tap to test. Treat the inline button as the contract.
  4. Treating "no eligibility window" as "refund anytime is safe". The API does not block old charges, but Telegram may already have settled the original purchase with Apple/Google. Refunding then can leave your bot's balance net-negative.

FAQ

Can I refund only part of a Stars payment?

No. refundStarPayment takes only user_id and telegram_payment_charge_id — no amount parameter, no partial refunds. Workaround: refund in full, then re-invoice for the kept items; or grant a Stars-equivalent in-bot credit for the disputed portion.

Is there a time limit on Telegram Stars refunds?

Telegram does not document a hard eligibility window. The only API blocker is CHARGE_ALREADY_REFUNDED. In practice, refunds older than ~3–6 months become risky because Telegram may have already settled the original purchase with Apple or Google, opening you to a downstream clawback debit.

Does refunding a subscription charge cancel the subscription?

No. refundStarPayment returns Stars from one charge only. The renewal cycle keeps running. To stop future renewals, also call editUserStarSubscription(user_id, telegram_payment_charge_id, is_canceled=True) against the same charge ID. Refund first, cancel second.

What happens to the user's Stars after a refund?

Immediately credited back to the user's Stars balance. They can spend them on any other Stars surface, withdraw via Fragment where supported, or — in the case of an Apple/Google clawback — see them disappear when the platform refund completes.

Do I have to handle /paysupport?

Yes, in practice. Telegram's UI directs users to /paysupport from Settings → Stars. A bot that ignores it leaves the user with no in-chat resolution; the user can then escalate to Telegram, who may debit Stars from your balance to cover the refund. Handle it even with just a "we will reply within 24 hours" response.

Can a user dispute a Stars purchase through Apple or Google?

Yes — Stars are bought via in-app purchase, so platform-level refunds are possible. If a user does this after you delivered the digital good, Telegram can debit equivalent Stars from your bot's balance to compensate. Keep a Stars float against high-value charges.

Bottom line

Refunds are a two-argument API call wrapped in a four-step operational discipline: decide the refund is right, give the user a clean /paysupport flow, call refundStarPayment with confirmation, reconcile the books. The technical surface is tiny; the business surface is everything around it. Ship the /paysupport handler before you ship your next paid drop — the trust dividend compounds and the clawback risk shrinks.

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 stars refund#refundstarpayment#paysupport#telegram bot refund#stars chargeback#telegram subscription refund#refund stars api

Subscribe to our newsletter

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

Or follow our Telegram channel