Problem Statement
WhatsApp is currently the only conversational interface. Telegram is a large, distinct user base — especially within crypto-native communities — with a different (and in some ways simpler) bot API. This issue adds Telegram as a second real channel, proving that the platform's conversational core is actually channel-agnostic rather than hard-wired to WhatsApp/Twilio specifics.
Current State
The WhatsApp integration is src/whatsapp/handler.ts (message ingestion + Twilio signature verification), src/whatsapp/userManager.ts (resolving a WhatsApp number to a User), and src/whatsapp/formatters.ts (the actual reply-formatting layer — richer than the older src/nlp/responses.ts stub, which appears to only cover 5 basic replies and may not even be fully wired into the live handler). Intent parsing itself, src/nlp/parser.ts, is channel-agnostic in principle: it takes a text string and returns an Intent, resolved via a regex fast-path with a Claude Haiku fallback (unless AI_MODE=local). This is the piece that should be shared, unmodified, between channels.
Proposed Solution
Data Model
Extend user identity to support multiple channels — likely a channel + channelId pair replacing (or added alongside) however the WhatsApp number is currently stored on User, e.g.:
model LinkedChannel {
id String @id @default(uuid())
userId String
channel ChannelType // WHATSAPP, TELEGRAM
channelId String // WhatsApp number or Telegram chat id
linkedAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([channel, channelId])
}
enum ChannelType { WHATSAPP TELEGRAM }
(Exact shape depends on how User↔WhatsApp-number linkage is modeled today — the implementer should check src/whatsapp/userManager.ts and adapt rather than assume this schema is final.)
API Surface
POST /api/telegram — new webhook route analogous to /api/whatsapp, verifying the request came from Telegram (Telegram's webhook secret-token header mechanism, the equivalent of Twilio's signature check) before processing.
Integration Points
- New
src/telegram/ module mirroring the shape of src/whatsapp/ — handler.ts (webhook ingestion + verification), userManager.ts (chat-id-to-User resolution via LinkedChannel), formatters.ts (Telegram-specific reply formatting, e.g. Markdown/HTML message formatting conventions differ from WhatsApp's).
- Both
handler.tss call into the same src/nlp/parser.ts parseIntent(message) — this is the point of the feature. Command coverage (balance, deposit, withdraw, goal/alert queries introduced by the other issues in this batch) stays in sync across channels by construction, because there's only one parser, not because both channels independently reimplement the same logic.
- Route-level rate limiting: apply a rate limiter group consistent with the existing
webhookRateLimiter used for the WhatsApp inbound webhook (src/middleware/rateLimiter.ts), not the general-purpose one, since this is the same class of "inbound webhook from a third party" traffic.
- Account linking: a user needs a way to link a Telegram chat to an existing account (e.g. a linking code generated via WhatsApp/API and entered in Telegram, or vice versa) — this is new UX, not just a backend wiring exercise, and should be designed explicitly rather than assumed trivial.
Edge Cases & Failure Modes
- A message arrives from an unlinked Telegram chat id — respond with clear linking instructions, not a silent failure or a confusing "balance" response for a nonexistent account.
- The same logical command parses differently due to platform-specific text quirks (e.g. Telegram's own emoji/formatting shortcuts leaking into the message text) — the regex fast-path in
parser.ts may need minor tolerance additions; these should be additive and not regress WhatsApp parsing (regression tests on both channels' existing fixtures required).
- Telegram webhook verification failure (missing/incorrect secret token) — reject with 401 the same way an invalid Twilio signature is rejected today, not silently processed.
Security & Privacy Considerations
- Telegram's webhook authenticity model (secret token in a header) is different from Twilio's HMAC signature scheme — implement it correctly per Telegram's documented mechanism rather than assuming the Twilio pattern transfers directly.
- Account-linking flow must not allow linking a Telegram chat to an account the requester doesn't control (e.g. a linking code must be single-use, short-lived, and tied to the account that generated it).
Out of Scope
- Telegram-specific features with no WhatsApp equivalent (inline keyboards, bot commands menu, group chat support) beyond what's needed for basic 1:1 conversational parity.
- Migrating existing WhatsApp users to Telegram — this is an additive channel, not a replacement.
Suggested Implementation Plan
LinkedChannel model (or equivalent) and account-linking flow design + implementation.
src/telegram/handler.ts with webhook verification, wired through the existing src/nlp/parser.ts.
src/telegram/formatters.ts for Telegram-appropriate reply formatting.
- Regression tests confirming WhatsApp parsing is unaffected; new integration tests for the Telegram webhook path.
Acceptance Criteria
Problem Statement
WhatsApp is currently the only conversational interface. Telegram is a large, distinct user base — especially within crypto-native communities — with a different (and in some ways simpler) bot API. This issue adds Telegram as a second real channel, proving that the platform's conversational core is actually channel-agnostic rather than hard-wired to WhatsApp/Twilio specifics.
Current State
The WhatsApp integration is
src/whatsapp/handler.ts(message ingestion + Twilio signature verification),src/whatsapp/userManager.ts(resolving a WhatsApp number to aUser), andsrc/whatsapp/formatters.ts(the actual reply-formatting layer — richer than the oldersrc/nlp/responses.tsstub, which appears to only cover 5 basic replies and may not even be fully wired into the live handler). Intent parsing itself,src/nlp/parser.ts, is channel-agnostic in principle: it takes a text string and returns anIntent, resolved via a regex fast-path with a Claude Haiku fallback (unlessAI_MODE=local). This is the piece that should be shared, unmodified, between channels.Proposed Solution
Data Model
Extend user identity to support multiple channels — likely a
channel+channelIdpair replacing (or added alongside) however the WhatsApp number is currently stored onUser, e.g.:(Exact shape depends on how
User↔WhatsApp-number linkage is modeled today — the implementer should checksrc/whatsapp/userManager.tsand adapt rather than assume this schema is final.)API Surface
POST /api/telegram— new webhook route analogous to/api/whatsapp, verifying the request came from Telegram (Telegram's webhook secret-token header mechanism, the equivalent of Twilio's signature check) before processing.Integration Points
src/telegram/module mirroring the shape ofsrc/whatsapp/—handler.ts(webhook ingestion + verification),userManager.ts(chat-id-to-Userresolution viaLinkedChannel),formatters.ts(Telegram-specific reply formatting, e.g. Markdown/HTML message formatting conventions differ from WhatsApp's).handler.tss call into the samesrc/nlp/parser.tsparseIntent(message)— this is the point of the feature. Command coverage (balance, deposit, withdraw, goal/alert queries introduced by the other issues in this batch) stays in sync across channels by construction, because there's only one parser, not because both channels independently reimplement the same logic.webhookRateLimiterused for the WhatsApp inbound webhook (src/middleware/rateLimiter.ts), not the general-purpose one, since this is the same class of "inbound webhook from a third party" traffic.Edge Cases & Failure Modes
parser.tsmay need minor tolerance additions; these should be additive and not regress WhatsApp parsing (regression tests on both channels' existing fixtures required).Security & Privacy Considerations
Out of Scope
Suggested Implementation Plan
LinkedChannelmodel (or equivalent) and account-linking flow design + implementation.src/telegram/handler.tswith webhook verification, wired through the existingsrc/nlp/parser.ts.src/telegram/formatters.tsfor Telegram-appropriate reply formatting.Acceptance Criteria
src/nlp/parser.ts, with zero duplicated intent-parsing logicdocs/openapi.yamlupdated