Skip to content

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:

Comparison: Separate vs Unified Bots
┌─────────────────────────────────────────────────────────────────┐
│ 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:

PlatformRequired CredentialsWhere to Get
TelegramBot Token@BotFather on Telegram
SlackBot Token, App Token, Signing Secretapi.slack.com
DingTalkApp Key, App Secretopen.dingtalk.com
FeishuApp ID, App Secretopen.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:

Message Flow
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 response

The AstrMessageEvent object provides:

  • event.platform: Which platform sent the message
  • event.get_sender_name(): Unified sender name
  • event.message_str: Normalized message text
  • event.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:

BotFather conversation
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:

Telegram Configuration
{
"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:

Terminal
ngrok http 6185
# Gives: https://abc123.ngrok.io

Then 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:

AstrBot logs
[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”:

Slack App Creation
App Name: MyMultiPlatformBot
Workspace: [Select your workspace]

Step 2: Configure OAuth Scopes

In the app settings, I navigated to “OAuth & Permissions” and added these Bot Token Scopes:

Required Slack Scopes
app_mentions:read - Read messages that @mention the bot
channels:history - Read messages in channels
channels:read - View basic channel info
chat:write - Send messages
groups:history - Read messages in private channels
im:history - Read messages in DMs
im:read - View DM info
mpim:history - Read messages in group DMs

At 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:

Event Subscription Settings
Request URL: https://my-server.com/api/slack/events

Slack verifies this URL by sending a challenge request. AstrBot handles this automatically.

I subscribed to these events:

Subscribed Events
app_mention - Bot is mentioned in a channel
message.im - Message sent in DM with bot
message.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:

Generated Tokens
Bot User OAuth Token: xoxb-1234567890-ABCDEFghijklmnOPQRSTuvwxyZ
App-Level Token: xapp-1-ABCDEFghijklmnOPQRSTuvwxyZ-1234567890
Signing Secret: abc123def456ghi789jkl012mno345pqr

Step 5: Configure AstrBot

In AstrBot, I added a Slack adapter:

Slack Configuration
{
"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:

DingTalk App Creation
App Name: MultiPlatformBot
App Type: Enterprise Internal App

After creation, I obtained:

DingTalk Credentials
App Key: dingabcdefghijklmnopqrstuv
App Secret: abcdefghijklmnopqrstuvwxyz123456

Step 2: Configure Event Subscription

In the app settings, I enabled “Message Reception” and configured the callback URL:

Event Subscription Settings
Callback URL: https://my-server.com/api/dingtalk/callback
Token: my-verification-token
EncodingAESKey: [Auto-generated or custom]

DingTalk requires a specific callback verification. When I first set this up, I got this error:

DingTalk Callback 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 Configuration
Robot Name: MultiPlatformBot
Robot Icon: [Upload image]
Message Reception: Enabled

Step 4: Configure AstrBot

DingTalk Configuration
{
"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:

Feishu App Creation
App Name: MultiPlatformBot
App Type: Self-built app

Step 2: Configure Permissions

In “Permissions & Scopes”, I added:

Required Feishu Scopes
im:message - Send and receive messages
im:message:send_as_bot - Send messages as bot
im:chat - Access chat info

Step 3: Enable Event Subscription

In “Event Subscriptions”, I configured:

Event Subscription Settings
Request URL: https://my-server.com/api/feishu/events
Encrypt Key: [Auto-generated]
Verification Token: [Auto-generated]

I subscribed to:

Subscribed Events
im.message.receive_v1 - Receive messages

Step 4: Configure AstrBot

Feishu Configuration
{
"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:

main.py
from astrbot.api.event import filter, AstrMessageEvent
from 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:

Platform-specific formatting
@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:

Feature detection
@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

Telegram conversation
Me: /hello
Bot: Hello, John! You're on telegram.
Me: /info
Bot: Bot Information:
Platform: telegram
Sender: John
Session ID: telegram_123456789

Slack Test

Slack conversation
Me: /hello
Bot: Hello, John! You're on slack.
Me: @MultiPlatformBot hello
Bot: Hello, John! You're on slack.

Note: Slack requires mentioning the bot in channels with @BotName.

DingTalk Test

DingTalk conversation
Me: /hello
Bot: Hello, John! You're on dingtalk.

Feishu Test

Feishu conversation
Me: /hello
Bot: 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:

  1. Server is accessible from internet
  2. HTTPS certificate is valid
  3. Firewall allows inbound connections
  4. 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:

PlatformRate Limit
Telegram30 messages/second to same chat
Slack1 message/second per channel (Tier 3)
DingTalkVaries by API
Feishu5 messages/second

AstrBot has built-in rate limiting. I configured it:

rate_limit_config.json
{
"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:

  1. Gather credentials from each platform’s developer portal
  2. Configure webhook endpoints for message reception
  3. Add each platform adapter in AstrBot’s WebUI
  4. 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:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments