askTIAN Telegram Bot — Engineering Response
Document type: Engineering response to developer integration guide v1.0
Date: April 9, 2026
Prepared by: askTIAN Engineering Team
For: External Telegram Bot Developer
Overview
This document supplements the Telegram Bot Integration Guide v1.0 with clarifications, corrections, and additional context based on the current API state. Read this document alongside the original guide. Where this document and the original guide differ, this document takes precedence.
The guide is accurate and complete. The items below are addenda and clarifications, not corrections to errors.
1. Credentials and Pre-requisites
Before writing any code, you need three things from the askTIAN team:
| Item | How to obtain | Notes |
|---|---|---|
TIAN_WALLET_API_KEY | Email [email protected] | Server-side only — never expose in bot messages or logs |
| Callback URL registration | Email [email protected] with your /wallet-callback URL | Required before the wallet auth flow works |
| Telegram Bot Token | @BotFather on Telegram | You manage this yourself |
The TIAN_WALLET_API_KEY is already provisioned in the askTIAN API platform environment. Contact [email protected] with your bot's callback URL and it will be shared securely.
2. Architecture Clarification
The guide's architecture diagram is correct. One important addition: the bot backend should be a separate service from the app.asktian.com backend. Do not embed the bot logic inside the app server. The two services share the same chat.asktian.com and wallet.asktian.com APIs but operate independently.
Telegram User
│
▼
Your Bot Backend (Node.js — separate service)
├──► chat.asktian.com/api/trpc (tRPC over HTTP — no auth header needed)
└──► wallet.asktian.com/api/v1 (REST — X-API-Key: TIAN_WALLET_API_KEY)
The chat.asktian.com tRPC endpoints do not require an Authorization header. Identity is established via sessionId and privyId/tianSessionToken in the request body. The wallet.asktian.com REST endpoints require X-API-Key.
3. Database Schema Recommendation
The guide lists the fields to persist per user. Here is a recommended minimal schema in SQL for reference:
CREATE TABLE bot_users (
telegram_user_id VARCHAR(32) PRIMARY KEY,
session_id VARCHAR(36) NOT NULL UNIQUE, -- UUID, generated once
privy_id VARCHAR(128), -- null until wallet sign-in
session_token VARCHAR(128), -- single-use, expires in 5 min
session_token_expires_at DATETIME,
current_conversation_id INT,
selected_system VARCHAR(32) NOT NULL DEFAULT 'tarot',
balance INT NOT NULL DEFAULT 0, -- cached, refresh before paid reading
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
The session_token is single-use. Clear it immediately after chat.ask consumes it (set to NULL). If session_token_expires_at has passed, call wallet.tianVerify again to get a fresh token before the next paid reading.
4. Session Token Lifecycle
This is the most common integration mistake. The flow is:
- User triggers a paid reading.
- Check
session_token_expires_at— if expired or null, callwallet.tianVerifyto get a fresh token. - Call
chat.askwith the freshtianSessionToken. - Immediately after
chat.askreturns (success or error), setsession_token = NULLin your DB. - Store the new balance from
result.tianPointsNewBalance.
async function getFreshSessionToken(user: BotUser): Promise<string> {
// If token is still valid (not expired and not null), reuse it
if (
user.sessionToken &&
user.sessionTokenExpiresAt &&
new Date(user.sessionTokenExpiresAt) > new Date(Date.now() + 30_000) // 30s buffer
) {
return user.sessionToken;
}
// Token expired or missing — re-verify with wallet
const result = await trpcMutation("wallet.tianVerify", {
privyToken: await getPrivyTokenForUser(user), // your wallet auth flow
});
await db.updateUser(user.telegramUserId, {
sessionToken: result.sessionToken,
sessionTokenExpiresAt: result.sessionExpiresAt,
balance: result.balance,
});
return result.sessionToken;
}
5. Divination System Picker — Recommended UX
The guide lists all 27 systems. For the Telegram UX, we recommend grouping them into a two-level picker to avoid overwhelming users:
Level 1 — Tradition picker (inline keyboard, 5 buttons):
| Button | Systems shown in Level 2 |
|---|---|
| Eastern | Tarot, Runes, Numerology, Astrology, Coin Flip |
| Western | Qimen, BaZi, I Ching, Meihua, Da Liu Ren, Xiao Liu Ren, Tai Yi, Name Analysis, Compatibility, Zodiac, Birthday, Blood Type, Auspicious, Almanac, Divination Lots |
| African | Ifá, Fa/Vodun, Hakata |
| Islamic | Rammal, Khatt al-Raml |
| Indian | Jyotish, Anka Shastra |
| TIAN Blend | Eastern TIAN, Western TIAN, East-West TIAN, African TIAN, Islamic TIAN, Indian TIAN, Global TIAN |
Level 2 — show the systems for the chosen tradition as a second inline keyboard.
For the TIAN Blend systems, always show the cost in TIAN Points on the button label (e.g. "Global TIAN — 130 pts") so users can make an informed choice before committing.
6. TIAN Blend Readings — Typing Indicator Pattern
TIAN Blend readings take 30–90 seconds. The guide correctly notes to show a typing indicator. Here is the recommended pattern:
// Send initial "consulting" message (keep the message_id for editing later)
const waitMsg = await bot.sendMessage(chatId,
"🌌 TIAN is consulting the cosmos across all traditions…\n\n" +
"_This may take up to 90 seconds for a Global TIAN reading._",
{ parse_mode: "Markdown" }
);
// Keep sending typing action every 4 seconds while waiting
const typingInterval = setInterval(() => {
bot.sendChatAction(chatId, "typing").catch(() => {});
}, 4000);
try {
const result = await trpcMutation("chat.ask", askParams);
clearInterval(typingInterval);
// Delete the waiting message and send the real result
await bot.deleteMessage(chatId, waitMsg.message_id);
await bot.sendMessage(chatId, result.answer, { parse_mode: "Markdown" });
} catch (err) {
clearInterval(typingInterval);
await bot.deleteMessage(chatId, waitMsg.message_id);
// handle error
}
7. Error Handling — Complete Error Code Reference
The guide covers UNAUTHORIZED and insufficient points. Here is the complete set of error codes the bot should handle:
| Error message contains | Cause | Bot response |
|---|---|---|
Insufficient TIAN Points | Balance too low | Send inline button: "💳 Top Up with Stripe" → wallet.tianTopupUrl |
Please sign in | No privyId or expired session | Send inline button: "🔐 Sign In" → wallet.asktian.com/tg-auth |
Session token expired | tianSessionToken is stale | Re-call wallet.tianVerify, retry chat.ask once |
Session not found | sessionId is invalid | Re-call session.init, retry |
Rate limit exceeded | Too many requests | Wait 60 seconds, then retry |
Conversation not found | conversationId is stale | Create new conversation, retry |
| Any other error | API or network issue | "Something went wrong. Please try /ask again." |
8. Webhook vs Polling
The guide's example uses polling ({ polling: true }). For production, use webhooks instead — polling is unreliable at scale and drains the Telegram API quota.
// Production: webhook
const bot = new TelegramBot(process.env.TELEGRAM_BOT_TOKEN!, { webHook: true });
await bot.setWebHook(`https://your-bot-backend.com/telegram-webhook`, {
secret_token: process.env.TELEGRAM_WEBHOOK_SECRET!, // validate on every request
});
// Express handler
app.post("/telegram-webhook", (req, res) => {
// Validate secret_token header
if (req.headers["x-telegram-bot-api-secret-token"] !== process.env.TELEGRAM_WEBHOOK_SECRET) {
return res.sendStatus(403);
}
bot.processUpdate(req.body);
res.sendStatus(200);
});
Always validate the x-telegram-bot-api-secret-token header on every webhook request to prevent spoofed updates (as noted in the guide's security checklist).
9. Message Length and Markdown Handling
askTIAN readings can be long (500–2,000 characters). Telegram has a 4,096 character limit per message. If result.answer exceeds this, split it:
function splitMessage(text: string, maxLen = 4000): string[] {
if (text.length <= maxLen) return [text];
const parts: string[] = [];
let remaining = text;
while (remaining.length > maxLen) {
// Split at last paragraph break before the limit
const splitAt = remaining.lastIndexOf("\n\n", maxLen);
const cutAt = splitAt > 0 ? splitAt : maxLen;
parts.push(remaining.slice(0, cutAt));
remaining = remaining.slice(cutAt).trimStart();
}
if (remaining) parts.push(remaining);
return parts;
}
for (const part of splitMessage(result.answer)) {
await bot.sendMessage(chatId, part, { parse_mode: "Markdown" });
}
The API returns Markdown-formatted text. Use parse_mode: "Markdown" on all reading messages. If Telegram rejects the message due to malformed Markdown (e.g. unmatched asterisks), fall back to parse_mode: undefined (plain text).
10. /history Command Implementation
The guide mentions /history but does not show the implementation. Here is the recommended pattern:
bot.onText(/\/history/, async (msg) => {
const user = await db.getUser(msg.from!.id.toString());
if (!user) return bot.sendMessage(msg.chat.id, "Use /start first.");
const result = await trpcQuery("conversations.list", {
sessionId: user.sessionId,
limit: 5,
});
if (!result.conversations?.length) {
return bot.sendMessage(msg.chat.id, "No conversations yet. Ask me anything!");
}
const lines = result.conversations.map((c: any, i: number) =>
`${i + 1}. *${c.title || "Untitled"}* — ${new Date(c.createdAt).toLocaleDateString()}`
).join("\n");
await bot.sendMessage(msg.chat.id,
`*Your last ${result.conversations.length} conversations:*\n\n${lines}\n\nUse /new to start a fresh conversation.`,
{ parse_mode: "Markdown" }
);
});
11. Recommended Sprint Plan
| Sprint | Duration | Deliverables |
|---|---|---|
| Sprint 1 — Foundation | 1 week | Bot registered, DB schema, session.init, /start command, basic message handler with free Tarot reading |
| Sprint 2 — Wallet Auth | 1 week | Wallet sign-in flow, wallet.tianVerify, balance check, top-up inline button, paid readings working |
| Sprint 3 — Full System Picker | 1 week | All 27 systems in two-level picker, TIAN Blend readings with typing indicator, /system /balance /topup commands |
| Sprint 4 — Polish | 1 week | /history /new commands, message splitting, webhook migration, load testing, security audit |
12. Questions and Support
For API keys, callback URL registration, or technical questions, contact the askTIAN engineering team at [email protected]. Reference this document version (Engineering Response v1.0, April 2026) in your communications.
Join the developer community on Telegram for real-time support.
