Connecting Discord, Slack, Telegram, and WhatsApp Integrations
Wire Discord, Slack, Telegram, and WhatsApp as adapters into one self-hosted personal agent brain - not as four separate agents.
Platform APIs and terms change. Verify bot capabilities, rate limits, and automation rules at build time.
Summary
Normalize every platform into one event shape, authorize per-platform sender ids, run a single bounded agent loop, and egress through the same adapter that received the message. Add platforms only after the first private channel is boringly reliable.
Recipe
- Choose a primary control channel. Prefer a private Telegram DM or a locked Discord/Slack channel you alone can use.
- Create bot credentials in each platform developer console; store tokens in the host env file (
chmod 600). - Implement one adapter interface per platform:
parse_inbound→ChatEvent,authorize,send_message. - Allowlist sender and channel ids per platform (default deny). Group chats stay off high-risk tools.
- Route all events into the same agent runner with
max_turnsand shared prefs/memory. - Preserve reply routing metadata (
platform,thread_id,channel_id) so answers return to the right place. - Add idempotency on platform message ids to survive retries and double delivery.
- Smoke-test each adapter alone, then together; confirm cross-platform users cannot share one identity by accident.
- Document pause and token rotation for every bot token you added.
Working Example
Shared types and a multi-adapter dispatch sketch:
from pydantic import BaseModel
from typing import Callable, Protocol
class ChatEvent(BaseModel):
platform: str
sender_id: str
text: str
channel_id: str
thread_id: str | None = None
message_id: str
class ChatAdapter(Protocol):
name: str
def poll_or_handle(self) -> list[ChatEvent]: ...
def send(self, event: ChatEvent, text: str) -> dict: ...
ALLOWED = {
"telegram": {"111"},
"discord": {"222"},
"slack": {"U333"},
# whatsapp: add only after Business API setup is real
}
SEEN: set[str] = set() # persist to disk in production
def authorize(event: ChatEvent) -> bool:
return event.sender_id in ALLOWED.get(event.platform, set())
def handle_event(event: ChatEvent, adapters: dict[str, ChatAdapter], run_agent) -> None:
key = f"{event.platform}:{event.message_id}"
if key in SEEN:
return
SEEN.add(key)
if not authorize(event):
return
answer = run_agent(event.text, meta=event.model_dump())
adapters[event.platform].send(event, answer)
def tick(adapters: dict[str, ChatAdapter], run_agent: Callable) -> None:
for adapter in adapters.values():
for event in adapter.poll_or_handle():
handle_event(event, adapters, run_agent)Telegram-shaped parse (structure only; use the official Bot API client of your choice):
def telegram_to_event(update: dict) -> ChatEvent | None:
msg = update.get("message") or {}
if "text" not in msg:
return None
return ChatEvent(
platform="telegram",
sender_id=str(msg["from"]["id"]),
text=msg["text"],
channel_id=str(msg["chat"]["id"]),
thread_id=str(msg["chat"]["id"]),
message_id=str(msg["message_id"]),
)Per-platform env sketch:
TELEGRAM_BOT_TOKEN=...
DISCORD_BOT_TOKEN=...
SLACK_BOT_TOKEN=...
SLACK_APP_TOKEN=... # if using socket mode
# WhatsApp Cloud API (verify current field names at build)
WHATSAPP_TOKEN=...
WHATSAPP_PHONE_NUMBER_ID=...Deep Dive
One brain, many connectors
Clawdbot/OpenClaw-style personal agents treat each platform as a connector. The model, tools, memory, and policy stay central.
Duplicating a full agent per platform multiplies cost, drift, and incident surface. Share the runner; isolate credentials and rate limits.
Platform notes (architecture level)
| Platform | Common personal pattern | Integration style | Watch-outs |
|---|---|---|---|
| Telegram | Solo DM control room | Bot API long poll or webhook | Group privacy; bot command UX |
| Discord | Private server + channel | Gateway bot | Shared servers need role gates |
| Slack | Private channel in a workspace | Events API / Socket Mode | Workspace admin and retention policies |
| Notify trusted contacts | Business / Cloud API | Heavier setup; stricter automation rules |
Exact intents, slash commands, and button payloads differ. Keep those differences inside the adapter, not in tool code.
Authorization models
- DM allowlist: map platform user id → permitted.
- Channel allowlist: even if the user is you, ignore random channels the bot was invited to.
- Role gates (Discord/Slack): useful when a household shares a server - still prefer separate tool credentials per person for mail/shell.
- Linking: store
telegram:111anddiscord:222as the same human only after an explicit link flow you control.
Never trust a display name. Ids are the unit of auth.
Group vs DM policy
| Surface | Default tool set |
|---|---|
| Private DM with allowlisted you | Personal tools per your risk policy |
| Private channel, only you | Same as DM |
| Shared group / server | Read-only or no tools; maybe FAQ only |
| Public channel | Do not connect personal mail/calendar/shell |
Idempotency and ordering
Chat platforms retry. Persist processed platform:message_id keys (SQLite or a file-backed set) so double delivery does not double-send mail later.
Do not assume global ordering across platforms. Task state should key off your internal task id, not "the last Discord message."
Rate limits and spam to yourself
Cap outbound messages per minute per platform.
Batch digests instead of one bubble per email.
Disable proactive posts during quiet hours except explicit VIP break-glass rules.
WhatsApp special case
WhatsApp is often excellent for notifications and weaker as a first deep tool-control surface because of Business API setup and policy constraints.
If you add it, start with outbound digests to yourself, not full bidirectional shell control.
Gotchas
- One shared allowlist string for all platforms. Ids are not portable across Telegram and Discord.
- Bot invited to a public server. Discoverable bots get random commands; deny by default.
- Putting platform-specific if/else inside every tool. Tools should not know Discord snowflakes.
- Logging full message bodies forever. Chat content is personal data; retain on purpose.
- Thread metadata dropped. Replies land in the wrong place or as orphan DMs.
- Slack workspace-wide install for a personal bot. Prefer the minimum install surface your admin allows.
- WhatsApp session hacks. Unofficial clients break ToS and reliability; prefer official APIs.
- Assuming buttons equal security. Approval UIs still need server-side checks that the clicker is allowlisted.
Alternatives
| Approach | Pros | Cons |
|---|---|---|
| Single platform only | Simple auth and UX | No fallback if that app is down |
| Multi-adapter one brain | Consistent tools/memory | More tokens to rotate |
| Agent per platform | Isolation | Drift and duplicated spend |
| Chat as notify-only + web UI for control | Stronger forms UX | Extra surface to host |
| Email as the only channel | Universal | Poor real-time control |
FAQs
How many platforms should I connect first?
One. Add a second only when you have a concrete reason (for example work lives in Slack, personal control in Telegram).
Can coworkers use my Slack bot?
Not if it has personal mail, calendar, or shell. Shared invocation with personal tools is an incident pattern.
Long poll or webhook?
Long poll is simpler for a private VPS without public HTTP. Webhooks need TLS and verified signatures; use them when latency or platform requirements demand it.
How do I map one human across platforms?
Explicit link: from an authenticated DM, issue a one-time code the user pastes in the other platform. Never auto-merge on display name.
Should group mentions run the full agent?
Usually no. Require a hard mention plus a reduced tool set, or ignore groups entirely for personal hosts.
Where do slash commands fit?
Adapters can translate /digest into a structured goal string before the model runs. That reduces prompt ambiguity for frequent jobs.
How do I test without spamming friends?
Use a private channel and a second throwaway account as the non-allowlisted negative test.
What if two platforms deliver the same logical request?
Deduplicate on your task layer (goal hash + time window) or accept that two wakes may run - budgets make that safe.
Do I need a separate model per platform?
No. Route by task difficulty, not by chat brand.
How does this relate to Clawdbot/OpenClaw?
Those projects popularized multi-chat personal hosts. Copy the connector pattern; evaluate any concrete integration code on security and maintenance, not marketing.
Related
- Self-Hosted Personal Agents Basics - first single-platform loop
- Setting Up Clawdbot/OpenClaw on a VPS or Local Machine - host process setup
- The Self-Hosted Personal Agent Mental Model - adapters vs brain
- Self-Hosted Personal Agents Best Practices - allowlist and token habits
- Chat-Integrated Agents - platform cheatsheet
- What a Personal AI Agent Actually Does Day to Day - why chat is the UI
Stack versions: Pins from the category manifest (verify at build): OpenRouter (~315+ models, July 2026 pricing/fees); LangGraph 1.0+; CrewAI 1.14+; Microsoft Agent Framework 1.0; Vercel AI SDK 6; Pydantic AI (latest); LlamaIndex (latest); OpenAI Agents SDK (latest + MCP); MCP (Linux Foundation governance); A2A (HTTP+SSE+JSON-RPC 2.0); Solana
@solana/web3.js+@solana/spl-token.