How to Make Your OpenClaw Skill Work with Discord & Slack
How to Make Your OpenClaw Skill Work with Discord & Slack

Let's cut to the chase: you built an OpenClaw skill, it works beautifully in the local console, and now you want it to actually do something useful — like respond to messages in your team's Discord server or post automated updates in Slack. You Google around, find Slack's API docs, lose forty-five minutes in OAuth scope hell, switch to Discord, realize their gateway system is a completely different paradigm, and suddenly it's three hours later and you haven't shipped anything.
I've been there. Multiple times. And having gone through the pain of wiring up OpenClaw skills to both platforms, I can tell you that it's both easier than you think and more annoying than it should be — unless you know the exact path to take. This post is that path.
Why This Is Harder Than It Should Be
The core issue isn't OpenClaw. OpenClaw is actually excellent at abstracting away the hard parts of building AI-powered skills. The problem is that Discord and Slack are fundamentally different platforms with fundamentally different philosophies about how bots should work.
Slack wants you to:
- Create an app in their developer portal
- Configure OAuth scopes (and there are dozens of them)
- Set up event subscriptions with URL verification
- Handle a 3-second response timeout on webhooks
- Navigate the difference between bot tokens and user tokens
Discord wants you to:
- Register an application and bot user
- Understand gateway intents (and which are "privileged")
- Deal with slash command registration (global vs. guild-specific)
- Handle interaction endpoints or gateway connections
- Work with snowflake IDs that look nothing like Slack's channel strings
When you're building an OpenClaw skill that needs to work on both? You're essentially maintaining two integration layers with completely different auth models, event formats, message structures, and rate limits.
This is exactly the kind of tedious infrastructure work that pulls you away from building the actual thing you care about — the skill logic itself.
The OpenClaw Approach: One Skill, Multiple Platforms
Here's the good news. OpenClaw's skill architecture was designed with this exact problem in mind. A skill is platform-agnostic by default. You write your logic once, and OpenClaw handles the translation layer between your skill and whatever messaging platform it's connected to.
The basic architecture looks like this:
[Discord] ←→ [OpenClaw Connector] ←→ [Your Skill] ←→ [OpenClaw Connector] ←→ [Slack]
Your skill sits in the middle. It receives normalized events and sends normalized responses. The connectors on either side handle the platform-specific nonsense.
Let me walk you through setting this up from scratch.
Step 1: Configure Your Skill for Messaging Platforms
First, make sure your OpenClaw skill is set up to accept external message events. In your skill's claw.config.js (or .ts if you're using TypeScript), you need to declare messaging as an input source:
// claw.config.js
module.exports = {
skill: {
name: 'my-team-assistant',
version: '1.0.0',
inputs: ['message', 'slash_command', 'reaction'],
platforms: ['slack', 'discord'],
},
server: {
port: process.env.PORT || 3000,
}
};
The inputs array tells OpenClaw what kinds of events your skill can handle. The platforms array tells it which connectors to initialize. This is important — OpenClaw won't load unnecessary connectors, which keeps your skill lightweight.
Step 2: Set Up Slack Integration
Create Your Slack App
Go to api.slack.com/apps and create a new app. Choose "From a manifest" if you want to skip the tedious manual configuration. Here's a manifest that works for most OpenClaw skills:
display_information:
name: My OpenClaw Skill
description: AI-powered team assistant
features:
bot_user:
display_name: openclaw-bot
always_online: true
oauth_config:
scopes:
bot:
- chat:write
- chat:write.public
- channels:history
- channels:read
- groups:read
- im:history
- im:read
- reactions:read
- files:read
- files:write
settings:
event_subscriptions:
bot_events:
- message.channels
- message.groups
- message.im
- reaction_added
interactivity:
is_enabled: true
socket_mode:
enabled: true
Pro tip: Enable Socket Mode. I cannot stress this enough. Socket Mode means Slack connects to you via WebSocket instead of you needing to expose a public HTTP endpoint. No ngrok, no tunnel, no public URL during development. It just works.
Add Slack Credentials to OpenClaw
Once your app is created, grab these three things:
- Bot Token (starts with
xoxb-) - App Token (starts with
xapp-) — you need this for Socket Mode - Signing Secret — for verifying webhook payloads
Add them to your environment:
# .env
SLACK_BOT_TOKEN=xoxb-your-token-here
SLACK_APP_TOKEN=xapp-your-app-token
SLACK_SIGNING_SECRET=your-signing-secret
Now configure OpenClaw's Slack connector:
// connectors/slack.js
const { SlackConnector } = require('openclaw/connectors');
const slack = new SlackConnector({
botToken: process.env.SLACK_BOT_TOKEN,
appToken: process.env.SLACK_APP_TOKEN,
signingSecret: process.env.SLACK_SIGNING_SECRET,
socketMode: true,
});
module.exports = slack;
That's it for Slack setup. OpenClaw's SlackConnector handles:
- Socket Mode connection and reconnection
- Event signature verification
- Token refresh (if using OAuth flow)
- Rate limit queuing (Slack's tier-based limits)
- The dreaded 3-second acknowledgment timeout (it acks immediately and processes in the background)
That last point is huge. If you've ever had a Slack bot that worked fine with fast operations but broke the moment you added AI processing (because LLM calls take way longer than 3 seconds), OpenClaw's background processing solves this completely. It acknowledges the event instantly and shows a typing indicator while your skill does its work.
Step 3: Set Up Discord Integration
Create Your Discord Bot
Head to discord.com/developers/applications, create a new application, and add a bot user.
Under the "Bot" tab, you need to enable these Privileged Gateway Intents:
- Message Content Intent (required to read message text)
- Server Members Intent (if your skill needs user info)
Under "OAuth2 > URL Generator," select these scopes:
botapplications.commands
And these bot permissions:
- Send Messages
- Read Message History
- Embed Links
- Attach Files
- Use Slash Commands
- Add Reactions
Copy the generated URL and use it to invite the bot to your server.
Add Discord Credentials to OpenClaw
# .env (add to existing)
DISCORD_BOT_TOKEN=your-discord-bot-token
DISCORD_CLIENT_ID=your-client-id
DISCORD_GUILD_ID=your-test-server-id # optional, for dev
Configure the connector:
// connectors/discord.js
const { DiscordConnector } = require('openclaw/connectors');
const discord = new DiscordConnector({
token: process.env.DISCORD_BOT_TOKEN,
clientId: process.env.DISCORD_CLIENT_ID,
guildId: process.env.DISCORD_GUILD_ID, // optional: limits to one server during dev
intents: ['Guilds', 'GuildMessages', 'MessageContent', 'GuildMessageReactions'],
});
module.exports = discord;
Setting the guildId during development is a quality-of-life thing — slash commands register instantly on a specific guild but take up to an hour to register globally. Don't waste time waiting during development.
Step 4: Write Your Skill Logic (Once)
Here's where OpenClaw really shines. Your skill handler doesn't know or care whether it's talking to Slack or Discord:
// skills/assistant.js
const { Skill } = require('openclaw');
const assistant = new Skill({
name: 'team-assistant',
async onMessage(event) {
// event.text - the message content (normalized)
// event.platform - 'slack' or 'discord'
// event.user - normalized user object
// event.channel - normalized channel reference
// event.thread - thread context (if in a thread)
// Show typing indicator while we process
await event.showTyping();
// Your AI logic here
const response = await this.process(event.text, {
context: await event.thread?.getHistory() || [],
user: event.user.displayName,
});
// Reply in the same thread/channel
await event.reply(response);
},
async onSlashCommand(event) {
if (event.command === 'summarize') {
await event.showTyping();
const history = await event.channel.getHistory({ limit: 100 });
const summary = await this.summarize(history);
await event.reply({
title: 'Channel Summary',
description: summary,
actions: [
{ type: 'button', label: 'Summarize Last Hour', id: 'summarize_hour' },
{ type: 'button', label: 'Summarize Today', id: 'summarize_today' },
],
});
}
},
async onAction(event) {
if (event.actionId === 'summarize_hour') {
const history = await event.channel.getHistory({ since: '1h' });
const summary = await this.summarize(history);
await event.update({ description: summary });
}
},
});
module.exports = assistant;
Notice what's happening here. The event.reply() call with a structured object (title, description, actions) automatically gets translated to:
- Slack: Block Kit with sections, markdown text, and interactive buttons
- Discord: Embeds with action row components
You wrote it once. OpenClaw formats it correctly for each platform. No more maintaining parallel template systems.
Step 5: Wire It All Together
// index.js
const { OpenClaw } = require('openclaw');
const slack = require('./connectors/slack');
const discord = require('./connectors/discord');
const assistant = require('./skills/assistant');
const claw = new OpenClaw({
connectors: [slack, discord],
skills: [assistant],
});
claw.start().then(() => {
console.log('OpenClaw is running on Slack and Discord');
});
Run it:
node index.js
Your skill is now live on both platforms simultaneously.
Handling the Edge Cases
The basic setup covers 80% of use cases. Here's how to handle the other 20%.
File Handling
Uploading files is one of the most inconsistently implemented features across platforms. Slack requires a multi-step upload process. Discord lets you attach files directly but has size limits based on server boost level.
OpenClaw normalizes this:
await event.reply({
content: 'Here is the report',
files: [
{ data: pdfBuffer, name: 'report.pdf' },
{ url: 'https://example.com/chart.png', name: 'chart.png' },
],
});
OpenClaw handles the platform-specific upload flow, checks size limits, and falls back to link sharing if a file exceeds the platform's limit.
Cross-Platform Message Formatting
Slack uses mrkdwn (their custom markdown flavor). Discord uses standard Markdown. OpenClaw's message formatter handles the translation:
// This works correctly on both platforms
await event.reply('**Bold text** and `inline code` and\n> a blockquote');
But if you need platform-specific formatting:
await event.reply({
content: 'Check out this update',
platformOverrides: {
slack: {
blocks: [/* native Block Kit */],
},
discord: {
embeds: [/* native Discord embeds */],
},
},
});
This escape hatch is there when you need it, but honestly, I rarely use it. The normalized format handles almost everything.
Rate Limiting
You don't need to think about this. Seriously. OpenClaw's connectors maintain per-platform, per-route rate limit buckets and automatically queue requests that would exceed limits. If you're sending messages to 50 channels at once:
// This won't hammer the API — OpenClaw queues automatically
await Promise.all(
channels.map(ch => claw.sendMessage({ channel: ch, content: 'Update deployed' }))
);
No 429 errors. No lost messages. No manual sleep/retry logic.
Error Handling That Actually Helps
One of the most underrated features of OpenClaw is its error context. Compare these two scenarios:
Raw Slack API error: "channel_not_found"
OpenClaw error:
SlackError: Failed to send message to #deployments (C04ABCD1234)
Reason: Channel not found. The bot may not be invited to this channel.
Suggestion: Run '/invite @your-bot-name' in #deployments
Platform: slack
Scope required: chat:write
Scopes granted: chat:write, channels:read
That second one actually tells you what to do. The number of hours I've saved not debugging cryptic platform errors is significant.
Testing Without Real Platforms
OpenClaw ships with a mock system that lets you test your skill without touching real APIs:
const { MockClaw } = require('openclaw/testing');
describe('Team Assistant', () => {
let claw;
beforeEach(() => {
claw = new MockClaw({ skills: [assistant] });
});
it('responds to messages', async () => {
const response = await claw.simulate.message({
platform: 'slack',
text: 'What were the action items from yesterday?',
channel: 'general',
user: { name: 'Alice' },
});
expect(response.text).toContain('action items');
});
it('handles rate limits gracefully', async () => {
claw.mock.slack.simulateRateLimit({ duration: 5000 });
// Skill should still work — messages queued
const response = await claw.simulate.message({ text: 'Hello' });
expect(response).toBeDefined();
});
});
You can simulate platform-specific scenarios — rate limits, permission errors, network failures — without needing real credentials. This is how you build reliable skills without deploying to production to find out what breaks.
The Fastest Way to Get Started
Look, everything I've described above works. I've set it up from scratch multiple times. But if I'm being honest, the initial configuration — creating apps on both platforms, getting the right scopes, setting up the connectors, configuring intents — takes a solid afternoon the first time you do it.
If you don't want to set all of this up manually, Felix's OpenClaw Starter Pack on Claw Mart includes pre-built skills with Discord and Slack connectors already configured. It's $29 and comes with the exact connector setup I described above, plus a few pre-configured skills (including a team assistant and notification bot) that you can customize or use as reference. I picked it up when I was setting up my second OpenClaw project and it saved me the entire first day of boilerplate. The Slack manifest and Discord intent configuration alone are worth it — those are the parts where one wrong checkbox wastes an hour of debugging.
Whether you use the starter pack or do it manually, the key takeaway is the same: write your skill logic once, let OpenClaw handle the platform translation.
What to Build Next
Once you have the basic integration working, here are the most useful skills I've seen people build:
-
Standup Bot — Collects async standups via DM, posts summary to a channel. Works identically on Slack and Discord.
-
Incident Response — Monitors alerts, creates threads, tracks resolution. The thread management API makes this surprisingly clean.
-
Knowledge Base Q&A — Team members ask questions, skill searches your docs/wiki and responds with relevant answers. The AI processing + typing indicator combo makes this feel natural.
-
PR Review Notifier — Watches GitHub webhooks, posts formatted updates to the right channel, lets people claim reviews with button clicks.
-
Meeting Summarizer — Takes meeting notes (or transcripts), generates summaries and action items, posts them to the relevant channel with assignee mentions.
All of these benefit from the cross-platform architecture. Your team uses Slack? Great. The engineering Discord? Also covered. Same skill, same logic, two platforms.
Final Thoughts
The messaging platform integration problem is a solved problem in OpenClaw — you just need to know the right configuration to use. The connector pattern means you're never locked into one platform, and the normalized event/response format means you're not maintaining parallel codebases.
Set up your connectors, write your skill logic against OpenClaw's normalized API, and let the framework handle the ugly parts. That's the whole playbook.
Now stop reading and go ship something.
Recommended for this post
