← Guides
Playbook In Build products intermediate · 24 min read · Updated Apr 11, 2026

Bot payments, end to end

A no-shortcut guide to accepting payments through a Telegram bot — Stars for digital, fiat for physical, refunds, webhooks, and the gotchas nobody documents.

Bot monetization example in Telegram
A Stars-priced bot interaction — the entire payment flow happens inline, no redirects. Source: telegram.org press kit.

Telegram bot payments are the simplest checkout flow on the internet. No card fields, no redirects, no SCA dance — the user taps Pay, confirms with biometrics, done.

This guide is the boring, complete version: every Bot API call you actually need, in order, with the error cases.

01

Pick your provider

Stars — no setup, just use currency: "XTR". Funds land in your bot’s Stars wallet. Fiat — talk to @BotFather → Payments and connect a provider (Stripe, Smart Glocal, YooKassa, Tranzzo, depending on geography). You’ll get a provider token to pass into invoices.

For 99% of digital products, start with Stars and skip the fiat dance entirely.

core.telegram.org Bot payments overview
02

Send an invoice

The simplest path is sendInvoice directly into a chat:

await fetch(`https://api.telegram.org/bot${TOKEN}/sendInvoice`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    chat_id: userId,
    title: "Pro plan, monthly",
    description: "Unlimited access for 30 days.",
    payload: `pro_${userId}_${Date.now()}`,
    currency: "XTR",
    prices: [{ label: "Pro", amount: 250 }],
  }),
});

payload is your private idempotency key — never expose it to the user, always include enough to identify the order on your side.

For Mini Apps, use createInvoiceLink instead, then call WebApp.openInvoice(url) from the client.

03

Handle pre-checkout

Telegram sends you a pre_checkout_query update before charging. You must answer within 10 seconds, or the payment auto-fails.

// inside your update handler
if (update.pre_checkout_query) {
  const ok = await canFulfill(update.pre_checkout_query.invoice_payload);
  await fetch(`https://api.telegram.org/bot${TOKEN}/answerPreCheckoutQuery`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      pre_checkout_query_id: update.pre_checkout_query.id,
      ok,
      error_message: ok ? undefined : "Out of stock — sorry.",
    }),
  });
}

Use this hook to: re-validate stock, check for fraud, confirm the user is not banned, lock inventory.

04

Grant entitlement on successful_payment

After charge, you receive message.successful_payment on the same chat. The payload is your idempotency key — dedupe on it.

if (update.message?.successful_payment) {
  const sp = update.message.successful_payment;
  await db.transaction(async (tx) => {
    const exists = await tx.payment.findUnique({ where: { payload: sp.invoice_payload } });
    if (exists) return;
    await tx.payment.create({ data: { ...sp, userId: update.message.chat.id } });
    await grantEntitlement(update.message.chat.id, sp.invoice_payload);
  });
  await sendMessage(update.message.chat.id, "Payment received — you're in. Tap /start to begin.");
}

Telegram retries successful_payment until you ack — your handler must be idempotent or you’ll grant access twice.

05

Refunds

Stars: refundStarPayment(user_id, telegram_payment_charge_id). Fully programmatic. Fiat: refund through your provider dashboard (Stripe etc.); Telegram doesn’t proxy refunds for fiat.

Build a /refund <order_id> admin command from day one. Without it, you’ll be doing manual SQL inside a week.

06

Recurring subscriptions

Two patterns:

Stars subscriptions (recommended) — pass subscription_period: 2592000 (30 days) on the invoice. Telegram bills the user automatically every period. You receive a successful_payment each cycle.

Manual recurring — schedule your own renewal reminders, send a fresh invoice each cycle. More work, but you control retries, dunning, and grace periods.

For SaaS-like products, Stars subscriptions are perfect. For high-ticket B2B, do manual recurring with fiat.

A working code walkthrough

If you prefer to see the implementation end-to-end before reading the rest, this third-party tutorial shows a complete Stars-payment flow inside a Mini App:

Independent tutorial. Walks through createInvoiceLink, openInvoice, and the successful_payment handler in code.

Production checklist

  • Idempotent successful_payment handler.
  • 10-second SLO on answerPreCheckoutQuery.
  • Refund admin command.
  • Webhook secret check on every incoming update (X-Telegram-Bot-Api-Secret-Token).
  • Ledger table separate from your business logic — every Stars/cents in and out, immutable.
  • Daily reconciliation job comparing getStarTransactions to your ledger.

That last one will save you the day a successful_payment retries to a bot you’ve redeployed without idempotency.

Stay in the loop

One short email when something useful ships. No tracking pixels, no upsell.