<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Haskell Thurber</title>
    <description>The latest articles on DEV Community by Haskell Thurber (@haskelldev).</description>
    <link>https://dev.to/haskelldev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3801315%2F0286bef5-62ed-4712-a865-c3f526bc52ea.png</url>
      <title>DEV Community: Haskell Thurber</title>
      <link>https://dev.to/haskelldev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/haskelldev"/>
    <language>en</language>
    <item>
      <title>How I Built a Referral System That Pays Users in Telegram Stars (And Actually Drives Growth)</title>
      <dc:creator>Haskell Thurber</dc:creator>
      <pubDate>Thu, 05 Mar 2026 09:18:40 +0000</pubDate>
      <link>https://dev.to/haskelldev/how-i-built-a-referral-system-that-pays-users-in-telegram-stars-and-actually-drives-growth-cil</link>
      <guid>https://dev.to/haskelldev/how-i-built-a-referral-system-that-pays-users-in-telegram-stars-and-actually-drives-growth-cil</guid>
      <description>&lt;p&gt;Most referral systems are an afterthought. A "Share with friends" button buried in settings that nobody ever clicks. I wanted mine to be different — and the results surprised me.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem With Traditional Referrals
&lt;/h2&gt;

&lt;p&gt;I built &lt;a href="https://whisprme.app?src=devto3" rel="noopener noreferrer"&gt;WhisprMe&lt;/a&gt;, an anonymous messaging Telegram Mini App. The concept is simple: you share your personal link, people send you anonymous messages, you read and reply.&lt;/p&gt;

&lt;p&gt;The traditional approach would be: build the app, add a referral page later, offer some discount. But here's the thing — &lt;strong&gt;the referral IS the product&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Users can't receive anonymous messages without sharing their link. Every single user becomes a distribution channel by default. There's no separate "invite friends" flow because sharing is the core action.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Referral Revenue Works
&lt;/h2&gt;

&lt;p&gt;When User A shares their link and User B signs up through it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User B signs up → A becomes B's referrer
User B buys premium (50 Telegram Stars) → A earns 15% (7 Stars)
User B buys anything else → A earns 15% of that too
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key insight: &lt;strong&gt;pay referrers in the same currency users spend&lt;/strong&gt;. Telegram Stars are the native payment method, so referral earnings are also in Stars. No PayPal, no bank transfers, no minimum thresholds. It's frictionless.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Database Schema
&lt;/h2&gt;

&lt;p&gt;Here's how I track referrals in PostgreSQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Users table has referral fields&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;referred_by&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;telegram_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;referral_earnings&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Track the chain: who referred who&lt;/span&gt;
&lt;span class="c1"&gt;-- When user B purchases, find their referrer and credit 15%&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The referral is captured at signup time from a deep link parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Bot handles /start with referral parameter&lt;/span&gt;
&lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;start&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;referralCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;referralCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;referrer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT telegram_id FROM users WHERE telegram_id = $1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;referralCode&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;referrer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Store the referral relationship&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UPDATE users SET referred_by = $1 WHERE telegram_id = $2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;referrer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;telegram_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Payment Flow with Telegram Stars
&lt;/h2&gt;

&lt;p&gt;When a referred user makes a purchase, the referrer's earnings update automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;successful_payment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;successful_payment&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;total_amount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// in Stars&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buyerId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Find referrer&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT referred_by FROM users WHERE telegram_id = $1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;buyerId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;referred_by&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;referralEarning&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UPDATE users SET referral_earnings = referral_earnings + $1 WHERE telegram_id = $2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;referralEarning&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;referred_by&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The beauty of Telegram Stars: &lt;code&gt;createInvoiceLink()&lt;/code&gt; handles the entire payment flow. No Stripe webhooks, no payment provider configuration. One API call.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned About Viral Coefficients
&lt;/h2&gt;

&lt;p&gt;After launching, here's what the data showed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Average shares per user&lt;/strong&gt;: ~3.2 (users share their link in stories, groups, DMs)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conversion from link click to signup&lt;/strong&gt;: ~18%&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;K-factor&lt;/strong&gt;: ~0.58 (each user brings 0.58 new users on average)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A K-factor below 1.0 means the app doesn't grow purely virally. But combined with organic discovery and content marketing, it creates a &lt;strong&gt;compounding effect&lt;/strong&gt;. Each new user from content marketing multiplies through referrals.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Leaderboard Effect
&lt;/h2&gt;

&lt;p&gt;One thing that massively boosted sharing: a &lt;strong&gt;public leaderboard&lt;/strong&gt; showing top users by messages received. People compete to get more anonymous messages, which means more sharing, which means more signups.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Leaderboard query&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;topUsers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
  SELECT username, display_name, 
         (SELECT COUNT(*) FROM messages WHERE recipient_id = u.telegram_id) as msg_count
  FROM users u
  WHERE is_banned = false
  ORDER BY msg_count DESC
  LIMIT 20
`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gamification layer turned passive users into active promoters without any additional incentive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Build referrals INTO the product&lt;/strong&gt;, not alongside it. If sharing is optional, most users won't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pay in native currency&lt;/strong&gt;. Telegram Stars → Star earnings. No friction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;15% is the sweet spot&lt;/strong&gt;. High enough to motivate, sustainable enough to maintain margins.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leaderboards drive sharing&lt;/strong&gt; better than referral bonuses alone. Social proof &amp;gt; monetary incentive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Track K-factor obsessively&lt;/strong&gt;. Even sub-1.0 is valuable — it's a multiplier on every other growth channel.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Tech Stack
&lt;/h2&gt;

&lt;p&gt;For anyone curious about the full setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backend&lt;/strong&gt;: Node.js + Express + PostgreSQL&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend&lt;/strong&gt;: React (Telegram Mini App)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bot&lt;/strong&gt;: Telegraf.js&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payments&lt;/strong&gt;: Telegram Stars (no external providers)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hosting&lt;/strong&gt;: $5/mo VPS with nginx&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The entire referral system was about 200 lines of code. The hardest part wasn't building it — it was designing a product where sharing is the natural first action.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you're building a Telegram Mini App and want to see how this works in practice, check out &lt;a href="https://whisprme.app?src=devto3" rel="noopener noreferrer"&gt;WhisprMe&lt;/a&gt;. Send yourself an anonymous message — the referral link on your profile page is how the viral loop starts.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>telegrambot</category>
      <category>startup</category>
      <category>webdev</category>
      <category>growth</category>
    </item>
    <item>
      <title>I Replaced Stripe With Telegram Stars — Here's What Happened to My Conversion Rate</title>
      <dc:creator>Haskell Thurber</dc:creator>
      <pubDate>Tue, 03 Mar 2026 14:14:37 +0000</pubDate>
      <link>https://dev.to/haskelldev/i-replaced-stripe-with-telegram-stars-heres-what-happened-to-my-conversion-rate-56ia</link>
      <guid>https://dev.to/haskelldev/i-replaced-stripe-with-telegram-stars-heres-what-happened-to-my-conversion-rate-56ia</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;I built a Mini App inside Telegram and used Telegram Stars for micropayments instead of traditional payment providers. The result: zero payment friction, working micropayments at $0.02, and a payment integration that's embarrassingly simple.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem With Traditional Micropayments
&lt;/h2&gt;

&lt;p&gt;I needed to charge users $0.02–$0.50 for individual actions in my app. If you've ever tried this with Stripe, you know the pain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Minimum transaction fees&lt;/strong&gt; eat your margin on anything under $1&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Checkout abandonment&lt;/strong&gt; — every redirect, every form field costs you 30%+ of users&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PCI compliance&lt;/strong&gt; — even with Stripe Elements, there's overhead&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Account creation&lt;/strong&gt; — users need to sign up, verify email, enter card details&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a social app where the entire experience should feel instant, this friction kills engagement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Telegram Stars
&lt;/h2&gt;

&lt;p&gt;Telegram introduced &lt;a href="https://core.telegram.org/bots/payments#the-stars-currency" rel="noopener noreferrer"&gt;Stars&lt;/a&gt; — a built-in micropayment currency for Mini Apps and bots. 1 Star ≈ $0.02. Users buy Stars through Apple/Google in-app purchases or directly through Telegram.&lt;/p&gt;

&lt;p&gt;Here's what makes it different:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Zero Checkout Friction
&lt;/h3&gt;

&lt;p&gt;Users tap "Pay 1 ⭐" → confirmation dialog → done. No card entry, no redirect, no account creation. The payment happens inside the same chat/app they're already using.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Traditional: Open app → See paywall → Redirect to checkout → 
Enter email → Enter card → Confirm → Wait → Return to app
(7 steps, ~60% dropout)

Stars: See paywall → Tap "Pay 1⭐" → Confirm → Done
(3 steps, ~5% dropout)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Micropayments Actually Work
&lt;/h3&gt;

&lt;p&gt;The minimum Stars payment is &lt;strong&gt;1 Star ($0.02)&lt;/strong&gt;. Try charging $0.02 on Stripe — between the $0.30 fixed fee and 2.9% variable fee, you'd &lt;strong&gt;lose $0.28 per transaction&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;With Stars, Telegram takes ~30% (similar to App Store), but there's no fixed fee. A 1-Star payment nets you ~$0.014. Small, but profitable.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The Integration Is Embarrassingly Simple
&lt;/h3&gt;

&lt;p&gt;Here's the complete payment flow in Node.js with Telegraf:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Step 1: Create an invoice link&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoiceLink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;telegram&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createInvoiceLink&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;'Unlock Message',&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;'Read the full anonymous message',&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;messageId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;provider_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Yes, empty string. That's it.&lt;/span&gt;
  &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;XTR&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;// Stars currency code&lt;/span&gt;
  &lt;span class="na"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unlock&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;  &lt;span class="c1"&gt;// 1 Star&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Step 2: Handle pre-checkout (validate the purchase)&lt;/span&gt;
&lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pre_checkout_query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;answerPreCheckoutQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Step 3: Handle successful payment&lt;/span&gt;
&lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;successful_payment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;successful_payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoice_payload&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;unlockMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messageId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Message unlocked!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. &lt;strong&gt;Three events.&lt;/strong&gt; No webhook signing secrets, no idempotency keys, no retry logic. Compare this to a &lt;a href="https://stripe.com/docs/payments/accept-a-payment" rel="noopener noreferrer"&gt;Stripe integration guide&lt;/a&gt; that spans multiple pages.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Built-in Refunds
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Refund a Stars payment&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;telegram&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refundStarPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;// Telegram user ID&lt;/span&gt;
  &lt;span class="nx"&gt;paymentChargeId&lt;/span&gt;   &lt;span class="c1"&gt;// From successful_payment event&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One function call. No disputes, no chargebacks, no evidence submission.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built: WhisprMe
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://t.me/WhisprMe_bot?start=src_devto2" rel="noopener noreferrer"&gt;WhisprMe&lt;/a&gt; is an anonymous messaging app inside Telegram. You share a link, friends send you anonymous messages, and you unlock them with Stars.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing tiers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 Star — Read full message (see 3-word preview for free)
&lt;/li&gt;
&lt;li&gt;5 Stars — Get a hint about the sender&lt;/li&gt;
&lt;li&gt;15 Stars/week — Plus subscription (unlimited reads)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Revenue streams (all Stars):&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Message unlocks&lt;/li&gt;
&lt;li&gt;Sender hints
&lt;/li&gt;
&lt;li&gt;Plus/Pro subscriptions&lt;/li&gt;
&lt;li&gt;Virtual gifts (Hearts, Fire, Crowns)&lt;/li&gt;
&lt;li&gt;Referral commissions (15-25% of referred users' purchases)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The referral system uses Telegram's native &lt;a href="https://core.telegram.org/bots/api#staraffiliateprogram" rel="noopener noreferrer"&gt;Affiliate Program API&lt;/a&gt;, so payments to referrers happen automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;p&gt;The entire backend is ~2000 lines of JavaScript:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Runtime:&lt;/strong&gt; Node.js + Express&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database:&lt;/strong&gt; PostgreSQL (raw SQL, 6 tables)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bot framework:&lt;/strong&gt; Telegraf.js&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; React (served as Telegram Mini App)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Process manager:&lt;/strong&gt; PM2 cluster mode&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hosting:&lt;/strong&gt; Single $5/month VPS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No Redis. No message queue. No microservices. The entire thing runs on one server and handles everything I need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Lessons
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Auth is free
&lt;/h3&gt;

&lt;p&gt;Telegram Mini Apps provide &lt;code&gt;initData&lt;/code&gt; — a signed payload with user info. Verify the HMAC signature server-side, and you have authenticated users without building any auth system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Distribution is built in
&lt;/h3&gt;

&lt;p&gt;Users share links inside Telegram chats. The sharing IS the product — you can't get anonymous messages without sharing your link. This creates a natural viral loop without any marketing spend.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monetization ceiling exists
&lt;/h3&gt;

&lt;p&gt;Stars are great for micropayments but limited for high-ticket items. The maximum single payment is 10,000 Stars (~$200). If your product needs $500+ transactions, Stars won't work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Telegram takes 30%
&lt;/h3&gt;

&lt;p&gt;Same as Apple/Google. After Telegram's cut, 1 Star nets you ~$0.014. Plan your pricing accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Should Use Stars?
&lt;/h2&gt;

&lt;p&gt;Stars work best for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Social apps with micropayments ($0.02–$5 range)&lt;/li&gt;
&lt;li&gt;Content unlocking / paywalls&lt;/li&gt;
&lt;li&gt;Digital goods and virtual items&lt;/li&gt;
&lt;li&gt;Subscription-based services&lt;/li&gt;
&lt;li&gt;Tipping and donations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stars &lt;strong&gt;don't&lt;/strong&gt; work for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Physical goods (use Stripe/PayPal payment bots instead)&lt;/li&gt;
&lt;li&gt;High-ticket B2B ($200+ per transaction)&lt;/li&gt;
&lt;li&gt;Apps outside Telegram ecosystem&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;If you're curious how Stars feel as a user:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;a href="https://t.me/WhisprMe_bot?start=src_devto2" rel="noopener noreferrer"&gt;@WhisprMe_bot&lt;/a&gt; in Telegram&lt;/li&gt;
&lt;li&gt;Get your anonymous link and share it&lt;/li&gt;
&lt;li&gt;When someone writes you, try unlocking for 1 Star&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The whole experience takes about 30 seconds, and you'll see why frictionless micropayments change everything.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Building a Telegram Mini App? I'd love to hear about your Stars integration experience in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>telegram</category>
      <category>javascript</category>
      <category>node</category>
      <category>startup</category>
    </item>
    <item>
      <title>5 Things That Surprised Me Building a Telegram Mini App (Real Production Lessons)</title>
      <dc:creator>Haskell Thurber</dc:creator>
      <pubDate>Tue, 03 Mar 2026 10:32:26 +0000</pubDate>
      <link>https://dev.to/haskelldev/5-things-that-surprised-me-building-a-telegram-mini-app-real-production-lessons-4bmk</link>
      <guid>https://dev.to/haskelldev/5-things-that-surprised-me-building-a-telegram-mini-app-real-production-lessons-4bmk</guid>
      <description>&lt;p&gt;I recently built and launched &lt;a href="https://t.me/WhisprMe_bot?start=src_devto" rel="noopener noreferrer"&gt;WhisprMe&lt;/a&gt; — an anonymous messaging app that runs entirely inside Telegram as a Mini App. No app store, no downloads, just a link.&lt;/p&gt;

&lt;p&gt;Here are 5 things that genuinely surprised me during development and after launch.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Telegram Stars Payments Are Absurdly Simple
&lt;/h2&gt;

&lt;p&gt;I expected payments to be the hardest part. Credit card integrations, Stripe webhooks, PCI compliance nightmares…&lt;/p&gt;

&lt;p&gt;Nope. Telegram Stars took about 30 lines of backend code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Create invoice&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;telegram&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createInvoiceLink&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unlock Anonymous Message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;See who sent you this message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;messageId&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;XTR&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unlock&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Handle pre-checkout&lt;/span&gt;
&lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pre_checkout_query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;answerPreCheckoutQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Handle successful payment&lt;/span&gt;
&lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;successful_payment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;messageId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;successful_payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoice_payload&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Unlock the message in DB&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Users tap "Pay 1 ⭐", confirm, done. No credit card forms. The conversion rate is &lt;strong&gt;significantly higher&lt;/strong&gt; than traditional payment flows because there's zero friction.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Haptic Feedback Changes Everything
&lt;/h2&gt;

&lt;p&gt;Adding this one line transformed how the app &lt;em&gt;feels&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Telegram&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WebApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HapticFeedback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notificationOccurred&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I added haptic feedback on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Message sent ✅&lt;/li&gt;
&lt;li&gt;Message unlocked 🔓&lt;/li&gt;
&lt;li&gt;Achievement earned 🏆&lt;/li&gt;
&lt;li&gt;Button taps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Users never mentioned it explicitly, but engagement metrics went up after I added it. The app just feels &lt;em&gt;native&lt;/em&gt; — like it belongs on the phone, not in a browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. &lt;code&gt;initData&lt;/code&gt; Is Your Auth Layer (But Validate It Server-Side!)
&lt;/h2&gt;

&lt;p&gt;Telegram sends &lt;code&gt;initData&lt;/code&gt; with every Mini App launch. It contains the user's ID, first name, username, etc. — cryptographically signed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Server-side validation&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateInitData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;botToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hash&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hash&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dataCheckString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;localeCompare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;secretKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;WebAppData&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;botToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;calculatedHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secretKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dataCheckString&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;calculatedHash&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The surprise:&lt;/strong&gt; I initially skipped server-side validation during development. Big mistake. Anyone can send fake &lt;code&gt;initData&lt;/code&gt; from a browser console. Always validate the HMAC signature.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The Cold Start Problem Is Real
&lt;/h2&gt;

&lt;p&gt;Building the app took 2 weeks. Getting the first 10 users took longer.&lt;/p&gt;

&lt;p&gt;What worked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shareable links&lt;/strong&gt; — each user gets &lt;code&gt;whisprme.app/u/username&lt;/code&gt; that they can share on Instagram Stories, Telegram bio, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gamification&lt;/strong&gt; — leaderboards and achievements gave people a reason to come back&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Referral system&lt;/strong&gt; — invite friends, get bonus messages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What didn't work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Posting on Reddit (got auto-moderated everywhere)&lt;/li&gt;
&lt;li&gt;Cold DMs (felt spammy, stopped after 2)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The social virality loop is key: someone receives an anonymous message → they're curious → they share their own link → their friends send messages → repeat.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. A $5/month VPS Handles More Than You Think
&lt;/h2&gt;

&lt;p&gt;My entire stack runs on a single Hetzner VPS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Node.js/Express&lt;/strong&gt; backend with PM2 cluster mode&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL&lt;/strong&gt; database&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React&lt;/strong&gt; frontend (static build served by Express)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nginx&lt;/strong&gt; reverse proxy with SSL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total cost: ~$5/month. For the first 1000 users, this is more than enough. I was tempted to over-engineer with Kubernetes, Redis, separate microservices... but a monolith on a VPS just works.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# My entire deployment:
pm2 restart all
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole CI/CD. 🤷&lt;/p&gt;




&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;p&gt;For anyone curious:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; Node.js, Express, Telegraf (bot framework)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database:&lt;/strong&gt; PostgreSQL with raw SQL (no ORM)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; React with vanilla CSS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payments:&lt;/strong&gt; Telegram Stars&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hosting:&lt;/strong&gt; Hetzner VPS, PM2, Nginx&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;i18n:&lt;/strong&gt; Custom solution supporting English + Russian&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;If you want to see a real-world Telegram Mini App in action:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://t.me/WhisprMe_bot?start=src_devto" rel="noopener noreferrer"&gt;Open WhisprMe in Telegram&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Share your link with friends, receive anonymous messages, and see the Stars payment flow in action. I'd love your technical feedback!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What's your experience with Telegram Mini Apps? Have you built one? What surprised you? Let me know in the comments!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>startup</category>
    </item>
    <item>
      <title>How to Accept Payments in a Telegram Mini App Using Stars (Step-by-Step Guide)</title>
      <dc:creator>Haskell Thurber</dc:creator>
      <pubDate>Tue, 03 Mar 2026 06:25:08 +0000</pubDate>
      <link>https://dev.to/haskelldev/how-to-accept-payments-in-a-telegram-mini-app-using-stars-step-by-step-guide-46je</link>
      <guid>https://dev.to/haskelldev/how-to-accept-payments-in-a-telegram-mini-app-using-stars-step-by-step-guide-46je</guid>
      <description>&lt;p&gt;Telegram Stars are a built-in payment system for Mini Apps — no Stripe, no App Store fees, no payment gateway needed. Users pay with Stars they already have in Telegram, and you get paid directly.&lt;/p&gt;

&lt;p&gt;In this tutorial, I'll show you exactly how to implement Stars payments in a Node.js + Express backend, based on what I built for &lt;a href="https://whisprme.app" rel="noopener noreferrer"&gt;WhisprMe&lt;/a&gt; — an anonymous messaging Mini App.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are Telegram Stars?
&lt;/h2&gt;

&lt;p&gt;Stars are Telegram's in-app currency. Users buy them inside Telegram and spend them in Mini Apps and bots. As a developer, you:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create an invoice via the Bot API&lt;/li&gt;
&lt;li&gt;Handle the pre-checkout query&lt;/li&gt;
&lt;li&gt;Confirm the payment&lt;/li&gt;
&lt;li&gt;Deliver the digital good&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are &lt;strong&gt;zero platform fees&lt;/strong&gt; for the first withdrawal (Telegram takes a cut on subsequent ones). The entire flow happens inside Telegram — no redirects, no card forms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A Telegram Bot (create one via &lt;a href="https://t.me/BotFather" rel="noopener noreferrer"&gt;@BotFather&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Node.js 18+&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;telegraf&lt;/code&gt; package (or &lt;code&gt;node-telegram-bot-api&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;A Telegram Mini App connected to your bot&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Create the Invoice Endpoint
&lt;/h2&gt;

&lt;p&gt;Your frontend calls this endpoint to get a payment link:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// routes.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Telegraf&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;telegraf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Telegraf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BOT_TOKEN&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/create-invoice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;telegramId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;itemType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoiceLink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;telegram&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createInvoiceLink&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Premium Feature&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unlock premium features in WhisprMe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; 
        &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;telegramId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;itemType&lt;/span&gt; 
      &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="na"&gt;provider_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Empty for Stars!&lt;/span&gt;
      &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;XTR&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;// XTR = Telegram Stars&lt;/span&gt;
      &lt;span class="na"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; 
        &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Premium&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;  &lt;span class="c1"&gt;// in Stars (1 Star = 1 unit)&lt;/span&gt;
      &lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;invoiceLink&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invoice error:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Failed to create invoice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key points:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;provider_token&lt;/code&gt; must be an empty string for Stars&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;currency&lt;/code&gt; must be &lt;code&gt;'XTR'&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;amount&lt;/code&gt; is in whole Stars (no decimals)&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;payload&lt;/code&gt; is your custom data — you'll get it back after payment&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 2: Handle Pre-Checkout Query
&lt;/h2&gt;

&lt;p&gt;Before Telegram charges the user, it sends a pre-checkout query. You &lt;strong&gt;must&lt;/strong&gt; answer within 10 seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// bot.js&lt;/span&gt;
&lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pre_checkout_query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preCheckoutQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoice_payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Validate the purchase&lt;/span&gt;
    &lt;span class="c1"&gt;// - Does the user exist?&lt;/span&gt;
    &lt;span class="c1"&gt;// - Is the item still available?&lt;/span&gt;
    &lt;span class="c1"&gt;// - Is the price correct?&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;answerPreCheckoutQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;answerPreCheckoutQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Something went wrong. Please try again.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Critical:&lt;/strong&gt; If you don't respond to pre_checkout_query within 10 seconds, the payment fails silently. Make your validation fast!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 3: Handle Successful Payment
&lt;/h2&gt;

&lt;p&gt;After the user pays, Telegram sends a &lt;code&gt;successful_payment&lt;/code&gt; message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;successful_payment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;successful_payment&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoice_payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;total_amount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Record the transaction&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;`INSERT INTO transactions 
     (user_id, type, stars_amount, telegram_payment_id) 
     VALUES ($1, $2, $3, $4)&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;,
    [payload.userId, payload.type, stars, 
     payment.telegram_payment_charge_id]
  );

  // Deliver the digital good
  if (payload.type === 'premium') {
    await db.query(
      'UPDATE users SET subscription_tier = $1 WHERE telegram_id = $2',
      ['premium', payload.userId]
    );
  }

  // Notify the user
  await ctx.reply(
    &lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;Payment successful! You paid &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;stars&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; Stars.&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;
  );
});
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Open the Invoice from Your Mini App
&lt;/h2&gt;

&lt;p&gt;In your React frontend, use the Telegram WebApp SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// React component&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handlePurchase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;itemType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/create-invoice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; 
      &lt;span class="na"&gt;telegramId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Telegram&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WebApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initDataUnsafe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;itemType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;amount&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;invoiceLink&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// This opens Telegram's native payment UI&lt;/span&gt;
  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Telegram&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WebApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;openInvoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;invoiceLink&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;paid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Refresh UI, show success&lt;/span&gt;
      &lt;span class="nf"&gt;showNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Payment successful!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Telegram&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WebApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HapticFeedback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notificationOccurred&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Complete Payment Flow
&lt;/h2&gt;

&lt;p&gt;Here's what happens when a user clicks "Buy":&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Frontend&lt;/strong&gt; calls &lt;code&gt;/api/create-invoice&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend&lt;/strong&gt; creates an invoice link via Bot API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend&lt;/strong&gt; opens it with &lt;code&gt;WebApp.openInvoice()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Telegram&lt;/strong&gt; shows native payment UI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User&lt;/strong&gt; confirms payment with Stars&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Telegram&lt;/strong&gt; sends &lt;code&gt;pre_checkout_query&lt;/code&gt; → your bot answers &lt;code&gt;true&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Telegram&lt;/strong&gt; sends &lt;code&gt;successful_payment&lt;/code&gt; → you deliver the good&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend&lt;/strong&gt; callback fires with &lt;code&gt;status === 'paid'&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The entire flow takes about 2 seconds. No redirects, no card forms, no 3D Secure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Tips from Building WhisprMe
&lt;/h2&gt;

&lt;p&gt;After processing real payments in &lt;a href="https://t.me/WhisprMe_bot?start=src_devto" rel="noopener noreferrer"&gt;WhisprMe&lt;/a&gt;, here's what I learned:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Always validate in pre_checkout_query.&lt;/strong&gt; Don't just auto-approve. Check that the item exists and the price hasn't changed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Use the payload wisely.&lt;/strong&gt; Encode everything you need to fulfill the order — user ID, item type, quantity. You'll thank yourself during debugging.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Idempotency matters.&lt;/strong&gt; Store &lt;code&gt;telegram_payment_charge_id&lt;/code&gt; and check for duplicates. Telegram can sometimes send duplicate &lt;code&gt;successful_payment&lt;/code&gt; events.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Stars pricing psychology.&lt;/strong&gt; We found that 15 Stars works better than 10 or 20 for premium features. It feels "affordable but not cheap."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Haptic feedback.&lt;/strong&gt; That &lt;code&gt;HapticFeedback.notificationOccurred('success')&lt;/code&gt; after payment makes a huge difference in user experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up in Production
&lt;/h2&gt;

&lt;p&gt;Make sure your webhook is properly configured for payment events:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// index.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Important: set webhook with allowed_updates&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;telegram&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setWebhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://yourdomain.com/bot-webhook&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;allowed_updates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pre_checkout_query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/bot-webhook&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; 
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handleUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Telegram Stars make payments ridiculously simple compared to traditional payment gateways. No KYC for users, no payment forms, instant delivery — it's the kind of payment UX we've always wanted.&lt;/p&gt;

&lt;p&gt;If you're building a Telegram Mini App, Stars should be your default monetization strategy. The conversion rates are significantly higher than traditional payment methods because users don't leave the app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it yourself:&lt;/strong&gt; &lt;a href="https://t.me/WhisprMe_bot?start=src_devto" rel="noopener noreferrer"&gt;@WhisprMe_bot&lt;/a&gt; — send anonymous messages, unlock premium features with Stars.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What payment challenges have you faced with Telegram Mini Apps? Drop a comment below!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>telegram</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>node</category>
    </item>
    <item>
      <title>I Built an Anonymous Feedback App for Telegram — Here's What I Learned</title>
      <dc:creator>Haskell Thurber</dc:creator>
      <pubDate>Mon, 02 Mar 2026 09:17:17 +0000</pubDate>
      <link>https://dev.to/haskelldev/i-built-an-anonymous-feedback-app-for-telegram-heres-what-i-learned-1caj</link>
      <guid>https://dev.to/haskelldev/i-built-an-anonymous-feedback-app-for-telegram-heres-what-i-learned-1caj</guid>
      <description>&lt;h1&gt;
  
  
  I Built an Anonymous Feedback App for Telegram — Here's What I Learned
&lt;/h1&gt;

&lt;p&gt;Remember Sarahah? The app that went viral in 2017, hitting #1 in 30+ countries with zero marketing budget? The concept was dead simple: share a link, get anonymous messages from friends.&lt;/p&gt;

&lt;p&gt;I built something similar — but inside Telegram as a Mini App, with native monetization through Telegram Stars. Here's the full story.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 The Idea
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;WhisprMe&lt;/strong&gt; (@WhisprMe_bot) is an anonymous feedback service built as a Telegram Mini App. You get a unique link, share it with friends, and they write anonymous messages to you. &lt;/p&gt;

&lt;p&gt;The hook? You can see a 3-word preview of each message, but the full text is blurred. Want to read it? Pay 15 Stars (~$0.26) or invite a friend.&lt;/p&gt;

&lt;p&gt;The tagline: &lt;strong&gt;"Find out what people really think about you"&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🏗️ Tech Stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;Node.js + Express&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;PostgreSQL (raw SQL, no ORM)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;React (Create React App)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bot&lt;/td&gt;
&lt;td&gt;Telegraf.js&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Process Manager&lt;/td&gt;
&lt;td&gt;PM2 (cluster mode)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Web Server&lt;/td&gt;
&lt;td&gt;nginx&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payments&lt;/td&gt;
&lt;td&gt;Telegram Stars API&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  🔥 The Viral Mechanics
&lt;/h2&gt;

&lt;p&gt;The killer feature is that &lt;strong&gt;sharing is mandatory, not optional&lt;/strong&gt;. Want feedback? You HAVE to share your link. This is the same mechanic that made Sarahah explode.&lt;/p&gt;

&lt;h3&gt;
  
  
  Card Templates
&lt;/h3&gt;

&lt;p&gt;Users can choose from 10 different sharing card styles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🤔 "What do you think about me?"&lt;/li&gt;
&lt;li&gt;💬 "Tell me anonymously what you're afraid to say in person"&lt;/li&gt;
&lt;li&gt;👀 "Would you date me? Be honest"&lt;/li&gt;
&lt;li&gt;🔥 "Rate me 1-10 and tell me why"&lt;/li&gt;
&lt;li&gt;❤️ "Give me an anonymous compliment"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each card generates a unique deep link (&lt;code&gt;t.me/WhisprMe_bot?start=msg_username_hash&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  The Blurred Message Hook
&lt;/h3&gt;

&lt;p&gt;When you receive a message, you see the first 3 words + blurred text. It's psychologically impossible to ignore — you NEED to know what someone thinks about you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Friend Portrait
&lt;/h3&gt;

&lt;p&gt;After 5+ messages, you get an AI-generated "Anonymous Portrait" — a beautiful infographic showing how people see you: top words used, emotional profile, sentiment analysis. This is &lt;strong&gt;screenshot-worthy content&lt;/strong&gt; that users share organically.&lt;/p&gt;

&lt;h2&gt;
  
  
  💰 Monetization with Telegram Stars
&lt;/h2&gt;

&lt;p&gt;This is where it gets interesting. Telegram Stars are the native payment system for Mini Apps. No Stripe, no App Store commission, direct micropayments.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Read a hidden message&lt;/td&gt;
&lt;td&gt;15 Stars&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reveal sender's city&lt;/td&gt;
&lt;td&gt;25 Stars&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reveal sender's contact&lt;/td&gt;
&lt;td&gt;49 Stars&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plus subscription (monthly)&lt;/td&gt;
&lt;td&gt;99 Stars&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pro subscription (monthly)&lt;/td&gt;
&lt;td&gt;299 Stars&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Virtual gifts (Heart, Fire, Crown)&lt;/td&gt;
&lt;td&gt;30-200 Stars&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The Referral System
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Invite 1 friend → +5 free messages&lt;/li&gt;
&lt;li&gt;Invite 3 friends → unlimited for 7 days&lt;/li&gt;
&lt;li&gt;Friend signs up → +50 Stars bonus&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Plus, Telegram's native Affiliate Program gives referrers 15% of all payments from referred users. Bloggers get 25%.&lt;/p&gt;

&lt;h2&gt;
  
  
  🛠️ Technical Deep Dive
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Authentication
&lt;/h3&gt;

&lt;p&gt;Telegram Mini Apps pass &lt;code&gt;initData&lt;/code&gt; with a cryptographic signature. I validate it server-side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateTelegramWebAppData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;WebAppData&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;BOT_TOKEN&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;checkString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;localeCompare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;checkString&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Stars Payment Flow
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Frontend calls &lt;code&gt;POST /api/payments/create-invoice&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Backend creates invoice via Telegram Bot API&lt;/li&gt;
&lt;li&gt;Telegram shows native payment dialog&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pre_checkout_query&lt;/code&gt; → validate → &lt;code&gt;successful_payment&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Backend unlocks content&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Anti-Abuse
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Rate limit: max 3 messages/day from one sender to one recipient&lt;/li&gt;
&lt;li&gt;Global IP-based hourly rate limit&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;basicToxicityCheck()&lt;/code&gt; filters toxic content before saving&lt;/li&gt;
&lt;li&gt;Blurred preview shows only first 3 words&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Database Design
&lt;/h3&gt;

&lt;p&gt;Pure PostgreSQL with raw SQL queries. No ORM overhead. The schema is straightforward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;users&lt;/code&gt; — profiles with &lt;code&gt;unique_slug&lt;/code&gt; for URLs&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;messages&lt;/code&gt; — with &lt;code&gt;preview&lt;/code&gt; (first 3 words), &lt;code&gt;is_unlocked&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;transactions&lt;/code&gt; — Stars payment history&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;referrals&lt;/code&gt;, &lt;code&gt;achievements&lt;/code&gt;, &lt;code&gt;mood_words&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cluster Mode
&lt;/h3&gt;

&lt;p&gt;PM2 cluster mode with multiple workers. Since PostgreSQL handles concurrency natively, no shared state issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  📊 Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;📨 Inbox&lt;/strong&gt; — Receive anonymous messages with blur effect&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;📤 Share&lt;/strong&gt; — 10 card template styles for sharing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🎭 Portrait&lt;/strong&gt; — AI sentiment analysis from messages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🏆 Leaderboard&lt;/strong&gt; — Weekly/monthly popularity ranking&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;👤 Profile&lt;/strong&gt; — Subscription management, referral stats&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🎁 Gifts&lt;/strong&gt; — Send anonymous virtual gifts with messages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;😀 Reactions&lt;/strong&gt; — Anonymous emoji reactions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🚀 Key Learnings
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. The viral loop IS the product
&lt;/h3&gt;

&lt;p&gt;Don't add a "Share" button as an afterthought. Make sharing the core mechanic. If users can't use your product without sharing, congratulations — you've built a viral loop.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Telegram Stars remove friction
&lt;/h3&gt;

&lt;p&gt;Traditional payments require credit cards, KYC, App Store approval. Stars are one tap. Users already have them. Conversion is dramatically higher.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Blurred content = irresistible curiosity
&lt;/h3&gt;

&lt;p&gt;The preview-then-paywall approach works incredibly well for anonymous messages. People can't resist knowing what someone thinks about them.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Mini Apps are an untapped market
&lt;/h3&gt;

&lt;p&gt;Telegram has 900M+ MAU. The Mini App ecosystem is still early. There's much less competition than the App Store or Play Store.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Referral programs that pay Stars actually work
&lt;/h3&gt;

&lt;p&gt;When users earn actual currency (Stars → TON → real money), they're genuinely motivated to invite friends.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔗 Try It
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bot:&lt;/strong&gt; &lt;a href="https://t.me/WhisprMe_bot?start=src_devto" rel="noopener noreferrer"&gt;@WhisprMe_bot&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web:&lt;/strong&gt; &lt;a href="https://whisprme.app" rel="noopener noreferrer"&gt;whisprme.app&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Share your link, get anonymous messages, discover what people really think about you.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built by a solo developer in ~3 weeks. Happy to answer questions in the comments!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>telegram</category>
      <category>javascript</category>
      <category>react</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
