Skip to main content
The Telegram integration enables Stars payments (in-app purchases) and channel membership management for JOIP users.

Overview

Telegram integration provides:
  • Telegram Stars payments for credit purchases
  • Automated channel invites for patrons
  • Membership verification and bonus credits
  • Bot message customization for admin control
  • Deep linking for seamless payment flow
Telegram Stars is Telegram’s digital currency. Users pay with Stars, and you receive payouts in USD.

Prerequisites

  • Telegram account
  • BotFather access for creating bots
  • Telegram channel (optional, for patron perks)
  • Approved bot for Stars payments

Setup Steps

1

Create Telegram Bot

Open Telegram and message @BotFather:
  1. Send /newbot
  2. Choose a display name (e.g., “JOIP Credits Bot”)
  3. Choose a username (must end in bot, e.g., JoipCreditsBot)
  4. Copy the bot token provided
Done! Congratulations on your new bot.
Token: 123456789:ABCdefGHIjklMNOpqrsTUVwxyz123456789

Use this token to access the HTTP API.
Keep your bot token secret. Anyone with this token can control your bot.
2

Request Stars Payments Access

Telegram Stars requires approval from Telegram:
  1. Message @BotSupport
  2. Request Stars payment access
  3. Provide:
    • Bot username
    • Description of your service
    • Business details (if required)
  4. Wait for approval (typically 1-3 days)
You can set up the bot before approval, but payments won’t work until enabled.
3

Create Telegram Channel (Optional)

For patron perks and announcements:
  1. Create a new channel in Telegram
  2. Make it public or private
  3. Add your bot as an administrator with:
    • Post messages permission
    • Invite users via link permission
  4. Get the channel ID:
    • Forward a message from the channel to @userinfobot
    • Copy the channel ID (format: -100123456789)
4

Configure Environment Variables

Add credentials to .env:
TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrsTUVwxyz
TELEGRAM_BOT_USERNAME=JoipCreditsBot
TELEGRAM_WEBHOOK_SECRET=your-random-secret-string
Generate webhook secret:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
# Output: a1b2c3d4e5f6...  (use this for TELEGRAM_WEBHOOK_SECRET)
5

Set Webhook URL

After deploying your app, configure the webhook:
curl -X POST "https://api.telegram.org/bot<YOUR_BOT_TOKEN>/setWebhook" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/api/telegram/webhook",
    "secret_token": "your-webhook-secret",
    "allowed_updates": ["message", "pre_checkout_query"]
  }'
Verify webhook:
curl "https://api.telegram.org/bot<YOUR_BOT_TOKEN>/getWebhookInfo"
6

Set Bot Commands

Configure user-facing commands:
import { setMyCommands, REQUIRED_TELEGRAM_COMMANDS } from './server/telegramBot';

await setMyCommands(REQUIRED_TELEGRAM_COMMANDS);
// Sets: /terms, /support, /paysupport
Users will see these commands when typing / in the bot chat.
7

Test Integration

Verify everything works:
  1. Send /start to your bot
  2. Should receive welcome message
  3. Try /terms and /support commands
  4. Check server logs for webhook events
[Telegram] Webhook set to: https://your-app.com/api/telegram/webhook
[Telegram] Bot commands configured
[Telegram] Received message from user 123456

Implementation Details

Payment Flow

The Telegram Stars payment flow:
1

User Initiates Purchase

User clicks “Pay with Telegram” on a credit package in JOIP.
// Create payment session
const response = await fetch('/api/payments/create-session', {
  method: 'POST',
  body: JSON.stringify({
    packageId: 'package_100',
    paymentMethod: 'telegram'
  })
});

const { sessionId, deepLink } = await response.json();
// deepLink: https://t.me/YourBot?start=pay_abc123
2

Deep Link Opens Bot

User clicks deep link, opens Telegram bot.Bot receives /start command with payment payload:
// Webhook receives:
{
  message: {
    text: "/start pay_abc123",
    from: { id: 123456, username: "johndoe" }
  }
}

// Extract session ID from payload
const [, sessionId] = message.text.split(' ');
// sessionId: "abc123" (remove "pay_" prefix)
3

Send Invoice

Server sends Telegram Stars invoice:
import { sendInvoice } from './server/telegramBot';

await sendInvoice(
  chatId,      // User's Telegram chat ID
  session,     // Payment session from DB
  package      // Credit package details
);

// User sees invoice in chat with "Pay X Stars" button
4

Pre-Checkout Query

Before charging, Telegram sends pre-checkout query:
app.post('/api/telegram/webhook', async (req, res) => {
  const { pre_checkout_query } = req.body;
  
  if (pre_checkout_query) {
    // Validate session, package, user
    const valid = await validatePaymentSession(
      pre_checkout_query.invoice_payload
    );
    
    await answerPreCheckoutQuery(
      pre_checkout_query.id,
      valid,
      valid ? undefined : "Session expired"
    );
  }
});
You must respond to pre-checkout queries within 10 seconds or the payment fails.
5

Successful Payment

Telegram charges user and sends successful_payment webhook:
const { successful_payment } = message;

if (successful_payment) {
  const sessionId = successful_payment.invoice_payload;
  
  // Add credits to user account
  await creditService.addCredits(
    userId,
    package.credits,
    `Telegram Stars purchase (${sessionId})`
  );
  
  // Mark payment session as completed
  await updatePaymentSession(sessionId, {
    status: 'completed',
    telegramChargeId: successful_payment.telegram_payment_charge_id
  });
  
  // Send confirmation message
  await sendMessage(
    chatId,
    `✅ Payment complete! ${package.credits} credits added.`
  );
}

Channel Management

Automatic channel membership management:
import { 
  checkChannelMembership, 
  createAndSendInvite,
  processPeriodicMembershipChecks
} from './server/telegramChannelService';

// Check if user is in channel
const isMember = await checkChannelMembership(userId, telegramId);

if (!isMember) {
  // Send invite link
  const result = await createAndSendInvite(telegramId);
  // User receives message with "Join Channel" button
}

// Periodic checks (run via cron)
const stats = await processPeriodicMembershipChecks();
// { checked: 50, invited: 10, errors: 0 }

Message Customization

Admins can customize bot messages:
import { updateBotMessage, getBotMessage } from './server/telegramMessageService';

// Update payment success message
await updateBotMessage(
  'payment_success',
  '✅ Payment complete!\n\n{credits} credits have been added to your account.',
  adminUserId,
  'HTML'  // parse_mode
);

// Get message with placeholders filled
const message = await getBotMessage('payment_success', {
  credits: '100'
});
console.log(message.content);
// "✅ Payment complete!\n\n100 credits have been added to your account."
Available message keys:
  • bot_welcome - Initial /start message
  • payment_success - Payment completed
  • payment_failed - Payment verification failed
  • session_ready - Payment session created
  • terms_info - /terms command response
  • support_info - /support command response
  • channel_invite - Channel invitation message
  • bonus_granted - Channel join bonus

API Reference

Core Functions

// Message functions
function sendMessage(
  chatId: string | number,
  text: string,
  options?: { parse_mode?: 'HTML' | 'Markdown'; reply_markup?: object }
): Promise<void>

function sendPhoto(
  chatId: string | number,
  photo: string,  // URL or file_id
  caption?: string,
  options?: object
): Promise<void>

// Payment functions
function sendInvoice(
  chatId: string | number,
  session: PaymentSession,
  pkg: CreditPackage
): Promise<void>

function answerPreCheckoutQuery(
  queryId: string,
  ok: boolean,
  errorMessage?: string
): Promise<void>

function createInvoiceLink(
  sessionId: string,
  pkg: CreditPackage
): Promise<string>  // Returns direct payment link

function refundStarPayment(
  userId: string | number,
  telegramPaymentChargeId: string
): Promise<void>

// Webhook functions
function setWebhook(webhookUrl: string): Promise<void>
function getWebhookInfo(): Promise<object>
function deleteWebhook(): Promise<void>
function verifyWebhookSecret(headerSecret: string): boolean

// Bot info
function getMe(): Promise<{ id: number; username: string; ... }>
function setMyCommands(commands: TelegramBotCommand[]): Promise<void>
function getMyCommands(): Promise<TelegramBotCommand[]>

// Channel functions
function getChat(chatId: string | number): Promise<object>
function getChatMember(
  chatId: string | number,
  userId: number
): Promise<{ status: string }>
function createChatInviteLink(
  chatId: string | number,
  options?: { member_limit?: number; expire_date?: number }
): Promise<{ invite_link: string }>

Type Definitions

interface TelegramUpdate {
  update_id: number;
  message?: TelegramMessage;
  pre_checkout_query?: TelegramPreCheckoutQuery;
  chat_member?: TelegramChatMemberUpdate;
}

interface TelegramMessage {
  message_id: number;
  from?: TelegramUser;
  chat: { id: number; type: string };
  text?: string;
  successful_payment?: {
    currency: string;  // "XTR" for Stars
    total_amount: number;
    invoice_payload: string;  // Your session ID
    telegram_payment_charge_id: string;
  };
  refunded_payment?: {
    currency: string;
    total_amount: number;
    telegram_payment_charge_id: string;
  };
}

interface TelegramPreCheckoutQuery {
  id: string;
  from: TelegramUser;
  currency: string;
  total_amount: number;
  invoice_payload: string;  // Your session ID
}

Common Operations

Send Custom Message

import { sendMessage } from './server/telegramBot';

await sendMessage(
  chatId,
  'Choose a credit package:',
  {
    parse_mode: 'HTML',
    reply_markup: {
      inline_keyboard: [
        [
          { text: '100 Credits - 10 Stars', callback_data: 'buy_100' },
          { text: '500 Credits - 45 Stars', callback_data: 'buy_500' }
        ],
        [
          { text: 'Visit Website', url: 'https://app.joip.io' }
        ]
      ]
    }
  }
);

Process Refund

import { refundStarPayment } from './server/telegramBot';
import { creditService } from './server/creditService';

// Refund Telegram Stars payment
await refundStarPayment(
  telegramUserId,
  telegramPaymentChargeId
);

// Deduct credits from user account
await creditService.deductCredits(
  userId,
  amount,
  `Refund for ${sessionId}`
);

// Notify user
await sendMessage(
  chatId,
  '⚠️ Your purchase has been refunded. Credits removed from your account.'
);

Bulk Channel Invites

import { bulkInviteUsers } from './server/telegramChannelService';

// Invite all patrons to channel
const patronIds = await db
  .select({ id: users.id })
  .from(users)
  .where(eq(users.isActivePatron, true));

const result = await bulkInviteUsers(
  patronIds.map(u => u.id),
  adminUserId
);

console.log(`Invited ${result.success.length} users`);
console.log(`Failed: ${result.failed.length}`);

Error Handling

Common Issues

Cause: Webhook URL not accessible or secret mismatchSolution:
  1. Verify webhook URL is publicly accessible (HTTPS required)
  2. Check webhook secret matches TELEGRAM_WEBHOOK_SECRET
  3. Use getWebhookInfo() to see last error
  4. Test webhook: send /start to bot and check server logs
  5. Ensure server is deployed (webhooks don’t work on localhost)
Cause: Bot doesn’t have Stars payment permissionSolution:
  • Check bot is approved by @BotSupport
  • Verify provider_token is empty string (required for Stars)
  • Check currency is “XTR” (Telegram Stars currency code)
  • Review error in Telegram bot chat
Cause: Response took longer than 10 secondsSolution:
  • Optimize validation logic
  • Use database indexes for session lookup
  • Pre-validate data when creating session
  • Add timeout monitoring to webhook handler
Cause: Bot lacks permissions in channelSolution:
  • Ensure bot is admin in channel
  • Grant “Invite users via link” permission
  • Check channel ID format (should start with -100)
  • Verify channel is not deleted or banned

Security Best Practices

Webhook Verification

Always verify webhook secret:
const secret = req.headers['x-telegram-bot-api-secret-token'];

if (!verifyWebhookSecret(secret)) {
  return res.status(403).json({ error: 'Forbidden' });
}

Bot Token Protection

  • Store in .env only
  • Never commit to version control
  • Regenerate if exposed
  • Use separate bots for dev/prod

Payment Validation

Validate all payment data:
  • Session exists and not expired
  • Package is available
  • Amount matches package price
  • User owns the session

Rate Limiting

Implement rate limits:
  • Max payments per user per hour
  • Cooldown on failed attempts
  • Block suspicious patterns
  • Log all payment events

Troubleshooting

Debug Webhook Events

app.post('/api/telegram/webhook', (req, res) => {
  console.log('Telegram webhook:', JSON.stringify(req.body, null, 2));
  // Process update...
});

Test Bot Locally

For development, use polling instead of webhooks:
import { deleteWebhook } from './server/telegramBot';

// Remove webhook
await deleteWebhook();

// Use getUpdates in loop (not shown - use library like 'node-telegram-bot-api')
Production should always use webhooks. Polling is inefficient and may hit rate limits.

Cost & Pricing

Telegram Stars

  • Currency: XTR (Telegram Stars)
  • User cost: ~$0.01 USD per Star (varies by region)
  • Your revenue: ~50% of user payment
  • Payouts: Monthly via Telegram
Example pricing:
  • 10 Stars → User pays ~0.10Youreceive 0.10 → You receive ~0.05
  • 100 Stars → User pays ~1.00Youreceive 1.00 → You receive ~0.50
  • 1000 Stars → User pays ~10.00Youreceive 10.00 → You receive ~5.00
Telegram takes ~50% fee. Factor this into your pricing strategy.

Build docs developers (and LLMs) love