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
Create Telegram Bot
Open Telegram and message @BotFather :
Send /newbot
Choose a display name (e.g., “JOIP Credits Bot”)
Choose a username (must end in bot, e.g., JoipCreditsBot)
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.
Request Stars Payments Access
Telegram Stars requires approval from Telegram:
Message @BotSupport
Request Stars payment access
Provide:
Bot username
Description of your service
Business details (if required)
Wait for approval (typically 1-3 days)
You can set up the bot before approval, but payments won’t work until enabled.
Create Telegram Channel (Optional)
For patron perks and announcements:
Create a new channel in Telegram
Make it public or private
Add your bot as an administrator with:
Post messages permission
Invite users via link permission
Get the channel ID:
Forward a message from the channel to @userinfobot
Copy the channel ID (format: -100123456789)
Configure Environment Variables
Add credentials to .env: Required Variables
Optional (Channel)
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)
Set Webhook URL
After deploying your app, configure the webhook: Manual Setup (via cURL)
Automatic Setup (via code)
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"
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.
Test Integration
Verify everything works:
Send /start to your bot
Should receive welcome message
Try /terms and /support commands
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:
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
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)
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
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.
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
Webhook Not Receiving Events
Cause : Webhook URL not accessible or secret mismatchSolution :
Verify webhook URL is publicly accessible (HTTPS required)
Check webhook secret matches TELEGRAM_WEBHOOK_SECRET
Use getWebhookInfo() to see last error
Test webhook: send /start to bot and check server logs
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.10 → Y o u r e c e i v e 0.10 → You receive ~ 0.10 → Y o u rece i v e 0.05
100 Stars → User pays ~1.00 → Y o u r e c e i v e 1.00 → You receive ~ 1.00 → Y o u rece i v e 0.50
1000 Stars → User pays ~10.00 → Y o u r e c e i v e 10.00 → You receive ~ 10.00 → Y o u rece i v e 5.00
Telegram takes ~50% fee. Factor this into your pricing strategy.