Payments
TMA.sh supports two payment methods: Telegram Stars for Telegram’s in-app currency and TON Connect for cryptocurrency payments.
Telegram Stars
Section titled “Telegram Stars”Telegram Stars is Telegram’s built-in digital currency. The SDK provides a first-class payments namespace that handles invoice creation, opening the payment sheet, and tracking the result — all without exposing your bot token to the client.
One-liner: tma.payments.stars.pay()
Section titled “One-liner: tma.payments.stars.pay()”The simplest way to accept Stars payments. Creates an invoice via the platform endpoint, opens the native Telegram payment sheet, and returns the result:
import { createTMA } from '@tma.sh/sdk';
const tma = createTMA({ projectId: 'my-project' });
const result = await tma.payments.stars.pay({ title: 'Premium Subscription', description: 'Access premium features for 30 days', payload: JSON.stringify({ userId: user.telegramId, plan: 'premium' }), prices: [{ label: 'Premium (30 days)', amount: 100 }],});
if (result.status === 'paid') { // Payment successful}The pay() method:
- Sends the invoice params to
POST /sdk/v1/payments/stars/invoice(the platform uses your project’s stored bot token) - Opens the native Telegram payment sheet via
WebApp.openInvoice() - Returns a
StarsPaymentResultwithstatus: 'paid' | 'cancelled' | 'failed' | 'pending'
Create invoice separately
Section titled “Create invoice separately”If you need the invoice URL without immediately opening it (for example, to share or store it):
const invoiceUrl = await tma.payments.stars.createInvoice({ title: 'Premium Subscription', description: 'Access premium features for 30 days', payload: JSON.stringify({ plan: 'premium' }), prices: [{ label: 'Premium (30 days)', amount: 100 }],});
// Open it laterwindow.Telegram.WebApp.openInvoice(invoiceUrl, (status) => { if (status === 'paid') { /* ... */ }});Invoice parameters
Section titled “Invoice parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
title | string | Yes | Product name (max 32 chars) |
description | string | Yes | Product description (max 255 chars) |
payload | string | Yes | Opaque data returned in webhooks (max 128 chars) |
prices | LabeledPrice[] | Yes | Array of { label, amount } where amount is in Stars |
photoUrl | string | No | URL of product photo |
photoWidth | number | No | Photo width in pixels |
photoHeight | number | No | Photo height in pixels |
React hook
Section titled “React hook”import { useStarsPayment, createTMA } from '@tma.sh/sdk/react';
const tma = createTMA({ projectId: 'my-project' });
function PurchaseButton() { const { pay, status, isLoading, error } = useStarsPayment();
const handlePurchase = async () => { await pay(tma, { title: 'Premium', description: '30-day access', payload: JSON.stringify({ plan: 'premium' }), prices: [{ label: 'Premium', amount: 100 }], }); };
return ( <button onClick={handlePurchase} disabled={isLoading}> {isLoading ? 'Processing...' : 'Buy Premium (100 Stars)'} </button> );}Svelte
Section titled “Svelte”<script> import { createStarsPayment, createTMA } from '@tma.sh/sdk/svelte';
const tma = createTMA({ projectId: 'my-project' }); const payment = createStarsPayment();
const handlePurchase = async () => { await payment.pay(tma, { title: 'Premium', description: '30-day access', payload: JSON.stringify({ plan: 'premium' }), prices: [{ label: 'Premium', amount: 100 }], }); };</script>
<button on:click={handlePurchase} disabled={$payment.isLoading}> {$payment.isLoading ? 'Processing...' : 'Buy Premium (100 Stars)'}</button>Server-side: typed methods
Section titled “Server-side: typed methods”If you need to create invoices or handle pre-checkout queries in your own API routes (rather than via the platform endpoint), use the typed methods on TelegramApiClient:
import { createTelegramApiClient } from '@tma.sh/sdk/server';
const telegram = createTelegramApiClient(c.env.BOT_TOKEN);
// Typed method (preferred over generic call())const invoiceUrl = await telegram.createInvoiceLink({ title: 'Premium', description: '30 days of premium', payload: JSON.stringify({ plan: 'premium' }), currency: 'XTR', prices: [{ label: 'Premium', amount: 100 }],});
// Answer pre-checkout query (must respond within 10 seconds)await telegram.answerPreCheckoutQuery(query.id, true);Bot context: sendInvoice
Section titled “Bot context: sendInvoice”In your bot handler, send an invoice directly to a chat:
import { defineBot } from '@tma.sh/sdk/bot';
export default defineBot({ commands: [ { command: 'buy', description: 'Purchase premium', handler: async (ctx) => { await ctx.sendInvoice({ title: 'Premium', description: '30-day access', payload: JSON.stringify({ userId: ctx.from?.id }), currency: 'XTR', prices: [{ label: 'Premium', amount: 100 }], }); }, }, ], onPreCheckoutQuery: async (ctx) => { await ctx.answerPreCheckoutQuery(true); },});Handle payment webhooks
Section titled “Handle payment webhooks”After a successful Stars payment, Telegram sends a pre_checkout_query and then a successful_payment update to your bot. TMA.sh automatically tracks stars_payment analytics events with campaign attribution when payments complete through the bot webhook.
TON Connect
Section titled “TON Connect”TON Connect lets users pay with TON cryptocurrency from their wallet. Use the @tonconnect/ui library directly — TON Connect is independent of the TMA SDK.
Installation
Section titled “Installation”bun add @tonconnect/uiConnect a wallet
Section titled “Connect a wallet”Before sending a transaction, the user must connect their TON wallet:
import { TonConnectUI } from '@tonconnect/ui';
const tonConnect = new TonConnectUI({ manifestUrl: 'https://your-app.tma.sh/tonconnect-manifest.json',});
// Opens the TON Connect wallet selectorawait tonConnect.connectWallet();This opens the standard TON Connect modal. The user selects their wallet app (Tonkeeper, MyTonWallet, etc.) and approves the connection.
Send a transaction
Section titled “Send a transaction”Once connected, send a transaction with the destination address and amount in nanotons:
const result = await tonConnect.sendTransaction({ validUntil: Math.floor(Date.now() / 1000) + 300, // 5 minutes messages: [ { address: 'UQ...', // recipient address amount: '1500000000', // nanotons (1.5 TON) }, ],});The user sees a confirmation screen in their wallet app before the transaction is submitted.
Disconnect
Section titled “Disconnect”To disconnect the wallet (for example, when the user logs out):
await tonConnect.disconnect();Amount conversion
Section titled “Amount conversion”TON amounts are specified in nanotons (1 TON = 1,000,000,000 nanotons). Some common values:
| TON | Nanotons |
|---|---|
| 0.1 | 100000000 |
| 1.0 | 1000000000 |
| 5.0 | 5000000000 |
| 10.0 | 10000000000 |
Choosing a payment method
Section titled “Choosing a payment method”| TON Connect | Telegram Stars | |
|---|---|---|
| Currency | TON cryptocurrency | Telegram Stars |
| User experience | Wallet confirmation | Native Telegram payment sheet |
| Server required | No (client-side) | Yes (invoice creation via API route) |
| Settlement | On-chain (instant) | Telegram balance |
| SDK dependency | @tonconnect/ui (independent) | @tma.sh/sdk/server |
| Best for | Crypto-native users, NFTs | Subscriptions, in-app items |