Trystpilot - Telegram Authentication Integration Guide
Trystpilot — Telegram Login & Bot Integration
Section titled “Trystpilot — Telegram Login & Bot Integration”Version: 1.0.0 · Last updated: 2026-03-02 Status: 🚀 Ready for implementation Recommended approach: Telegram Login Widget + optional bot
Overview
Section titled “Overview”This guide covers implementing Telegram authentication across three integration points:
- Telegram Login Widget — User & admin login (primary auth)
- Telegram Bot — Notifications, commands, optional interactions
- Database integration — Store Telegram ID linked to users/profiles
Why Telegram?
- ✅ User-friendly (everyone uses it)
- ✅ Secure OAuth-based authentication
- ✅ No need to store passwords
- ✅ Bot can handle moderation notifications
- ✅ Optional: Can collect minimal user data (no PII exposure)
1. Telegram Login Widget (Primary)
Section titled “1. Telegram Login Widget (Primary)”1.1 Setup
Section titled “1.1 Setup”Step 1: Register with BotFather
Section titled “Step 1: Register with BotFather”- Open Telegram, find
@BotFather - Send
/newbotcommand - Follow prompts to create a new bot:
- Name:
Trystpilot Bot(user-visible) - Username:
trystpilot_bot(must end in_bot)
- Name:
- Save the API token → Will look like:
123456789:ABCDefGHIjklmnOPQRstUVwxyz
Step 2: Enable Telegram Login
Section titled “Step 2: Enable Telegram Login”- Send
/mybots→Trystpilot Bot→Settings - Select
Manage inline mode - Go back, select
Domain - Add your domain:
trystpilot.xyz - Important: Telegram will add your domain to a whitelist
Step 3: Generate Login Widget Code
Section titled “Step 3: Generate Login Widget Code”<!-- In your Next.js app (server-side or client component) --><script async src="https://telegram.org/js/telegram-widget.js?15"></script>
<telegram-login data-telegram-login="trystpilot_bot" data-size="large" data-auth-url="https://trystpilot.xyz/api/auth/telegram/callback" data-request-access="write"></telegram-login>Parameters:
data-telegram-login— Bot username (without @)data-size— Button size:small,medium,largedata-auth-url— Your callback endpoint (CRITICAL: must match domain registered with BotFather)data-request-access—writefor notifications,readfor read-only
1.2 Database Schema
Section titled “1.2 Database Schema”Add Telegram fields to your existing tables:
-- For admin usersALTER TABLE admin_users ADD COLUMN ( telegram_id BIGINT UNIQUE, telegram_username VARCHAR(255), telegram_first_name VARCHAR(255), telegram_verified_at TIMESTAMP, telegram_hash VARCHAR(255) -- For verification);
-- For regular user profiles (optional)ALTER TABLE profiles ADD COLUMN ( telegram_id BIGINT UNIQUE, telegram_username VARCHAR(255), telegram_verified_at TIMESTAMP);
-- New table: Telegram sessionsCREATE TABLE telegram_sessions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), telegram_id BIGINT NOT NULL, user_id UUID REFERENCES profiles(id) ON DELETE CASCADE, admin_id UUID REFERENCES admin_users(id) ON DELETE CASCADE, session_token VARCHAR(255) UNIQUE NOT NULL, expires_at TIMESTAMP NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);1.3 Backend Implementation
Section titled “1.3 Backend Implementation”A. Create Telegram Auth Callback Endpoint
Section titled “A. Create Telegram Auth Callback Endpoint”File: app/api/auth/telegram/callback/route.ts
import { createHash } from 'crypto';import { db } from '@/lib/db/client';import { cookies } from 'next/headers';
// Validate Telegram login data using HMAC-SHA256function verifyTelegramAuth(data: Record<string, any>, botToken: string): boolean { const { hash, ...authData } = data;
// Create data check string (must be alphabetically sorted) const dataCheckString = Object.entries(authData) .sort(([a], [b]) => a.localeCompare(b)) .map(([key, val]) => `${key}=${val}`) .join('\n');
// Compute HMAC-SHA256 const secretKey = createHash('sha256').update(botToken).digest(); const computedHash = createHash('sha256') .update(dataCheckString) .update(secretKey) .digest('hex');
return computedHash === hash;}
export async function POST(request: Request) { try { const data = await request.json(); const botToken = process.env.TELEGRAM_BOT_TOKEN!;
// Step 1: Verify the login data if (!verifyTelegramAuth(data, botToken)) { return Response.json( { error: 'Invalid Telegram auth signature' }, { status: 401 } ); }
// Step 2: Check if login is not too old (prevent replay attacks) const authDate = new Date(data.auth_date * 1000); const now = new Date(); if (now.getTime() - authDate.getTime() > 60000) { // 60 seconds return Response.json( { error: 'Login data is too old (replay attack detected)' }, { status: 401 } ); }
const telegramId = data.id; const telegramUsername = data.username; const firstName = data.first_name;
// Step 3: Check if user is requesting admin access or regular user const isAdminAttempt = request.headers.get('x-admin-attempt') === 'true';
if (isAdminAttempt) { // ADMIN LOGIN FLOW // Step 3a: Find or create admin user let admin = await db.query( 'SELECT * FROM admin_users WHERE telegram_id = $1', [telegramId] );
if (!admin.rows.length) { // Check if admin whitelist is enabled (recommended) const whitelist = process.env.TELEGRAM_ADMIN_WHITELIST?.split(',') || []; if (whitelist.length > 0 && !whitelist.includes(String(telegramId))) { return Response.json( { error: 'Telegram ID not in admin whitelist' }, { status: 403 } ); }
// Create new admin (with auto-approve if whitelist passed) admin = await db.query( `INSERT INTO admin_users (telegram_id, telegram_username, telegram_first_name, telegram_verified_at) VALUES ($1, $2, $3, NOW()) RETURNING *`, [telegramId, telegramUsername, firstName] ); }
// Step 4: Create session const sessionToken = require('crypto').randomBytes(32).toString('hex'); const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days
await db.query( `INSERT INTO telegram_sessions (telegram_id, admin_id, session_token, expires_at) VALUES ($1, $2, $3, $4)`, [telegramId, admin.rows[0].id, sessionToken, expiresAt] );
// Step 5: Set session cookie const cookieStore = await cookies(); cookieStore.set('telegram_session', sessionToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax', path: '/', maxAge: 7 * 24 * 60 * 60 // 7 days in seconds });
return Response.json({ success: true, redirectTo: '/moderation', message: 'Admin login successful' }); } else { // REGULAR USER LOGIN FLOW // Step 3b: Find or create user profile let profile = await db.query( 'SELECT * FROM profiles WHERE telegram_id = $1', [telegramId] );
if (!profile.rows.length) { // Auto-create profile linked to Telegram profile = await db.query( `INSERT INTO profiles (alias_name, telegram_id, telegram_username, telegram_verified_at, moderation_status) VALUES ($1, $2, $3, NOW(), 'pending') RETURNING *`, [`User_${telegramId}`, telegramId, telegramUsername] ); }
// Step 4: Create session const sessionToken = require('crypto').randomBytes(32).toString('hex'); const expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); // 30 days
await db.query( `INSERT INTO telegram_sessions (telegram_id, user_id, session_token, expires_at) VALUES ($1, $2, $3, $4)`, [telegramId, profile.rows[0].id, sessionToken, expiresAt] );
// Step 5: Set session cookie const cookieStore = await cookies(); cookieStore.set('telegram_user_session', sessionToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax', path: '/', maxAge: 30 * 24 * 60 * 60 // 30 days in seconds });
return Response.json({ success: true, redirectTo: `/profile/${profile.rows[0].alias_name}`, message: 'User login successful' }); } } catch (error) { console.error('Telegram auth error:', error); return Response.json( { error: 'Authentication failed' }, { status: 500 } ); }}B. Verify Session Middleware
Section titled “B. Verify Session Middleware”File: lib/auth/verify-telegram-session.ts
import { db } from '@/lib/db/client';
export async function verifyTelegramSession( sessionToken: string, type: 'admin' | 'user' = 'user'): Promise<{ valid: boolean; telegramId?: number; userId?: string; adminId?: string }> { try { const result = await db.query( `SELECT * FROM telegram_sessions WHERE session_token = $1 AND expires_at > NOW()`, [sessionToken] );
if (!result.rows.length) { return { valid: false }; }
const session = result.rows[0];
if (type === 'admin' && !session.admin_id) { return { valid: false }; } if (type === 'user' && !session.user_id) { return { valid: false }; }
return { valid: true, telegramId: session.telegram_id, userId: session.user_id, adminId: session.admin_id }; } catch (error) { console.error('Session verification error:', error); return { valid: false }; }}C. Protect Admin Routes
Section titled “C. Protect Admin Routes”File: app/moderation/middleware.ts (or use route-level middleware)
import { NextRequest, NextResponse } from 'next/server';import { verifyTelegramSession } from '@/lib/auth/verify-telegram-session';
export async function middleware(request: NextRequest) { const sessionToken = request.cookies.get('telegram_session')?.value;
if (!sessionToken) { return NextResponse.redirect(new URL('/login/telegram', request.url)); }
const session = await verifyTelegramSession(sessionToken, 'admin');
if (!session.valid) { return NextResponse.redirect(new URL('/login/telegram', request.url)); }
return NextResponse.next();}
export const config = { matcher: ['/moderation/:path*']};1.4 Frontend Components
Section titled “1.4 Frontend Components”A. Telegram Login Button (Client Component)
Section titled “A. Telegram Login Button (Client Component)”File: app/components/TelegramLoginButton.tsx
'use client';
import Script from 'next/script';import { useEffect } from 'react';import { useRouter } from 'next/navigation';
interface TelegramLoginButtonProps { callbackUrl?: string; isAdmin?: boolean; size?: 'small' | 'medium' | 'large';}
export function TelegramLoginButton({ callbackUrl = '/profile', isAdmin = false, size = 'large'}: TelegramLoginButtonProps) { const router = useRouter();
useEffect(() => { // Handle Telegram login widget callback if (typeof window !== 'undefined') { (window as any).onTelegramAuth = async (user: any) => { try { const response = await fetch('/api/auth/telegram/callback', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-admin-attempt': String(isAdmin) }, body: JSON.stringify(user) });
const data = await response.json();
if (data.success) { router.push(data.redirectTo || callbackUrl); } else { alert(data.error || 'Login failed'); } } catch (error) { console.error('Login error:', error); alert('An error occurred during login'); } }; } }, [router, callbackUrl, isAdmin]);
return ( <> <Script src="https://telegram.org/js/telegram-widget.js?15" strategy="beforeInteractive" /> <div className="flex justify-center my-4"> <telegram-login data-telegram-login={process.env.NEXT_PUBLIC_TELEGRAM_BOT_USERNAME!} data-size={size} data-onauth="onTelegramAuth" data-request-access="write" /> </div> </> );}B. Login Page
Section titled “B. Login Page”File: app/login/telegram/page.tsx
import { TelegramLoginButton } from '@/app/components/TelegramLoginButton';
export default function TelegramLoginPage() { return ( <div className="flex min-h-screen items-center justify-center bg-gray-50"> <div className="mx-auto w-full max-w-md rounded-lg bg-white p-8 shadow"> <h1 className="mb-2 text-2xl font-bold">Login with Telegram</h1> <p className="mb-6 text-gray-600"> Use your Telegram account to log in securely. </p>
<TelegramLoginButton callbackUrl="/profile" isAdmin={false} size="large" />
<p className="mt-4 text-center text-sm text-gray-500"> We respect your privacy. We only store your Telegram ID, not your phone number or other data. </p>
<hr className="my-6" />
<h2 className="mb-4 text-lg font-semibold">Admin Access</h2> <TelegramLoginButton callbackUrl="/moderation" isAdmin={true} size="medium" /> </div> </div> );}2. Telegram Bot (Secondary)
Section titled “2. Telegram Bot (Secondary)”2.1 Setup
Section titled “2.1 Setup”Step 1: Create Bot
Section titled “Step 1: Create Bot”(Already done via BotFather above — save your token)
Step 2: Set Webhook for Incoming Messages
Section titled “Step 2: Set Webhook for Incoming Messages”# Configure webhook (one-time setup)curl -X POST https://api.telegram.org/bot<YOUR_BOT_TOKEN>/setWebhook \ -d "url=https://trystpilot.xyz/api/webhooks/telegram"Step 3: Test Bot
Section titled “Step 3: Test Bot”# Send test message to your bot# Find @trystpilot_bot in Telegram, send any message# Check logs for webhook delivery2.2 Webhook Endpoint
Section titled “2.2 Webhook Endpoint”File: app/api/webhooks/telegram/route.ts
import { db } from '@/lib/db/client';
interface TelegramUpdate { update_id: number; message?: { message_id: number; from: { id: number; username: string }; chat: { id: number }; text: string; };}
export async function POST(request: Request) { try { const update: TelegramUpdate = await request.json();
if (!update.message) { return Response.json({ ok: true }); // Ignore non-message updates }
const { from, chat, text } = update.message; const telegramId = from.id;
// Command handling if (text === '/start') { await sendTelegramMessage( chat.id, `👋 Welcome to Trystpilot!\n\n` + `Log in at: https://trystpilot.xyz/login/telegram\n\n` + `Commands:\n` + `/status - Check your profile status\n` + `/help - Get help\n` + `/logout - Log out` ); } else if (text === '/status') { // Check user profile const profile = await db.query( 'SELECT alias_name, moderation_status, review_count FROM profiles WHERE telegram_id = $1', [telegramId] );
if (!profile.rows.length) { await sendTelegramMessage( chat.id, 'No profile found. Log in at https://trystpilot.xyz/login/telegram' ); } else { const p = profile.rows[0]; await sendTelegramMessage( chat.id, `📊 Profile Status\n\n` + `Alias: ${p.alias_name}\n` + `Status: ${p.moderation_status}\n` + `Reviews: ${p.review_count}` ); } } else if (text === '/help') { await sendTelegramMessage( chat.id, `ℹ️ Help\n\n` + `Trystpilot is an anonymous review platform.\n` + `Use our website at https://trystpilot.xyz\n\n` + `Questions? Contact us at support@trystpilot.xyz` ); } else { // Default response await sendTelegramMessage( chat.id, `I don't understand that command.\n\n/help for available commands` ); }
return Response.json({ ok: true }); } catch (error) { console.error('Telegram webhook error:', error); return Response.json({ ok: false }); }}
// Helper function to send messages via Telegram Bot APIasync function sendTelegramMessage(chatId: number, text: string) { const response = await fetch( `https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/sendMessage`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ chat_id: chatId, text: text, parse_mode: 'Markdown' }) } );
if (!response.ok) { throw new Error(`Telegram API error: ${response.statusText}`); }}2.3 Optional: Moderation Notifications
Section titled “2.3 Optional: Moderation Notifications”File: lib/telegram/send-notification.ts
export async function notifyAdminViaTelegram( adminTelegramId: number, message: string) { const response = await fetch( `https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/sendMessage`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ chat_id: adminTelegramId, text: `⚠️ Moderation Alert\n\n${message}`, parse_mode: 'Markdown' }) } );
if (!response.ok) { throw new Error(`Failed to send notification`); }}
// Usage in moderation dashboard:// await notifyAdminViaTelegram(admin.telegram_id,// `New review flagged for profile "${profile.alias_name}"`)3. Environment Variables
Section titled “3. Environment Variables”Add to .env.example and Vercel Project Settings
Section titled “Add to .env.example and Vercel Project Settings”# ─── Telegram Authentication ───────────────────────────────────────────# Bot token from BotFather (/newbot command)TELEGRAM_BOT_TOKEN=<your-bot-token>
# Bot username (for widget, must end in _bot)NEXT_PUBLIC_TELEGRAM_BOT_USERNAME=trystpilot_bot
# Admin whitelist (comma-separated Telegram IDs; empty = no whitelist)TELEGRAM_ADMIN_WHITELIST=123456789,987654321
# Optional: Encryption key for session tokens (generate: openssl rand -hex 32)TELEGRAM_SESSION_SECRET=<random-hex-string>4. Database Migrations
Section titled “4. Database Migrations”Create migration file: db/migrations/006_telegram_auth.sql
-- Telegram authentication tables
-- Add Telegram columns to admin_usersALTER TABLE admin_users ADD COLUMN IF NOT EXISTS ( telegram_id BIGINT UNIQUE, telegram_username VARCHAR(255), telegram_first_name VARCHAR(255), telegram_verified_at TIMESTAMP, telegram_hash VARCHAR(255));
-- Add Telegram columns to profilesALTER TABLE profiles ADD COLUMN IF NOT EXISTS ( telegram_id BIGINT UNIQUE, telegram_username VARCHAR(255), telegram_verified_at TIMESTAMP);
-- Telegram session tableCREATE TABLE IF NOT EXISTS telegram_sessions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), telegram_id BIGINT NOT NULL, user_id UUID REFERENCES profiles(id) ON DELETE CASCADE, admin_id UUID REFERENCES admin_users(id) ON DELETE CASCADE, session_token VARCHAR(255) UNIQUE NOT NULL, expires_at TIMESTAMP NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT at_least_one_user CHECK ( (user_id IS NOT NULL AND admin_id IS NULL) OR (user_id IS NULL AND admin_id IS NOT NULL) ));
CREATE INDEX idx_telegram_sessions_token ON telegram_sessions(session_token);CREATE INDEX idx_telegram_sessions_expires ON telegram_sessions(expires_at);CREATE INDEX idx_telegram_sessions_user_id ON telegram_sessions(user_id);CREATE INDEX idx_telegram_sessions_admin_id ON telegram_sessions(admin_id);5. Security Considerations
Section titled “5. Security Considerations”- Verify HMAC signatures — Always validate login data using
crypto.createHash() - Check auth timestamp — Reject logins older than 60 seconds (prevents replay attacks)
- Use httpOnly cookies — Session tokens never exposed to JavaScript
- Rotate session tokens — Generate new tokens on login
- Admin whitelist — Restrict admin access to specific Telegram IDs
- Log Telegram logins — Audit trail for security
- Webhook signature verification — Verify requests from Telegram Bot API
❌ DON’T
Section titled “❌ DON’T”- Store bot token in code — Use environment variables only
- Skip HMAC verification — Attackers can forge login data
- Trust client-side data — Always verify signatures server-side
- Log sensitive data — Never log auth tokens or secrets
- Use HTTP for callbacks — Must be HTTPS
- Expose Telegram IDs — Treat as sensitive (not displayed to other users)
6. Testing Locally
Section titled “6. Testing Locally”Setup ngrok for Webhook Testing
Section titled “Setup ngrok for Webhook Testing”# Install ngrok: https://ngrok.com/download
# Start ngrok tunnelngrok http 3000# Output: https://abc123.ngrok.io
# Update bot webhookcurl -X POST https://api.telegram.org/bot<TOKEN>/setWebhook \ -d "url=https://abc123.ngrok.io/api/webhooks/telegram"
# Find your bot in Telegram, send a message# Check terminal for webhook deliveryTest Login Locally
Section titled “Test Login Locally”- Create test user in Telegram
- Click login widget on
http://localhost:3000/login/telegram - Authenticate with test account
- Check database for new session
7. Deployment Checklist
Section titled “7. Deployment Checklist”Before Going Live
Section titled “Before Going Live”- BotFather bot created and token saved
- Domain added to BotFather settings
- Telegram Login Widget verified (test button works)
- Database migrations applied
-
.env.exampleupdated withTELEGRAM_*variables - Vercel Project Settings has all
TELEGRAM_*secrets configured - Webhook endpoint deployed and accessible
- Admin whitelist configured (if applicable)
- Test login flow (widget → callback → redirect)
- Test bot commands (/start, /status, /help)
- Test admin panel access via Telegram
- Logging configured for audit trail
- Terms of Service updated (mention Telegram auth)
- Privacy Policy updated (mention Telegram ID storage)
8. Troubleshooting
Section titled “8. Troubleshooting”| Issue | Cause | Solution |
|---|---|---|
| Widget not showing | Wrong bot username or domain not whitelisted | Verify NEXT_PUBLIC_TELEGRAM_BOT_USERNAME and ask BotFather to add domain again |
| Callback fails with 401 | HMAC signature verification failed | Check TELEGRAM_BOT_TOKEN is correct; verify data is being passed correctly |
| Callback timeout | Database query is slow | Add indexes on telegram_id columns; check connection pooling |
| Webhook not delivering | URL not HTTPS or domain mismatch | Use https:// only; check setWebhook response for errors |
| Session expires immediately | expires_at calculation wrong | Check server clock is correct; verify timestamp logic |
| Admin login shows 403 | Telegram ID not in whitelist | Add your Telegram ID to TELEGRAM_ADMIN_WHITELIST in Vercel |
9. Privacy & Compliance
Section titled “9. Privacy & Compliance”What data is stored:
telegram_id— Unique identifier (not reverse-identifiable)telegram_username— Public username (user-provided)first_name— First name only (not last name)
What is NOT stored:
- ❌ Phone number
- ❌ Email address
- ❌ Location data
- ❌ Chat history
- ❌ Profile picture
Privacy statement for users:
We only collect your Telegram ID and first name for authentication. We do not have access to your phone number, email, or any other personal data. You can revoke our bot’s access anytime by blocking it in Telegram.
10. Optional: Telegram Passport (Advanced)
Section titled “10. Optional: Telegram Passport (Advanced)”For identity verification in Phase 2+, Telegram Passport can collect verified documents:
// Example: Request passport dataconst requestButton = { text: 'Verify Identity', request_user: { request_id: 1, user_is_bot: false, user_is_premium: false }};
// Available passport types:// - personal_details (name, DOB, gender, nationality)// - passport / driver_license / identity_card// - address_document// - utility_billNot recommended for MVP — Too complex. Use widget login only.
11. API Reference
Section titled “11. API Reference”Telegram Login Widget Parameters
Section titled “Telegram Login Widget Parameters”| Parameter | Type | Required | Example |
|---|---|---|---|
data-telegram-login | string | ✅ | trystpilot_bot |
data-size | string | ❌ | large |
data-auth-url | string | ❌ | https://trystpilot.xyz/api/auth/telegram/callback |
data-request-access | string | ❌ | write |
data-onauth | function | ❌ | onTelegramAuth |
Telegram Auth Response Object
Section titled “Telegram Auth Response Object”{ id: 123456789, // Unique Telegram ID first_name: "John", // First name username: "john_doe", // Optional username photo_url: "https://...", // Optional profile photo auth_date: 1234567890, // Unix timestamp hash: "abcd1234..." // HMAC-SHA256 signature}12. Related Documentation
Section titled “12. Related Documentation”docs/SECURITY.md— Security & compliancedocs/DEVOPS.md— Deployment & CI/CDCLAUDE.md— Project status.env.example— Environment variable reference
13. Support & Resources
Section titled “13. Support & Resources”- Telegram Bot API Docs: https://core.telegram.org/bots
- Telegram Login Widget: https://core.telegram.org/widgets/login
- Telegram Passport: https://core.telegram.org/passport
- BotFather Commands: https://core.telegram.org/bots#botfather
- Webhook Documentation: https://core.telegram.org/bots/api#setwebhook
Status: ✅ Ready for implementation Estimated effort: 2-3 hours for MVP (widget + basic bot) Next phase: Telegram Passport for identity verification (Phase 2+)
Last updated: 2026-03-02