Telegram today. The rest behind the same trait.
openclaw-channels exposes the Channel,
ChannelInbound, and ChannelOutbound traits. A
full Telegram Bot API adapter ships today. Discord, Slack, Signal,
Matrix, and WhatsApp are tracked roadmap items; each is a
single-file PR.
Telegram shipped Full Bot API adapter with attachments, allowlist-based access control, and rule-based agent routing.
- Long polling + webhook
- Photo / document / voice attachments
- Per-channel allowlist
- Routes to multiple agents by rule
Discord planned Slash commands, threaded conversations, attachments.
- Roadmap target
- Trait-based, single-PR add
Slack planned App Mention, DM, slash commands, threaded replies.
- Roadmap target
- Bolt-equivalent surface
Signal planned Privacy-first channel via signal-cli or signald.
- Roadmap target
Matrix planned Federated open-protocol adapter.
- Roadmap target
- End-to-end encrypted rooms
WhatsApp planned WhatsApp Business API, with template-message support.
- Roadmap target
The Channel trait
pub trait Channel: Send + Sync {
fn id(&self) -> ChannelId;
fn name(&self) -> &str;
}
#[async_trait]
pub trait ChannelInbound: Channel {
async fn run(&self, sink: mpsc::Sender<InboundEvent>) -> Result<()>;
}
#[async_trait]
pub trait ChannelOutbound: Channel {
async fn send(&self, to: PeerId, message: OutboundMessage) -> Result<MessageId>;
} Allowlist + routing
Before an inbound event reaches an agent, it passes through an allowlist and a router. Default allowlist is deny-all. Default router has no rules — you have to opt in.
let allowlist = Allowlist::new()
.allow(AllowRule::PeerId("123456789".into()))
.allow(AllowRule::GroupId("-1001234567890".into()));
let router = Router::new()
.add(100, RouteMatcher::Keyword("/code".into()), AgentId("code".into()))
.add(0, RouteMatcher::Always, AgentId("default".into())); Telegram adapter
Full Bot API client built on reqwest:
- Long-polling (webhook support on the roadmap).
- Photo / document / voice / video attachments, normalised into the runtime's
Attachmentshape. - Bot token stored as an
ApiKeywith redaction guarantees. - Per-channel allowlist and routing rules.
{
"channels": {
"telegram": {
"enabled": true,
"bot_token": "{{ TELEGRAM_BOT_TOKEN }}",
"allowlist": [
{ "kind": "PeerId", "value": "123456789" }
],
"routing": [
{ "priority": 100, "match": { "kind": "Keyword", "value": "/code" }, "target": "code-agent" },
{ "priority": 0, "match": { "kind": "Always" }, "target": "default" }
]
}
}
} {{ TELEGRAM_BOT_TOKEN }} is resolved from the credential
store at load. The plaintext never sits in the config file.
Adding a new adapter
The Telegram adapter is the reference. To add Discord, Slack, Signal, Matrix, or WhatsApp:
- Implement
Channel + ChannelInbound + ChannelOutboundin a new module undercrates/openclaw-channels/. - Register it in
ChannelRegistry::register(). - Add a config schema entry.
- Write a smoke test.
Open a PR. We'll review.
The deep dive
For the full walkthrough — Bot API specifics, attachment handling, outbound, configuration — see the Telegram-as-channel article.