Build Telegram bots that earn Stars.
We run 35 Telegram bots on Cloudflare Workers. Here is how they take payment, how Mini Apps work, and how to get your own bot listed or built.
await tg("sendInvoice", {
chat_id: chatId,
title: "Pro",
description: "Unlock Pro for this group. One payment.",
payload: `pro:${chatId}`, // your own ID, 1-128 bytes
provider_token: "", // empty string for Stars
currency: "XTR", // Telegram Stars
prices: [{ label: "Pro", amount: 150 }], // exactly one item
}); That is the whole invoice. Telegram handles the checkout UI.
What you can do here
35 bots. One pattern.
Every bot follows the same path: a Telegram update hits a webhook on Cloudflare Workers, the bot answers, and paid features unlock after a Stars payment.
01 · People
Users in Telegram
Private chats, groups and channels.
02 · Transport
Telegram Bot API
Each update arrives as one HTTPS POST to a webhook.
- POSTupdate → webhook
- callsendMessage
- callsendInvoice · XTR
03 · Compute
Cloudflare Workers
One bot per job. 35 bots. No servers to patch.
04 · Revenue
Stars checkout
- 1 Invoicecurrency XTR
- 2 Pre-checkoutanswer < 10 s
- 3 Paymentsuccessful_payment
- 4 Pro onuser or group
Free
Free to use in Telegram. No sign-up. Pro is optional.
⭐ 75 or 150
One-time Pro in 30 bots. Paid in Stars inside the bot.
1 payment
Unlocks Pro for a whole group in 16 bots. Per user in 12, per chat in 2.
8
Bots that also sell a 30-day Stars subscription.
01
One bot, one job
Small bots are easy to name, easy to explain, easy to find in search. Each of ours does one thing.
02
Free first, Pro second
Everyone can use the free tier. 30 bots sell a one-time Pro in Stars, scoped per user or per group.
03
Stars only for digital goods
Telegram requires Stars (XTR) for digital goods in bots and Mini Apps. No card forms, no payment provider setup.
04
Edge, not servers
Webhooks land on Cloudflare Workers. No machines to patch. One deploy per bot.
The code that takes the money.
A Worker that sells Pro for Stars fits in under 100 lines. No SDK. Copy it, add your token, deploy.
- 1
Create a bot
Talk to @BotFather. Copy the token.
- 2
Deploy the Worker
wrangler deploy. Store the token as a secret.
- 3
Set the webhook
One curl call. Add a secret token.
- 4
Test in the test environment
Separate accounts, no real Stars.
# Point Telegram at your Worker. Telegram sends the secret back in a header.
curl "https://api.telegram.org/bot$BOT_TOKEN/setWebhook" \
-d "url=https://my-bot.example.workers.dev/" \
-d "secret_token=$WEBHOOK_SECRET" \
-d 'allowed_updates=["message","pre_checkout_query"]' // The two updates that matter for payments (excerpt of src/index.ts)
if (update.pre_checkout_query) {
const q = update.pre_checkout_query;
// Answer within 10 seconds, or Telegram cancels the payment.
await tg(env, "answerPreCheckoutQuery", {
pre_checkout_query_id: q.id,
ok: q.currency === "XTR" && q.total_amount === PRO_STARS,
error_message: "This invoice is out of date. Send /pro again.",
});
}
const p = update.message?.successful_payment;
if (p) {
// Save the charge ID first: refundStarPayment needs it.
await savePurchase(env, p.telegram_payment_charge_id, update.message.from.id, p.invoice_payload);
await unlockPro(env, p.invoice_payload); // per user or per group
} Excerpt. The full Worker, with tg(), /terms and /paysupport, is in the Stars guide. Checked against the Bot API docs on 2026-10-28.
// validate.ts — check Telegram.WebApp.initData on a Cloudflare Worker (Web Crypto, no deps)
const enc = new TextEncoder();
async function hmac(key: BufferSource, msg: string): Promise<ArrayBuffer> {
const k = await crypto.subtle.importKey("raw", key, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
return crypto.subtle.sign("HMAC", k, enc.encode(msg));
}
const hex = (b: ArrayBuffer) => [...new Uint8Array(b)].map((x) => x.toString(16).padStart(2, "0")).join("");
export async function validateInitData(initData: string, botToken: string, maxAgeSec = 3600) {
const params = new URLSearchParams(initData);
const hash = params.get("hash");
if (!hash) return null;
params.delete("hash");
// data-check-string: every other field, sorted by key, "key=value", joined by "\n"
const dataCheckString = [...params.entries()]
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
.map(([k, v]) => `${k}=${v}`)
.join("\n");
const secretKey = await hmac(enc.encode("WebAppData"), botToken); // key = "WebAppData"
const expected = hex(await hmac(secretKey, dataCheckString));
// Constant-time compare
if (expected.length !== hash.length) return null;
let diff = 0;
for (let i = 0; i < hash.length; i++) diff |= expected.charCodeAt(i) ^ hash.charCodeAt(i);
if (diff !== 0) return null;
const authDate = Number(params.get("auth_date"));
if (!authDate || Date.now() / 1000 - authDate > maxAgeSec) return null; // too old
return { user: JSON.parse(params.get("user") ?? "null"), startParam: params.get("start_param") };
} Try the checkout yourself.
These bots sell a monthly Stars subscription next to the one-time Pro. Open one in Telegram, pick the Pro option and see a real Stars invoice.
Have a bot? Need one?
Two ways to work with us. Pick the one that fits.
List your bot
You built a Telegram bot or Mini App. Send it to us. We test it, and if it fits, we list it where people look for bots like yours.
Submit your botHire us to build it
We build Telegram bots that take payment in Stars. The same stack runs our 35 bots: Cloudflare Workers, a free tier and a one-time Pro.
Get a quoteQuestions first? Message us in Telegram.