<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[TeleCrow Blog]]></title><description><![CDATA[TeleCrow Blog]]></description><link>https://telecrow.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6ad2b75ca305ffe669187a59/9b4ef79f-a0e0-46f9-a562-d9ca80872a93.png</url><title>TeleCrow Blog</title><link>https://telecrow.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Sun, 25 Oct 2026 00:44:01 GMT</lastBuildDate><atom:link href="https://telecrow.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How to accept Telegram Stars in your bot (the full flow, plus a no-code shortcut)]]></title><description><![CDATA[Originally published on DEV. If your Telegram bot sells anything digital (an ebook, a course, a preset pack, access to a private channel), Telegram expects…]]></description><link>https://telecrow.hashnode.dev/how-to-accept-telegram-stars-in-your-bot-the-full-flow-plus-a-no-code-shortcut</link><guid isPermaLink="true">https://telecrow.hashnode.dev/how-to-accept-telegram-stars-in-your-bot-the-full-flow-plus-a-no-code-shortcut</guid><category><![CDATA[telegram]]></category><category><![CDATA[telegram bot]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Marylyn Roundy]]></dc:creator><pubDate>Fri, 16 Oct 2026 23:52:32 GMT</pubDate><content:encoded><![CDATA[<p><em>Originally published on <a href="https://dev.to/marylyn_roundy_e32b126c99/how-to-accept-telegram-stars-in-your-bot-the-full-flow-plus-a-no-code-shortcut-2pa2">DEV</a>.</em></p>
<p>If your Telegram bot sells anything digital (an ebook, a course, a preset pack, access to a private channel), Telegram expects you to charge in <strong>Telegram Stars</strong>. Card providers are only allowed for physical goods. This post walks through the whole Stars payment flow with a small working example, the gotchas that cost people money, and how much you actually receive.</p>
<h2>Why Stars and not cards?</h2>
<p>Telegram's <a href="https://core.telegram.org/bots/payments-stars">payments page for digital goods</a> is clear: digital goods and services sold inside Telegram must be paid in Stars (currency code <code>XTR</code>). This follows the App Store and Play Store rules. Buyers get Stars through normal in-app purchases, so paying takes two taps and they never type a card number.</p>
<h2>The flow in six steps</h2>
<ol>
<li>Send an invoice with <code>sendInvoice</code> (or make a reusable link with <code>createInvoiceLink</code>), using <code>currency: "XTR"</code>.</li>
<li>When the buyer taps <strong>Pay</strong>, you receive a <code>pre_checkout_query</code> update.</li>
<li>Approve or reject it with <code>answerPreCheckoutQuery</code> <strong>within 10 seconds</strong>, or the payment is cancelled.</li>
<li>You then receive a message containing <code>successful_payment</code>.</li>
<li>Save <code>successful_payment.telegram_payment_charge_id</code>, because you need it for refunds.</li>
<li>Deliver the product.</li>
</ol>
<p>For digital goods you don't need a <code>provider_token</code>; leave it empty.</p>
<h2>A minimal working bot (Node 18+, no libraries)</h2>
<p>Save this as <code>bot.mjs</code> and run it with <code>BOT_TOKEN=... node bot.mjs</code>. It uses long polling, so you don't need a server or a webhook.</p>
<pre><code class="language-js">const TOKEN = process.env.BOT_TOKEN;
const api = (method, body) =&gt;
  fetch(`https://api.telegram.org/bot${TOKEN}/${method}`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  }).then((r) =&gt; r.json());

const delivered = new Set(); // use a real database in production

async function handle(update) {
  const msg = update.message;

  if (msg?.text === '/buy') {
    await api('sendInvoice', {
      chat_id: msg.chat.id,
      title: 'Preset pack',
      description: '12 photo presets, delivered instantly',
      payload: `order:${msg.from.id}:${Date.now()}`,
      currency: 'XTR',
      prices: [{ label: 'Preset pack', amount: 250 }], // 250 Stars
    });
  }

  if (update.pre_checkout_query) {
    // Check stock / that the order is still valid. You have 10 seconds.
    await api('answerPreCheckoutQuery', {
      pre_checkout_query_id: update.pre_checkout_query.id,
      ok: true,
    });
  }

  const paid = msg?.successful_payment;
  if (paid &amp;&amp; !delivered.has(paid.telegram_payment_charge_id)) {
    delivered.add(paid.telegram_payment_charge_id);
    await api('sendMessage', {
      chat_id: msg.chat.id,
      text: 'Thank you! Here is your download link: …',
    });
  }
}

let offset = 0;
for (;;) {
  const { result = [] } = await api('getUpdates', { offset, timeout: 30 });
  for (const u of result) {
    offset = u.update_id + 1;
    await handle(u).catch(console.error);
  }
}
</code></pre>
<p>To refund a buyer, call:</p>
<pre><code class="language-js">await api('refundStarPayment', { user_id, telegram_payment_charge_id });
</code></pre>
<h2>Gotchas that cost real money</h2>
<ul>
<li><strong>Deliver only on <code>successful_payment</code>.</strong> Answering the pre-checkout query does not mean the buyer paid.</li>
<li><strong>Deduplicate by <code>telegram_payment_charge_id</code>.</strong> Network retries happen, and you don't want to deliver twice or record a sale twice.</li>
<li><strong>Keep the pre-checkout handler fast.</strong> Ten seconds includes your database calls. Never put slow work there.</li>
<li><strong>Support and terms.</strong> Telegram's checklist asks bots that take payments to have a clear way to reach support (it mentions <code>/paysupport</code>) and easy access to your terms. You handle disputes and refunds yourself.</li>
<li><strong>Subscriptions.</strong> <code>createInvoiceLink</code> accepts <code>subscription_period</code>, which currently must be <code>2592000</code> (30 days). That's how you charge monthly for a private channel.</li>
<li><strong>Turn on two-step verification</strong> on the Telegram account that owns the bot. Telegram recommends it, and it's needed when you withdraw Stars.</li>
</ul>
<h2>How much do you actually receive?</h2>
<p>Buyers pay Apple or Google prices for Stars, and the app stores take their cut. When you withdraw through <a href="https://fragment.com">Fragment</a>, Telegram's rate is about <strong>$0.013 per Star</strong>, paid in TON, and new Stars can only be withdrawn after a holding period.</p>
<p>In practice:</p>
<ul>
<li>At <strong>50 Stars per $1</strong> (a common default), you receive about <strong>$0.65 per dollar</strong> of list price.</li>
<li>To receive about <strong>$1 per dollar</strong>, price at roughly <strong>77 Stars per $1</strong>.</li>
</ul>
<h2>The no-code shortcut</h2>
<p><em>Disclosure: I'm part of the team building TeleCrow.</em></p>
<p>If you'd rather not run and maintain the code above, <a href="https://telecrow.com">TeleCrow</a> is a no-code dashboard for this kind of bot. You connect your own bot's token from BotFather and turn on Stars in a few taps. The Stars go to <strong>your own bot's balance</strong>, and you withdraw them on Fragment yourself. TeleCrow handles:</p>
<ul>
<li>shops with automatic file delivery;</li>
<li>paid channels and groups with expiry, renewal reminders, trials and promo codes.</li>
</ul>
<p>There's a free plan, and TeleCrow takes 0% commission on sales.</p>
<p>Either way, the flow above is what happens under the hood. If you build it yourself, get the six steps and the gotchas right and Stars become a very smooth checkout.</p>
]]></content:encoded></item></channel></rss>