Telegram Stars vs Stripe vs YooKassa for Mini Apps
Choosing Stars, Stripe, or YooKassa for Telegram Mini Apps impacts margins, compliance, and build time. Here’s the pragmatic, technical comparison and how to implement each.

Pick the wrong payment rail for your Telegram Mini App and you’ll leak margin, get blocked on iOS/Android policies, or sink weeks into rework. Payments are not a skin you can peel off later without cost.
If you want the quick answer: use Telegram Stars for purely digital goods inside Telegram (lowest policy risk, native UX); use Stripe for global fiat coverage, subscriptions, and invoicing; use YooKassa when you need Russian domestic rails (MIR) and localized flows. Many teams ship a dual-rail setup: Stars for in-Telegram digital SKUs, Stripe/YooKassa for everything else.
What you’re choosing between
- Telegram Stars: Telegram’s in-app currency for bots and Mini Apps. Ideal for digital goods and services consumed within Telegram. Minimizes App Store policy risk on iOS/Android because users buy Stars via Telegram’s own channels. Payout typically requires off-ramping and its own KYC/compliance steps.
- Stripe: Broad international coverage, strong subscriptions and invoicing, clean APIs, excellent webhooks. Merchant onboarding required. Best fit for global fiat, cards, wallets, and structured billing.
- YooKassa: The de facto choice for Russia domestic payments. Supports MIR and local methods. Merchant onboarding in Russia, localized compliance. Less flexible globally, strong locally.
From a Mini App operator’s perspective, the big levers are: policy compliance (especially digital goods on mobile), fees and settlement friction, developer velocity, fraud/chargebacks, and UX (no context-switch).
Fast decision guide
- You sell digital goods strictly inside Telegram (stickers, boosts, premium features, digital content): Telegram Stars first. It’s the least likely to be tripped by app store rules. Use Stripe/YooKassa only for desktop web or out-of-app flows if needed.
- You sell subscriptions, invoices, or B2B payments, and you need clear receipts and tax data: Stripe. Add Stars as a secondary rail if you also want in-chat impulse upgrades.
- You operate in Russia and need MIR or local bank rails: YooKassa. Consider adding Stars for in-telegram digital SKUs.
- You need the simplest MVP: Stars for in-app goods; Stripe for everything else. Defer YooKassa unless your audience demands it.
One dry-wit aside: choosing a single rail “to keep it simple” is how you end up with three rails in Q3.
Reference architectures for each rail
A. Telegram Stars flow (Mini App + Bot API)
Core idea: keep the purchase in Telegram. Your backend records an order, generates a Stars invoice, the client opens it in Telegram, and Telegram notifies your bot on completion.
- Client: Telegram WebApp JS
- Server: Node.js/Express (or your stack of choice)
- Storage: Postgres (orders, entitlements)
- Telegram: Bot API for invoices and webhook updates
High-level flow:
- Client asks server for an invoice for a SKU.
- Server creates an invoice link via Bot API using a Stars currency and returns it.
- Client opens the invoice in Telegram; user pays with Stars.
- Telegram sends payment confirmation to your bot webhook.
- Server verifies the update, marks the order paid, unlocks the entitlement.
B. Stripe flow (Checkout Session or Payment Intents)
- Client: Your Mini App webview opens Stripe Checkout or embedded payment element in a secure page.
- Server: Create Checkout Session / PaymentIntent; store
order_idandpayment_idatomically. - Webhook: Verify signature, update
paid_atidempotently. - Extras: Subscriptions via Billing, invoices, proration, SCA/3DS handled.
C. YooKassa flow
- Client: Redirect to YooKassa payment page or embedded widget.
- Server: Create a payment in RUB (or supported local methods), pass
return_url. - Webhook: Handle asynchronous notifications from YooKassa; update entitlements.
- Extras: MIR support and Russia-localized options.
D. Dual-rail abstraction
Unify Stars and fiat behind a simple service interface. Persist all orders with a canonical status machine: created -> pending -> paid -> fulfilled -> refunded/failed. Keep idempotency keys across rails.
// rails.ts
export type Rail = 'stars' | 'stripe' | 'yookassa';
export interface Order {
id: string;
userId: string;
sku: string;
amountCents: number; // store minor units
currency: string; // 'XTR' for Stars-like invoicing; 'USD', 'RUB', ... for fiat
rail: Rail;
status: 'created' | 'pending' | 'paid' | 'fulfilled' | 'refunded' | 'failed';
}
export interface PaymentService {
createPaymentLink(order: Order): Promise<{ url: string }>; // Stars, Stripe Checkout, or YooKassa URL
handleWebhook(payload: any, headers: any): Promise<void>; // idempotent state transitions
}
Creating a Stars invoice link
For Stars-based invoices in a Mini App, you typically create an invoice via the Bot API and open it in the WebApp context. Use the appropriate currency for Stars (check current Bot API docs) and price amounts in minor units.
// stars.ts (Node)
import TelegramBot from 'node-telegram-bot-api';
const bot = new TelegramBot(process.env.BOT_TOKEN!, { polling: false });
export async function createStarsInvoiceLink(params: {
title: string;
description: string;
payload: string; // your order id
amount: number; // in minor units of Stars
}): Promise<string> {
const link = await (bot as any).createInvoiceLink({
title: params.title,
description: params.description,
payload: params.payload,
currency: 'XTR', // Stars-like currency code per Telegram docs
prices: [{ label: params.title, amount: params.amount }],
});
return link;
}
Client side in the Mini App:
// webapp.js
async function payWithStars(orderId) {
const res = await fetch('/api/orders/' + orderId + '/stars-link');
const { url } = await res.json();
Telegram.WebApp.openInvoice(url, (status) => {
if (status === 'paid') {
// Optimistically poll your backend for entitlement
window.location.reload();
}
});
}
On the server, handle bot webhook updates for payment confirmations. Always verify the update signature via Telegram’s Bot API recommendations and apply idempotency when marking orders paid.
Stripe webhook verification (Express)
// stripe-webhook.ts
import express from 'express';
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { apiVersion: '2023-10-16' });
const app = express();
// Use raw body for Stripe signature verification
app.post('/webhooks/stripe', express.raw({ type: 'application/json' }), async (req, res) => {
const signature = req.headers['stripe-signature'] as string;
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(req.body, signature, process.env.STRIPE_WEBHOOK_SECRET!);
} catch (err) {
return res.status(400).send(`Webhook Error: ${(err as Error).message}`);
}
if (event.type === 'checkout.session.completed' || event.type === 'payment_intent.succeeded') {
const data = event.data.object as any;
const orderId = data.metadata?.order_id;
// Idempotently mark order as paid
await markOrderPaid(orderId, data.id);
}
res.json({ received: true });
});
async function markOrderPaid(orderId?: string, paymentId?: string) {
if (!orderId) return;
// UPDATE orders SET status='paid', payment_id=$1 WHERE id=$2 AND status IN ('created','pending')
}
YooKassa create payment example
// yookassa.ts
import fetch from 'node-fetch';
export async function createYooPayment(order: { id: string; amount: number; returnUrl: string }) {
const response = await fetch('https://api.yookassa.ru/v3/payments', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Idempotence-Key': order.id,
'Authorization': 'Basic ' + Buffer.from(process.env.YK_SHOP_ID + ':' + process.env.YK_SECRET).toString('base64'),
},
body: JSON.stringify({
amount: { value: (order.amount / 100).toFixed(2), currency: 'RUB' },
confirmation: { type: 'redirect', return_url: order.returnUrl },
capture: true,
description: `Order ${order.id}`,
metadata: { order_id: order.id },
}),
});
if (!response.ok) throw new Error('YooKassa error');
return response.json();
}
Feature and trade-off comparison
| Dimension | Telegram Stars | Stripe | YooKassa |
|---|---|---|---|
| Best for | Purely digital in-telegram goods | Global fiat, subscriptions, invoices | Russia domestic (MIR, local methods) |
| UX | Native in Telegram, no context switch | External checkout/page in webview | External redirect/page |
| App store policy risk (mobile) | Lowest for digital goods in Telegram | Potential risk for digital goods in-app; safer for physical/B2B | Similar to Stripe; check local guidance |
| Coverage | Global users with Telegram balance | 40+ countries supported for merchants; many payment methods | Primarily Russia-focused |
| Chargebacks | N/A in classic sense; Stars are redeemed | Full chargebacks/disputes | Disputes supported per local rules |
| Refunds | In-ecosystem refund/compensation model | Full refunds/partial refunds via API | Refunds via API |
| Subscriptions | Limited; you roll your own state machine | Mature Billing + proration | Possible, less turnkey |
| Merchant onboarding | Low for Stars usage; off-ramp requires KYC | Merchant account and KYC | Merchant account and Russia KYC |
| Settlement | Convert Stars via supported off-ramps | Bank settlements in supported currencies | Bank settlements in RUB |
| Tax/compliance artifacts | Limited fiscal docs | Invoices, receipts, tax settings | Local fiscalization options |
| Dev velocity | Fast for in-telegram digital SKUs | Moderate; more surface area | Moderate; localized docs |
Important: exact fees, payout methods, and API specifics change. Always check the latest provider docs for your region and use case.
Compliance and policy angles you can’t ignore
- Digital goods on iOS/Android: If the user is paying inside the Telegram app for purely digital content, in-app purchase rules from Apple/Google are strict. Telegram Stars exists to keep such purchases compliant inside Telegram’s ecosystem. Using external processors (Stripe/YooKassa) for in-app digital goods can be risky on mobile. Routing fiat to the web outside the mobile app mitigates some risk.
- Physical goods/services: Stripe/YooKassa are a better fit. Stars are meant for digital value in Telegram, not shipping logistics.
- Receipts and taxes: If your buyers need VAT/GST-compliant invoices or corporate billing, Stripe is the straightforward option. YooKassa offers local fiscalization for Russia. Stars is limited for formal invoicing; you’ll need to generate your own receipts and handle taxes.
- KYC/AML: Stars redemption requires off-ramping to fiat or crypto, which triggers KYC somewhere (exchange/off-ramp). Stripe/YooKassa require merchant KYC upfront.
- Consumer rights and refunds: Stripe/YooKassa have well-defined dispute/refund flows. With Stars, design your own refund/compensation UX and document it clearly in your Mini App.
Data model and order lifecycle
Maintain a consistent order and entitlement model across rails:
- orders: id, user_id, sku, amount_cents, currency, rail, status, payment_id, created_at, paid_at
- entitlements: id, user_id, sku, order_id, active, started_at, expires_at
- events (audit): id, order_id, type, payload, created_at
State transitions:
- created → pending: when you generate a link or create a payment intent
- pending → paid: webhook/Stars confirmation, idempotent update
- paid → fulfilled: your system grants access
- paid → refunded: refund API call confirmed
Use idempotency keys for all external calls. Deduplicate webhook deliveries. For Stars, treat each paid update as a single source of truth event.
Production pitfalls and patterns
- Webhook reliability: Timeouts and retries happen. Don’t do heavy work synchronously in webhook handlers. Persist an event, ack 200, then process asynchronously.
- Floating point errors: Always store amounts in integer minor units (cents). Never use floating point arithmetic for money.
- Currency codes: Don’t mix Stars currency with fiat. We use
currency='XTR'in the example; gate your logic by rail to avoid accidental conversions. - Race conditions: Users can click multiple times or have multiple pending payments. Use unique constraints on
(user_id, sku, status in ('created','pending'))to limit duplicates. - Double fulfillment: Guard
paid -> fulfilledwith a transaction checking current status. - Regional outages: Plan for fallback rails. If Stripe is degraded for a region, allow Stars as a temporary option for digital SKUs.
- Refund entropy: Stripe partial refunds are straightforward; YooKassa refund timing depends on method; Stars refunds are UX-driven—communicate clearly.
- Ledger reconciliation: Nightly reconcile your orders with provider reports. Build a small utility that flags mismatches for manual review.
- Security: Verify Telegram WebApp
initDataon your server when attributing a client session to a Telegram user. For Stripe, always verify webhook signatures. For YooKassa, use idempotence keys and verify notification signatures.
We’ve summarized some complementary production lessons in our post on JVM backends; the failures rhyme across stacks even if languages don’t: Spring Boot production lessons.
Cost and ROI considerations
- Fees and effective margin: Stripe and YooKassa fees depend on region and method. Budget a few percent plus fixed fees for cards; local methods vary. Stars purchasing routes through Telegram’s channels; the user’s acquisition of Stars can carry store/platform fees upstream. Your net from Stars then subtracts off-ramp fees if you convert to fiat. Model both flows with a spreadsheet for your actual markets.
- Churn and retry logic: Stripe’s subscription tooling (dunning, retries, smart retries) reduces involuntary churn. Stars doesn’t give you that machinery—you’ll build renewal prompts and expiry checks yourself.
- Engineering time: Stars integration for in-telegram digital unlocks is fast and reduces policy headaches. Stripe/YooKassa require more time (webhooks, receipts, taxes) but unlock broader business models.
- Settlement and treasury: With Stars, your treasury risk is timing and conversion costs. With Stripe/YooKassa, you manage bank payouts and reconciliation. Don’t underestimate monthly close effort.
- Localization and conversion: For Russia, YooKassa boosts conversion with local rails. For global audiences, Stripe’s wallets and local methods matter. Minimal friction compounds to real revenue.
If you’re building a high-velocity MVP, ship Stars first for digital entitlements, Stripe for fiat expansions, and defer advanced billing logic until you have usage data. Build the abstraction upfront so you don’t rewrite the app when adding a new rail—learn by shipping, then formalize: See something that works, then understand it.
Implementation checklist
- Define SKUs and map to rails: which are Stars-eligible (digital-only)? Which need fiat?
- Build a payment service interface and keep order states uniform.
- Implement Stars invoice creation + bot webhook handling; verify Telegram data.
- Implement Stripe Checkout and webhook; store idempotency keys.
- Implement YooKassa if needed; redirect flows + webhook handler.
- Add entitlement service; gate features by entitlement.
- Instrument analytics at each step (link generated, opened, paid, fulfilled).
- Reconciliation job + admin tools for manual grants/refunds.
- Legal pages: terms, refunds, privacy. List specific flows by rail.
What breaks in production
- Users on iOS can pay with Stars but balk at external card flows inside Telegram. Offer Stars prominently for digital goods; route fiat to a web context to reduce policy risk.
- Mispriced currencies: forgetting to convert to minor units leads to 100x overcharges. Add unit tests for pricing conversions.
- Webview quirks: some mobile devices limit third-party cookies or block redirects. Prefer hosted Checkouts that support SCA. If embedding, keep a fallback.
- Duplicate webhooks: At scale, expect 1–3 duplicates per thousand events. Make your handlers idempotent.
- Clock skew: If you expire invoices too soon, slow networks cause user frustration. Keep reasonable windows and display a countdown.
- Customer support load: Stars refunds are not one-click from a payment processor. Provide in-app refund buttons that file a ticket and implement compensations quickly.
When each rail is a bad fit
- Stars is a bad fit when buyers need fiscal receipts, B2B invoicing, or when goods are physical. Also, if your finance team must avoid crypto/off-ramps, pre-clear the policy.
- Stripe is a bad fit if 80% of your market is Russia domestic—local methods outperform.
- YooKassa is a bad fit for global expansion or if you need advanced subscription tooling.
Putting it together: a minimal multi-rail flow
- Catalog screen shows SKUs. Digital-only SKUs display a “Pay with Stars” button. The same SKU on desktop web may also show “Pay with card” via Stripe.
- The server generates either a Stars invoice link or a Stripe Checkout Session.
- On success, the server marks the order paid and issues the entitlement with a TTL if subscription-like.
- A nightly job emails Stripe receipts (for fiat) and generates in-app receipts for Stars purchases.
- Admin panel allows manual grant/revoke and refunds per rail.
This approach lets you start simple while avoiding costly refactors. The abstraction is a few hundred lines; the business impact is immediate.
FAQ
Q: Can I mix Stars and Stripe for the same SKU? A: Yes. For digital SKUs, offer Stars in-telegram and Stripe on desktop web. Keep separate price IDs and map both to one internal SKU.
Q: How do I verify a Telegram Mini App user on my server?
A: Use Telegram WebApp initData. Send it to your server, compute and verify the HMAC per Telegram’s docs, and resolve the Telegram user ID before creating orders.
Q: Do Stars support subscriptions? A: There’s no turnkey subscription engine. You can implement a recurring entitlement by selling time-limited access and prompting renewal. For automated billing and retries, use Stripe.
Q: What about chargebacks with Stars? A: Traditional chargebacks don’t apply the same way. You should build your own refund/compensation flow and clearly state policies. For fiat disputes, Stripe/YooKassa have standard processes.
Q: How do I handle taxes for Stars purchases? A: You’ll likely need to generate your own receipts and account for taxes in your jurisdiction. For formal invoices and VAT/GST handling, Stripe/YooKassa are easier.
Q: Is YooKassa necessary if I already use Stripe? A: If your audience is Russia-heavy and you need MIR/local methods, yes. Otherwise, Stripe may suffice. Look at conversion metrics before adding complexity.
Key takeaways
- Use Telegram Stars for digital goods consumed inside Telegram to minimize mobile policy risk and ship fast.
- Use Stripe for global fiat coverage, subscriptions, invoices, and rich billing operations.
- Use YooKassa for Russia domestic payments and MIR; it materially boosts local conversion.
- Abstract rails behind a common order/entitlement model; enforce idempotency and reconciliation.
- Model fees, settlements, and refunds per rail before launch—finance friction erodes margin faster than you think.
If you’re building a Telegram Mini App with payments, MTBYTE can help design the rails, integrate Stars/Stripe/YooKassa, and harden them for production. Reach out at /contact to discuss your architecture and timeline.
NEXT STEP
Liked the approach?
We apply the same principles to client projects: AI, automation, products that don't die after launch.