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.
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.
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 ↗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.
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.
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.
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.
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:
Production checklist
- Idempotent
successful_paymenthandler. - 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
getStarTransactionsto your ledger.
That last one will save you the day a successful_payment retries to a bot you’ve redeployed without idempotency.
Read next
Ship a Telegram Mini App in 48 hours
A practical, end-to-end walkthrough — from BotFather to deploy — for shipping your first Telegram Mini App with auth, payments, and a real distribution plan.
Mini App monetization patterns that work (and 5 that don't)
A taxonomy of every Mini App monetization pattern in production today, with conversion rates, ARPU expectations, and the patterns to avoid.
The Telegram glossary — every term that matters in 2026
A reference glossary of every Telegram concept, API surface, and product feature you need to know — with one-sentence definitions and direct links to the official docs.