How to integrate Telegram Stars Payment to your bot
Telegram Stars is a new in-app currency introduced by Telegram for purchasing digital goods and services. Users can buy Stars directly within Telegram using Apple Pay or Google Pay. This article is a step-by-step guide on how to connect Stars payments to your bot, as well as GitHub example. While the code will be in Node.js, I'll show you the general ideas for accepting Stars payments so you can apply them to any language and framework.
Step 1. Create a bot
If you haven't created a bot yet, go to BotFather, create one, and grab your API bot token.
Step 2. Set up a project
You'll need a project that uses Telegram's bot API to issue invoices and handle successful payments. We'll use grammY, a bot framework for Node.js. Run the following commands:
mkdir telegram-stars-payment && cd telegram-stars-payment npm i grammy
Copy the following code to your index.mjs
and insert your bot token:
import { Bot } from "grammy"; const bot = new Bot(""); // <-- put your bot token between the "" bot.command("start", (ctx) => ctx.reply("Welcome! Up and running.")); bot.start();
Now run the bot via node index.mjs
. The bot should start responding to the start
command.
Step 3. Create an invoice
In terms of Telegram, an invoice is a special message type used in the Telegram Bot API to make payments within the chat. Regardless of which framework you're using, you can find these two methods for creating invoices:
sendInvoice
: The bot sends a message with a payment interface to the user, allowing them to complete the transaction within the chat. It includes details like the title, description, currency, prices, and other optional parameters such as photos and payload.createInvoiceLink
: This command generates a link to an invoice. When users click the link, they are redirected to a payment interface to complete the transaction. This method is useful for sharing the payment link outside the Telegram chat, such as in Telegram Mini Apps, emails, or on websites.
We'll stick to the first method for this example. Let's add a command to send invoices:
bot.command("pay", (ctx) => { return ctx.replyWithInvoice( "Test Product", // Product title "Test description", // Product description "{}", // Product payload, not required for now "XTR", // Stars Currency [{ amount: 1, label: "Test Product" }, // Product variants ]); });
You can now send /pay
to the bot, and it will respond with an invoice:
Step 4. Handle pre_checkout_query
Now we need to handle the payment. When a user buys Stars from Telegram and presses the pay button, the Bot API sends an update to the bot with all the order details in a field called pre_checkout_query
. The bot must respond with answerPreCheckoutQuery
within 10 seconds, or the transaction will be canceled.
If the bot can't process the order, it should send an error message explaining why in plain language (e.g., "Sorry, the item is out of stock. Would you like to order something else?"). Telegram will show this message to the user. For our example, we don't need that, so we'll just answer via answerPreCheckoutQuery
:
bot.on("pre_checkout_query", (ctx) => { return ctx.answerPreCheckoutQuery(true).catch(() => { console.error("answerPreCheckoutQuery failed"); }); });
Only after that Telegram will proceed with the payment.
Step 5. Handle successful payment
At this point, the user has already paid, and their money has been sent to you. Telegram will notify you who paid and for what, so you can mark this user as paid in your database. To get notified, you only need to search for an event like successful_payment
in your bot framework API.
// Map is used for simplicity. For production use a database const paidUsers = new Map(); bot.on("message:successful_payment", (ctx) => { if (!ctx.message || !ctx.message.successful_payment || !ctx.from) { return; } paidUsers.set( ctx.from.id, ctx.message.successful_payment.telegram_payment_charge_id, ); console.log(ctx.message.successful_payment); }); bot.command("status", (ctx) => { const message = paidUsers.has(ctx.from.id) ? "You have paid" : "You have not paid yet"; return ctx.reply(message); });
The basic version is ready! We've saved the information about successful payments into a JavaScript Map. Make sure to use a real database for production so the data is persistent between app reloads. We've saved the user's information alongside the telegram_payment_charge_id
. It's needed for refunds. Let's implement them:
bot.command("refund", (ctx) => { const userId = ctx.from.id; if (!paidUsers.has(userId)) { return ctx.reply("You have not paid yet, there is nothing to refund"); } ctx.api .refundStarPayment(userId, paidUsers.get(userId)) .then(() => { paidUsers.delete(userId); return ctx.reply("Refund successful"); }) .catch(() => ctx.reply("Refund failed")); });
Step 6. Adding Stars payment to Mini apps
Now since you know how to create invoices in bot's conversation let's switch to mini apps. The idea is completely the same; you just need to use createInvoiceLink
instead of replyWithInvoice
. The method createInvoiceLink
will generate a link that you can open using Telegram's Mini App SDK. Let's create a simple API for that. Our mini app is going to ask this API for an invoice to display:
import express from "express"; import { Bot } from "grammy"; const app = express(); const port = 3000; app.use(express.json()); const bot = new Bot(''); // Your bot token app.post("/generate-invoice", async (req, res) => { const title = "Test Product"; const description = "Test description"; const payload = "{}"; const currency = "XTR"; const prices = [{ amount: 1, label: "Test Product" }]; const invoiceLink = await bot.api.createInvoiceLink( title, description, payload, "", // Provider token must be empty for Telegram Stars currency, prices, ); res.json({ invoiceLink }); }); app.listen(port, () => { console.log(`Server running on port ${port}`); });
Now we can open this invoice link using one of the following Telegram Mini App SDKs:
// Call the endpoint we've just created const response = await apiGetLink(); WebApp.openInvoice(response.invoiceLink, (status) => { if (status === "paid") { // Telegram notified us that the payment has been made // Refresh user's balance, plan, etc } });
In the future I'll add an example for @telegram-apps
. This is how it works in one of my apps:
The code with instructions for the demo payment is available on GitHub: https://github.com/kubk/telegram-stars-example
Common Questions
How to buy Telegram Stars?
You can buy Telegram Stars in the conversation with the bot you've just created while following the article. You can also purchase Stars via @PremiumBot on the direct version of Telegram for Android, as well as Telegram’s Web and Desktop apps.
How to withdraw Stars that I got from users?
You can see your earned stars if you go to your Bot’s profile > Edit > Balance. This section is only visible after at least one payment is made (1 star is enough)
Then wait until 1000 stars have accumulated for 21 days and withdraw them to your TON wallet via Fragment. The process is pretty straightforward and can be done via 1 button. More details.
You can only withdraw after 3 weeks following the stars payment. This feature is likely designed for users who would like to request a refund from you for the stars payment.
1 star is always worth $0.013, regardless of current TON exchange rates. However once you exchange stars for TON, the $ price of your earnings will be affected by the current exchange rate.
Why did you use real payments for testing?
Because it's much easier than setting up a separate Telegram test account. 50 Telegram stars is around $1, and you can still refund them.
Any tips for production?
The article shows a simple example so you can get the basic idea. For production, I'd suggest a real database like PostgreSQL with 2 separate entities:
- Product with fields like name, price
- Order with fields like user_id (1-to-many), status ('initial', 'paid', 'failed'), product_id, product_price, product_name, telegram_payload for storing the Telegram data needed for refunds. Why denormalize the product's price and name? Because they may change in the future, and you want to save the real value at the moment of purchase to avoid confusion about how much users paid in the past. It's a common practice in such applications.
About the author
I am Egor, full-stack developer and a contributor to many popular open source libraries. I've built MemoCard - Award-winning Telegram mini app for improving memory with spaced repetition and AI. Give it a shot, it also accepts Telegram Stars.