DEV Community

Haskell Thurber
Haskell Thurber

Posted on

5 Things That Surprised Me Building a Telegram Mini App (Real Production Lessons)

I recently built and launched WhisprMe — an anonymous messaging app that runs entirely inside Telegram as a Mini App. No app store, no downloads, just a link.

Here are 5 things that genuinely surprised me during development and after launch.

1. Telegram Stars Payments Are Absurdly Simple

I expected payments to be the hardest part. Credit card integrations, Stripe webhooks, PCI compliance nightmares…

Nope. Telegram Stars took about 30 lines of backend code:

// Create invoice
const link = await bot.telegram.createInvoiceLink({
  title: 'Unlock Anonymous Message',
  description: 'See who sent you this message',
  payload: JSON.stringify({ userId, messageId }),
  currency: 'XTR',
  prices: [{ label: 'Unlock', amount: 1 }]
});

// Handle pre-checkout
bot.on('pre_checkout_query', (ctx) => {
  ctx.answerPreCheckoutQuery(true);
});

// Handle successful payment
bot.on('message', (ctx) => {
  if (ctx.message.successful_payment) {
    const { userId, messageId } = JSON.parse(
      ctx.message.successful_payment.invoice_payload
    );
    // Unlock the message in DB
  }
});
Enter fullscreen mode Exit fullscreen mode

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

2. Haptic Feedback Changes Everything

Adding this one line transformed how the app feels:

window.Telegram.WebApp.HapticFeedback.notificationOccurred('success');
Enter fullscreen mode Exit fullscreen mode

I added haptic feedback on:

  • Message sent ✅
  • Message unlocked 🔓
  • Achievement earned 🏆
  • Button taps

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

3. initData Is Your Auth Layer (But Validate It Server-Side!)

Telegram sends initData with every Mini App launch. It contains the user's ID, first name, username, etc. — cryptographically signed.

// Server-side validation
const crypto = require('crypto');

function validateInitData(initData, botToken) {
  const params = new URLSearchParams(initData);
  const hash = params.get('hash');
  params.delete('hash');

  const dataCheckString = [...params.entries()]
    .sort(([a], [b]) => a.localeCompare(b))
    .map(([k, v]) => `${k}=${v}`)
    .join('\n');

  const secretKey = crypto
    .createHmac('sha256', 'WebAppData')
    .update(botToken)
    .digest();

  const calculatedHash = crypto
    .createHmac('sha256', secretKey)
    .update(dataCheckString)
    .digest('hex');

  return calculatedHash === hash;
}
Enter fullscreen mode Exit fullscreen mode

The surprise: I initially skipped server-side validation during development. Big mistake. Anyone can send fake initData from a browser console. Always validate the HMAC signature.

4. The Cold Start Problem Is Real

Building the app took 2 weeks. Getting the first 10 users took longer.

What worked:

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

What didn't work:

  • Posting on Reddit (got auto-moderated everywhere)
  • Cold DMs (felt spammy, stopped after 2)

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

5. A $5/month VPS Handles More Than You Think

My entire stack runs on a single Hetzner VPS:

  • Node.js/Express backend with PM2 cluster mode
  • PostgreSQL database
  • React frontend (static build served by Express)
  • Nginx reverse proxy with SSL

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.

# My entire deployment:
pm2 restart all
Enter fullscreen mode Exit fullscreen mode

That's the whole CI/CD. 🤷


The Stack

For anyone curious:

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

Try It

If you want to see a real-world Telegram Mini App in action:

👉 Open WhisprMe in Telegram

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


What's your experience with Telegram Mini Apps? Have you built one? What surprised you? Let me know in the comments!

Top comments (1)

Collapse
 
haskelldev profile image
Haskell Thurber

Since writing this, I've extracted the auth and payment code into a standalone starter kit:

🔗 telegram-miniapp-starter

Includes initData validation (HMAC-SHA256), Stars payment flow, Express middleware, and React frontend hooks — everything mentioned in the article, ready to clone and use.

Also wrote a detailed step-by-step for the Stars payment part specifically: How to Accept Payments in a Telegram Mini App Using Stars