How to Deploy a Chatbot Across Telegram, Slack, DingTalk, and Feishu with AstrBot
Problem
I needed a chatbot that worked across four different messaging platforms: Telegram for international users, Slack for team communication, DingTalk for Chinese enterprise clients, and Feishu for internal collaboration.
My first approach was building separate bots for each platform. After two weeks, I had:
- A Python bot for Telegram using
python-telegram-bot - A Node.js bot for Slack using Bolt SDK
- A Java bot for DingTalk using their SDK
- Another Python bot for Feishu
This was a maintenance nightmare. Feature requests required implementing the same logic four times. Bug fixes meant updating four codebases. Documentation was scattered.
When I showed this to a colleague, they pointed me to AstrBot’s multi-platform architecture.
Why Multi-Platform Matters
Before diving into setup, let me show why unified deployment matters:
┌─────────────────────────────────────────────────────────────────┐│ SEPARATE BOTS │├─────────────────────────────────────────────────────────────────┤│ Telegram Bot ──→ Python + python-telegram-bot ││ Slack Bot ────→ Node.js + Bolt SDK ││ DingTalk Bot ─→ Java + DingTalk SDK ││ Feishu Bot ───→ Python + Feishu SDK │├─────────────────────────────────────────────────────────────────┤│ Problems: ││ - 4 codebases to maintain ││ - 4 different deployment pipelines ││ - 4 sets of tests ││ - Feature parity is manual │└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐│ UNIFIED ASTRBOT │├─────────────────────────────────────────────────────────────────┤│ ┌─────────────────┐ ││ │ Plugin Logic │ ← Write once ││ └────────┬────────┘ ││ │ ││ ┌─────────────┼─────────────┐ ││ ▼ ▼ ▼ ││ ┌──────────┐ ┌──────────┐ ┌──────────┐ ││ │ Telegram │ │ Slack │ │ DingTalk │ Feishu... ││ │ Adapter │ │ Adapter │ │ Adapter │ ││ └──────────┘ └──────────┘ └──────────┘ │├─────────────────────────────────────────────────────────────────┤│ Benefits: ││ - Single codebase ││ - One deployment pipeline ││ - Platform-agnostic plugins ││ - Automatic feature parity │└─────────────────────────────────────────────────────────────────┘The key insight: AstrBot normalizes messages from different platforms into a unified event format. My plugin code doesn’t need to know which platform sent the message.
Prerequisites
Before starting, I gathered credentials for each platform:
| Platform | Required Credentials | Where to Get |
|---|---|---|
| Telegram | Bot Token | @BotFather on Telegram |
| Slack | Bot Token, App Token, Signing Secret | api.slack.com |
| DingTalk | App Key, App Secret | open.dingtalk.com |
| Feishu | App ID, App Secret | open.feishu.cn |
I also needed:
- A server with public HTTPS endpoint (for webhooks)
- Python 3.10+ or Docker
- AstrBot installed (see my getting started guide for installation)
Understanding the Architecture
AstrBot uses an adapter pattern to abstract platform differences:
User sends message │ ▼┌───────────────┐│ Platform │ Telegram API / Slack API / DingTalk API / Feishu API│ (External) │└───────┬───────┘ │ Webhook/Long-polling ▼┌───────────────┐│ Adapter │ Platform-specific code│ (AstrBot) │ - Parse platform message format│ │ - Normalize to AstrMessageEvent└───────┬───────┘ │ ▼┌───────────────┐│ Plugin │ Platform-agnostic code│ (Your Bot) │ - Receives AstrMessageEvent│ │ - Returns plain text or rich content└───────┬───────┘ │ ▼┌───────────────┐│ Adapter │ - Convert response to platform format│ (AstrBot) │ - Send via platform API└───────────────┘ │ ▼User receives responseThe AstrMessageEvent object provides:
event.platform: Which platform sent the messageevent.get_sender_name(): Unified sender nameevent.message_str: Normalized message textevent.plain_result(): Send text response
Telegram Bot Configuration
I started with Telegram since it’s the simplest to set up.
Step 1: Create Bot with BotFather
On Telegram, I searched for @BotFather and sent /newbot:
Me: /newbot
BotFather: Alright, a new bot. How shall we call it? Please choose a name for your bot.
Me: MyMultiPlatformBot
BotFather: Good. Now let's choose a username for your bot. It must end in `bot`. Like TetrisBot or Tetris_bot.
Me: MyMultiPlatformBot_bot
BotFather: Done! Congratulations on your new bot... Use this token to access the HTTP API: 1234567890:ABCdefGHIjklMNOpqrsTUVwxyz
Keep your token secure...Step 2: Configure AstrBot
In AstrBot’s WebUI (http://localhost:6185), I navigated to Platform Management and added a Telegram adapter:
{ "platform": "telegram", "token": "1234567890:ABCdefGHIjklMNOpqrsTUVwxyz", "webhook_url": "https://my-server.com/api/telegram/webhook", "pre_ack_emoji": true, "allow_inline_query": false}The webhook_url is where Telegram sends messages. For testing without a public server, I used ngrok:
ngrok http 6185# Gives: https://abc123.ngrok.ioThen I set webhook_url to https://abc123.ngrok.io/api/telegram/webhook.
Step 3: Verify Connection
After saving the configuration, I sent a message to my bot on Telegram. In AstrBot’s log viewer, I saw:
[2026-03-03 22:00:15] [INFO] [Telegram] Received message from user: Hello[2026-03-03 22:00:15] [INFO] [Telegram] Processing with default handler...This confirmed the webhook was working.
Slack Bot Integration
Slack requires more setup than Telegram. I needed to create a Slack App, configure OAuth permissions, and enable event subscriptions.
Step 1: Create Slack App
At api.slack.com, I clicked “Create New App” and chose “From scratch”:
App Name: MyMultiPlatformBotWorkspace: [Select your workspace]Step 2: Configure OAuth Scopes
In the app settings, I navigated to “OAuth & Permissions” and added these Bot Token Scopes:
app_mentions:read - Read messages that @mention the botchannels:history - Read messages in channelschannels:read - View basic channel infochat:write - Send messagesgroups:history - Read messages in private channelsim:history - Read messages in DMsim:read - View DM infompim:history - Read messages in group DMsAt first, I only added chat:write and app_mentions:read. But my bot couldn’t read messages in channels. Adding channels:history fixed this.
Step 3: Enable Event Subscriptions
In “Event Subscriptions”, I enabled events and added the callback URL:
Request URL: https://my-server.com/api/slack/eventsSlack verifies this URL by sending a challenge request. AstrBot handles this automatically.
I subscribed to these events:
app_mention - Bot is mentioned in a channelmessage.im - Message sent in DM with botmessage.channels - Message in public channel (if bot is member)Step 4: Install App to Workspace
After configuring, I clicked “Install to Workspace” and authorized the app. This generated tokens:
Bot User OAuth Token: xoxb-1234567890-ABCDEFghijklmnOPQRSTuvwxyZApp-Level Token: xapp-1-ABCDEFghijklmnOPQRSTuvwxyZ-1234567890Signing Secret: abc123def456ghi789jkl012mno345pqrStep 5: Configure AstrBot
In AstrBot, I added a Slack adapter:
{ "platform": "slack", "bot_token": "xoxb-1234567890-ABCDEFghijklmnOPQRSTuvwxyZ", "app_token": "xapp-1-ABCDEFghijklmnOPQRSTuvwxyZ-1234567890", "signing_secret": "abc123def456ghi789jkl012mno345pqr", "scopes": ["chat:write", "channels:history", "app_mentions:read"], "socket_mode": false}I tried using Socket Mode initially (setting socket_mode: true), which doesn’t require a public webhook URL. But I found webhook mode more reliable for production.
DingTalk Robot Setup
DingTalk has two types of bots: internal enterprise apps and custom robots via webhook. For full functionality, I created an internal app.
Step 1: Create DingTalk App
At open.dingtalk.com, I navigoted to “App Development” and created a new app:
App Name: MultiPlatformBotApp Type: Enterprise Internal AppAfter creation, I obtained:
App Key: dingabcdefghijklmnopqrstuvApp Secret: abcdefghijklmnopqrstuvwxyz123456Step 2: Configure Event Subscription
In the app settings, I enabled “Message Reception” and configured the callback URL:
Callback URL: https://my-server.com/api/dingtalk/callbackToken: my-verification-tokenEncodingAESKey: [Auto-generated or custom]DingTalk requires a specific callback verification. When I first set this up, I got this error:
{"errcode":71010,"errmsg":"URL address could not be resolved"}The issue was my server wasn’t responding to DingTalk’s verification POST request. AstrBot handles this, but my firewall was blocking the endpoint. Adding a firewall rule fixed it.
Step 3: Configure Robot
In the “Robot” section of the app, I enabled the robot and configured:
Robot Name: MultiPlatformBotRobot Icon: [Upload image]Message Reception: EnabledStep 4: Configure AstrBot
{ "platform": "dingtalk", "app_key": "dingabcdefghijklmnopqrstuv", "app_secret": "abcdefghijklmnopqrstuvwxyz123456", "callback_url": "https://my-server.com/api/dingtalk/callback", "robot_code": "dingabcdefg", "enable_card_messages": true}DingTalk supports “message cards” - rich interactive messages with buttons and forms. I enabled this feature but implemented it later.
Feishu/Lark Bot Configuration
Feishu (Lark internationally) has similar requirements to DingTalk.
Step 1: Create Feishu App
At open.feishu.cn, I created a new app:
App Name: MultiPlatformBotApp Type: Self-built appStep 2: Configure Permissions
In “Permissions & Scopes”, I added:
im:message - Send and receive messagesim:message:send_as_bot - Send messages as botim:chat - Access chat infoStep 3: Enable Event Subscription
In “Event Subscriptions”, I configured:
Request URL: https://my-server.com/api/feishu/eventsEncrypt Key: [Auto-generated]Verification Token: [Auto-generated]I subscribed to:
im.message.receive_v1 - Receive messagesStep 4: Configure AstrBot
{ "platform": "feishu", "app_id": "cli_abcdefghijklmnopqrstuvwxyz123456", "app_secret": "abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuv", "encrypt_key": "abcdefghijklmnopqrstuvwxyz123456", "verification_token": "abcdefghijklmnopqrstuvwxyz123456", "event_url": "https://my-server.com/api/feishu/events"}Step 5: Publish App
Feishu requires apps to be published before they can be used. In the app management, I submitted for approval. For internal apps in the same organization, approval is instant.
Writing Unified Plugin Code
Now comes the key part: writing code that works on all four platforms.
Platform-Agnostic Example
Here’s a simple plugin that works identically on all platforms:
from astrbot.api.event import filter, AstrMessageEventfrom astrbot.api.star import Context, Star, register
@register("multibot", "cowrie", "Multi-platform chatbot", "1.0.0", "https://github.com/example/multibot")class MultiPlatformBot(Star): def __init__(self, context: Context): super().__init__(context)
@filter.command("hello") async def hello_command(self, event: AstrMessageEvent): # Works identically on Telegram, Slack, DingTalk, and Feishu platform = event.platform user_name = event.get_sender_name() yield event.plain_result(f"Hello, {user_name}! You're on {platform}.")
@filter.command("echo") async def echo_command(self, event: AstrMessageEvent): # Echo back the user's message message = event.message_str # Remove the command part if message.startswith("/echo"): message = message[5:].strip() yield event.plain_result(f"Echo: {message}")
@filter.regex(r"^(?i)help$") async def help_command(self, event: AstrMessageEvent): help_text = """Available commands:/hello - Greet the bot/echo <message> - Echo a message/help - Show this help/info - Get bot information""" yield event.plain_result(help_text)
@filter.command("info") async def info_command(self, event: AstrMessageEvent): info = f"""Bot Information:Platform: {event.platform}Sender: {event.get_sender_name()}Session ID: {event.session_id}""" yield event.plain_result(info)The key is using event.plain_result() for responses. AstrBot handles converting this to the appropriate format for each platform:
- Telegram: Plain text message
- Slack: Formatted message block
- DingTalk: Text message
- Feishu: Text content
Handling Platform-Specific Features
Sometimes you need platform-specific formatting. Here’s how I handle that:
@filter.command("rich")async def rich_message(self, event: AstrMessageEvent): platform = event.platform
if platform == "slack": # Slack supports mrkdwn formatting yield event.plain_result("```code block``` and *bold text*") elif platform == "telegram": # Telegram supports MarkdownV2 yield event.plain_result("*bold text* and `inline code`") elif platform == "feishu": # Feishu rich text yield event.plain_result("**bold text** and `code`") else: # Plain fallback for DingTalk and others yield event.plain_result("bold text and code")I use event.platform to detect which platform sent the message and format accordingly.
Feature Detection
For more robust platform handling, I check capabilities:
@filter.command("button")async def button_demo(self, event: AstrMessageEvent): platform = event.platform
if platform == "telegram": # Telegram inline keyboards # AstrBot supports this through platform-specific methods yield event.plain_result("Button feature: Use /keyboard for interactive buttons") elif platform == "slack": # Slack block kit yield event.plain_result("Button feature: Use /blocks for interactive blocks") elif platform == "feishu": # Feishu message cards yield event.plain_result("Button feature: Use /card for interactive cards") else: yield event.plain_result("This platform doesn't support interactive buttons")Testing Multi-Platform Deployment
After setting up all platforms and writing the plugin, I tested each one.
Telegram Test
Me: /helloBot: Hello, John! You're on telegram.
Me: /infoBot: Bot Information: Platform: telegram Sender: John Session ID: telegram_123456789Slack Test
Me: /helloBot: Hello, John! You're on slack.
Me: @MultiPlatformBot helloBot: Hello, John! You're on slack.Note: Slack requires mentioning the bot in channels with @BotName.
DingTalk Test
Me: /helloBot: Hello, John! You're on dingtalk.Feishu Test
Me: /helloBot: Hello, John! You're on feishu.All four platforms responded with the same logic, proving the unified approach works.
Troubleshooting Common Issues
Webhook Verification Failures
Each platform verifies webhook URLs differently:
Telegram: Silent failures. Check bot token and webhook URL.
Slack: Returns challenge parameter. AstrBot auto-responds.
DingTalk: Returns encrypted verification. Check AES key.
Feishu: Returns JSON with challenge. AstrBot auto-responds.
If webhooks fail, check:
- Server is accessible from internet
- HTTPS certificate is valid
- Firewall allows inbound connections
- Correct URL in configuration
Message Not Received
I encountered this issue with Slack. Messages in channels weren’t being received.
The fix was adding channels:history scope and re-installing the app. Slack doesn’t notify you about missing scopes - messages just silently fail.
Rate Limiting
Each platform has different rate limits:
| Platform | Rate Limit |
|---|---|
| Telegram | 30 messages/second to same chat |
| Slack | 1 message/second per channel (Tier 3) |
| DingTalk | Varies by API |
| Feishu | 5 messages/second |
AstrBot has built-in rate limiting. I configured it:
{ "enabled": true, "max_requests_per_minute": 20, "cooldown_seconds": 60}Summary
In this post, I showed how to deploy a single chatbot across Telegram, Slack, DingTalk, and Feishu using AstrBot’s unified adapter architecture. The key point is AstrBot’s event normalization lets you write platform-agnostic plugin code that works identically across all supported platforms.
The main steps were:
- Gather credentials from each platform’s developer portal
- Configure webhook endpoints for message reception
- Add each platform adapter in AstrBot’s WebUI
- Write unified plugin code using
AstrMessageEvent
Instead of maintaining four separate codebases, I now have one codebase that deploys to all platforms. Feature additions or bug fixes are implemented once and automatically apply everywhere.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
- 👨💻 AstrBot Official Documentation
- 👨💻 AstrBot GitHub Repository
- 👨💻 Telegram Bot API
- 👨💻 Slack API Documentation
- 👨💻 DingTalk Open Platform
- 👨💻 Feishu/Lark Open Platform
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments