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:

ItemHow to obtainNotes
TIAN_WALLET_API_KEYEmail [email protected]Server-side only — never expose in bot messages or logs
Callback URL registrationEmail [email protected] with your /wallet-callback URLRequired before the wallet auth flow works
Telegram Bot Token@BotFather on TelegramYou 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:

  1. User triggers a paid reading.
  2. Check session_token_expires_at — if expired or null, call wallet.tianVerify to get a fresh token.
  3. Call chat.ask with the fresh tianSessionToken.
  4. Immediately after chat.ask returns (success or error), set session_token = NULL in your DB.
  5. 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):

ButtonSystems shown in Level 2
EasternTarot, Runes, Numerology, Astrology, Coin Flip
WesternQimen, BaZi, I Ching, Meihua, Da Liu Ren, Xiao Liu Ren, Tai Yi, Name Analysis, Compatibility, Zodiac, Birthday, Blood Type, Auspicious, Almanac, Divination Lots
AfricanIfá, Fa/Vodun, Hakata
IslamicRammal, Khatt al-Raml
IndianJyotish, Anka Shastra
TIAN BlendEastern 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 containsCauseBot response
Insufficient TIAN PointsBalance too lowSend inline button: "💳 Top Up with Stripe" → wallet.tianTopupUrl
Please sign inNo privyId or expired sessionSend inline button: "🔐 Sign In" → wallet.asktian.com/tg-auth
Session token expiredtianSessionToken is staleRe-call wallet.tianVerify, retry chat.ask once
Session not foundsessionId is invalidRe-call session.init, retry
Rate limit exceededToo many requestsWait 60 seconds, then retry
Conversation not foundconversationId is staleCreate new conversation, retry
Any other errorAPI 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

SprintDurationDeliverables
Sprint 1 — Foundation1 weekBot registered, DB schema, session.init, /start command, basic message handler with free Tarot reading
Sprint 2 — Wallet Auth1 weekWallet sign-in flow, wallet.tianVerify, balance check, top-up inline button, paid readings working
Sprint 3 — Full System Picker1 weekAll 27 systems in two-level picker, TIAN Blend readings with typing indicator, /system /balance /topup commands
Sprint 4 — Polish1 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.