Skip to main content
After this guide, people in a Slack workspace can message your agent directly or mention it in a channel, and it answers in a thread. Lua fills in the Slack app for you: Slack opens its create-app page with the app named after your agent and its permissions and events already set. A private connection uses the app’s bot token and serves one workspace; a public connection installs the app through Lua and can be installed by other workspaces. Both paths exist in the admin dashboard and in lua channels. Verified against lua-cli 3.41.0. The CLI steps on this page (creating the app from Lua’s manifest, the signing secret, and the update and re-check actions) ship in lua-cli 3.42.0. lua-cli 3.41.0 can no longer connect Slack, because the signing secret is now required: until 3.42.0 is out, connect Slack from the admin dashboard. Before you begin
  • A Slack workspace where you can create and install apps.
  • An agent with a promoted production version; see Release an agent.
1

Create the app from Lua's manifest

In the admin dashboard open Agents, select the agent, select + in the Channels section of its Overview tab, and choose Slack. Choose Private — just my workspace or Public — a distributable app, then Create a new app (recommended), and select Create the app in Slack.
Slack opens Create an app with the manifest filled in. Pick the workspace, select Next, then Create and Install. To use an app you already have, choose I already have a Slack app instead; Lua gives you its manifest to apply after you connect.
2

Provider console: copy the values Lua needs

All of them are in the new app’s settings in Slack.
  • Signing Secret, always: Basic Information → App Credentials → Signing Secret (select Show). It is not the Client Secret just above it.
  • Private: install the app first with Install App → Install to Workspace → Allow, then copy the Bot User OAuth Token, which starts with xoxb-.
  • Public: the App ID, Client ID and Client Secret from Basic Information → App Credentials.
3

Connect it

Paste the values into the dashboard form or the CLI prompts and select Connect. The signing secret is required: Lua checks every event Slack sends against it (see Signing secret).
4

Public only: install through Lua

In the dashboard select Install in Slack: Slack’s install screen opens in a small window, and the dashboard confirms once you allow it. The CLI prints the install link instead, which works for 30 minutes; allowing it lands on Lua’s result page with Open in Slack. To let other workspaces install the app, turn on Manage Distribution in Slack and share its install button, or the link https://auth.heylua.ai/slack/install/<appId>.
5

Verify

Send the app a direct message with hello; it answers in a thread under your message. That first message also confirms the signing secret: the connection’s card stops showing Verifying. Invite the app to a channel and mention it; it answers in a thread under that message. lua channels list shows a SLACK entry.

Signing secret

Slack signs every event it sends with the app’s signing secret. The first event that matches the saved secret confirms it; from then on Lua rejects any event that doesn’t match, including one with no signature at all, so nobody can make the agent act on a forged message. Until that first match, an event that doesn’t match is still answered, and the card says Signing secret doesn’t match: paste it again from Basic Information. After it was confirmed, an event that doesn’t match is rejected and the connection shows Signature check failed. If you regenerated the secret in Slack, paste the new one. If you didn’t, it was most likely a forged request, and the message clears with the next real event. Saving a different secret for a connection that is already verified never turns the check off. Until Slack sends an event signed with the new secret, events signed with either the previous or the new one are accepted, and the status shows Verified; new secret not confirmed yet. The first real event signed with the new secret makes it the only one. So a mistyped secret doesn’t stop the bot, and forged events stay rejected. While Signature check failed shows, though, Slack is already signing with a secret Lua doesn’t have: only the right one brings the bot back. Connections made before Lua asked for the secret keep working as before and show Update available; see Update an existing app.

Update an existing app

Lua can’t change an app in your workspace; you apply each new version of the manifest. The Slack connection under Channels shows Update available until Slack has shown Lua the current manifest and the signing secret is saved, and it says which of the two is missing. Select Update; the dialog walks you through it:
  1. Open App Manifest in Slack, switch to JSON and keep a copy of what’s there. Pasting it back and reinstalling undoes the update.
  2. Apply Lua’s manifest on the same JSON tab and Save Changes. It keeps your app’s name and every permission the app already has. If your app has features of its own, such as slash commands, shortcuts or agent mode switched on in Slack, the dialog first asks for your current manifest and gives you a merged one that keeps them, with a list of what it changes in Slack.
  3. Save the signing secret.
  4. Reinstall: a public app with Install in Slack; a private app with the banner Slack shows, or Install App → Reinstall to Workspace. The bot keeps its token and keeps answering.
  5. Open the bot’s DM in Slack. Slack sends Lua a signed event that confirms both the signing secret and the new manifest, and the dialog says Done.
After that, don’t regenerate the signing secret in Slack. If you do, paste the new one in Lua straight away: once the secret is confirmed, Lua rejects every event that doesn’t match it. In the CLI, choose List channels, pick the Slack connection, and use Show the update steps and manifest, Add or replace the signing secret and Re-check permissions with Slack. The CLI’s manifest keeps the permissions your app holds; for an app with slash commands, shortcuts or agent mode switched on in Slack, update from the dashboard so the merge keeps them. What each permission is for is on Slack permissions.

When the app is uninstalled

If someone removes the app from the workspace or revokes its token, Slack tells Lua, Lua checks with Slack that the token no longer works, and the card shows Uninstalled in Slack. Replies that fail because the token is dead trigger the same check. The agent’s settings and conversations are kept. Select Reinstall: a public app reinstalls through Lua; a private app needs a new bot token from Install App → Reinstall to Workspace, pasted with the signing secret, and the same connection comes back.

Settings

Open Agents, select the agent, and in the Channels section of its Overview tab select the Slack connection, or select it on the Channels page. The panel shows the connection’s status first, then every setting with its default. A choice saves when you pick it and can be undone from the confirmation under it; a text field saves when you leave it. Change history lists who changed which setting, and when. The buttons open the same steps as the connection card’s. To change settings from code, send only the keys you change. null resets a setting to its default. The response is the connection’s full status and settings, each with its value and its source. <channel> is the connection’s id or its identifier (T…-A…).
An invalid value answers 400 with errors, keyed by setting, and changes nothing. GET on the same path reads the settings, and GET …/settings/history lists past changes, newest first. The older PUT …/channels/<identifier> with {"slack":{…}} still takes responseMode, autoRespondChannels, reactions and externalUsers, checks them the same way and records them in the history. Changing settings needs the Builder role on the agent; Operators can read them.

While the agent works

As soon as the agent takes a message, it puts 👀 on it, and Slack shows “Your app is working…” (with your app’s name) under it (inside the thread once the thread has replies). The status goes away when the answer arrives; 👀 then becomes ✅. If the turn fails, both go away and no ✅ is added. A button click shows the status only. The status needs only chat:write, which every install has. The reaction needs reactions:write, an optional permission: an app installed before it was in the manifest shows the status alone until it is updated, and so does an app whose admin left it unticked. Turn both off with Show that the agent is working, and pick other emoji under Settings.

Agent mode

Slack’s agent mode shows the bot as an agent: an AGENT badge next to its name, up to four prompt buttons at the top of its direct message, and a Stop button under “Your app is working…” while it answers. It is off by default. Leave it off for workspaces with guests or Slack Connect partners. Turn it on
  • A new app: in the Create your Slack app step, tick Agent mode. Check the description (filled in from the agent, up to 300 characters, shown in Slack’s Agents & tools list) and add up to four prompts, each a button label and the message a click sends. The create link carries them.
  • An app you already have: tick Agent mode on the step where you paste its values.
  • A connected app: open the connection’s settings and select Set up next to Agent mode. Save it there, then apply the updated manifest in Slack and reinstall, as in Update an existing app. The same step changes the prompts or turns agent mode off.
  • CLI (from the lua-cli release after 3.42.0-beta.1): lua channels asks about agent mode when you connect Slack; on a connection, Turn agent mode on or off, or change its prompts saves it and prints the manifest to apply.
If agent mode was switched on by hand in Slack’s app settings, Lua’s manifest keeps it, but Lua can’t read the prompts set there. Add them in Lua first, or applying the manifest removes them. Stop Anyone in the thread can select Stop. The agent stops at once and posts nothing more. Where the app may add reactions, 👀 becomes ⏹️. The conversation keeps the message with the note [Reply stopped by <name> before it was sent], so a later question knows what happened. What a tool already did, such as an email sent or a message posted from code, is not undone. A Stop in the first second or two, before the agent has started, also ends the turn, but the message is not kept in the agent’s memory. While a turn runs, an agent-mode app shows Slack’s agent status in place of the “is working…” status described above. It clears when the answer arrives, when the turn fails, and on Stop. An app switched to agent mode in Slack without the agent_session_stopped event keeps the plain “is working…” status and shows no Stop button.

Home tab and greetings

Home tab. Selecting the bot’s name in Slack opens its Home tab. The page shows:
  • the bot’s name and one line about it;
  • how to reach it: a message in the Messages tab (left out when the bot answers mentions only), a mention in a channel it’s in (it answers in the thread), or /invite to add it to a channel;
  • example questions, under Try asking;
  • a support line, when you wrote one;
  • the note “Answers come from AI and can be wrong.”
Lua writes the page for each person when they open the bot, and again only when its content changed. It has no buttons. Welcome in a direct message. The first time someone opens the bot (its Home tab or its direct message) while their direct message with it is still empty, the bot posts one short welcome there: what it does, and what to ask. It is skipped for an agent-mode app with suggested prompts (Slack shows those at the top of the direct message), when the bot answers mentions only, and for people from another company the bot doesn’t answer. Hello in a channel. When a person adds the bot to a channel, it posts one short hello saying how to ask it. It stays quiet in group direct messages and in Slack Connect channels whose outside people it doesn’t answer. What the bot says about itself. In the Slack connection’s settings you can write an Intro (up to 300 characters), up to four Example questions and a Support contact line. The Home page, the welcome and the hello use them. Left empty, the bot uses the agent-mode description and prompts if it has them, otherwise “An AI assistant for this workspace.” Two switches, both on by default, turn the welcome and the hello off: Welcome people in DMs and Say hello in new channels. The greetings are not added to the agent’s memory of a direct message. An earlier hello in a channel is part of the channel history the agent reads when someone mentions it there. All of this needs the current manifest. If an app’s manifest still has the Home tab off, Slack refuses the page the first time someone opens the bot’s direct message, and the connection shows Update available until the updated manifest is applied. See Update an existing app. The Home tab alone needs no reinstall.

Conversations

A direct-message thread is a private conversation between one person and the agent. A channel thread and a group DM (a direct message between several people and the app) are shared conversations: the agent keeps one transcript for everyone in them and knows who said what. Channels here are public and private channels, including Slack Connect channels shared with other workspaces.
  • Later messages are context. After the first mention, every message in that thread or group DM joins the transcript without a reply, so the next mention knows the whole discussion. In a channel the agent follows the thread, not later top-level messages.
  • Edits and deletes follow Slack. When someone edits a message, the stored copy is replaced by (edited) <new text>; the old wording is gone. When someone deletes a message, including the first message of a thread that has replies, it stays in the transcript as [message deleted], with its sender and time. This applies in channel threads, group DMs and direct messages, to the agent’s memory, the admin dashboard, and your org memory. An edit never gets a reply, even when it adds a mention. What is not changed: the agent’s own earlier replies (edit or delete them in Slack), messages sent before 29 September 2026 (a delete or edit of those is not applied), a long conversation’s summary notes, files already copied when the message arrived, and facts the agent saved to a person’s memory (remove them from memory settings).
  • Speakers are named. Each message reaches the agent with the sender’s name, and people mentioned in it appear by name. Posts from other Slack apps, such as alerts or reports, are kept as context under the app’s name, and an app that mentions the agent gets an answer. An auto-respond channel answers people only, so two agents there never answer each other. A message an app posts for someone with their own Slack sign-in shows in Slack as theirs, and the agent treats it as theirs: it answers it, remembers it under their name, and follows their edits. Slackbot’s own posts, such as reminders, are not kept.
  • Who each person is. Lua knows a Slack person by their own workspace and Slack member id (their organisation, on Enterprise Grid). The first time the bot sees them, the email Slack shows for them links them to the Lua account with that email; after that the member id decides, so a changed or hidden email never turns them into someone new. People from other companies are recognised the same way. Only the person talking to the bot counts as signing in: others writing in the thread, people mentioned, earlier authors and people you message from code are recorded without it. Each other app is one Lua user, whichever way Slack names it. If Slack can’t say who someone is, their message in a thread isn’t kept, and a person talking to the bot is asked to send it again.
  • Slack Connect. People from other workspaces in a shared channel are answered like anyone else, unless People from other companies says otherwise (see Settings), and the files they attach reach the agent too. A file the bot can’t open reaches the agent as a note naming it, even when the message also has text, so the agent can tell the person.
  • Personal memory stays personal. Nothing a person told the agent in a direct message is brought into a channel or group DM. Each channel thread is its own conversation: nothing carries over from one thread to another, or from a thread into direct messages.
  • Direct messages. To continue a conversation, reply in the thread under the agent’s answer; a new top-level message starts a fresh conversation. Each thread has its own transcript, though an agent with personal memory can still recall what it learned about the person earlier. link-me-to:<agentId> and remove-my-link work in a direct message; see Test an agent on Lua’s channels.
  • Your code sees the room. In a tool, a condition, or a processor, Lua.request.conversation lists the people in a channel thread or group DM and marks the current speaker. It is undefined in a direct message. Its externalId is the thread’s key, T…:A…:C…:threadTs (workspace, app, channel, thread), or T…:A…:G… for a group DM; pass it to Channels.send as to: { conversationId } to post there later through the same bot (Post from code).
  • One reply per message. Slack sometimes delivers the same event twice, and a mention in a channel arrives both as a mention and as a message; the agent answers each message once. When a turn fails, the agent posts a short notice instead of staying silent.
  • The admin dashboard shows the room. Activity → Group chats lists each Slack thread and group DM, titled #channel · opening words or Group DM · opening words, with its transcript and the people in it. The tab is read-only. Direct-message conversations appear under Conversations.
  • Rooms close when the agent leaves. Disconnecting Slack in the dashboard archives the connection’s threads and group DMs (Disconnected in Group chats); removing the app from a channel archives that channel’s threads (Bot removed), and uninstalling the app in Slack archives them all (Uninstalled). Their transcripts are kept, and a mention in the same thread after the app is back reopens the room with its memory. Someone who leaves a channel leaves its threads, and a renamed channel renames them. The channel events behind this reach apps created from, or updated to, the current manifest.

Channel behavior

Files

When the agent’s answer carries a documents block, the files follow the answer as one more message in the same thread or direct message.
  • With files:write (in the manifest since 29 September 2026; see Update an existing app), each file stored on Lua’s CDN, up to 25 MB and 10 per message, is uploaded as a Slack file under a 📎 line naming the files. People preview and download it in Slack, under Slack’s access rules.
  • Without it, and for a file hosted elsewhere, larger than 25 MB, or one Slack refuses (a workspace that blocks uploads or that file type), the message lists each file as a link: 📎 Loan Agreement (loan_agreement.pdf). A CDN link opens for anyone who has it.
  • After you re-apply the manifest, the first answer can still send links; the agent learns the new permission from that answer’s post and uploads from the next one.
  • The agent remembers the block it wrote: the file’s name and link. Each uploaded file message has its own delivery record, keyed by Slack’s file id.

Post from code

Channels.send({ channel: 'slack', to, text }) posts through the agent’s own Slack bot, the app you connected above, never a Lua bot. It works from a tool, a job, a webhook, or a workflow code step. Address exactly one of to.conversationId, to.userId, or to.email; a phoneNumber is refused. Which bot posts. A Lua.request.conversation.externalId names its bot. Otherwise the send uses the agent’s only active Slack connection; an agent with more than one must pass options.channelIdentifier, the connection identifier T…-A…, or the send answers 400 with a list of its connections. The bot must be in the channel. Someone invites it with /invite @your-bot. Posting in a public channel the bot was never invited to needs the chat:write.public permission: apps set up from the dashboard’s manifest have it, and older apps get it by applying the updated manifest and reinstalling. A private channel always needs the invite. What arrives. The text renders exactly like the agent’s replies: Slack Markdown, ::: components (a ::: documents block included, see Files), and a long text split into consecutive messages. A text that is only a documents block posts a 📎 line naming the files first, and the files under it. One send makes one delivery record, and messageId is the first message’s ts. If a later part of a long text fails, the send still returns delivered: true, with a warning. What the agent remembers.
  • A post into a thread or group DM where someone has already mentioned the bot is recorded in that conversation, so the next mention knows what the agent posted. The result has persisted: true.
  • Where nobody has mentioned the bot yet, the result has persisted: false and a warning. When someone later mentions the bot in that thread, the history read at the first mention brings in the bot’s earlier posts.
  • A DM is recorded under the person’s Lua user, in the conversation of the post’s own thread: a reply in that thread continues with the context, and a new top-level message starts a fresh conversation, as with any direct message.
  • Posts your code makes by calling Slack’s API directly are not recorded. Use Channels.send when the agent should remember them.
src/skills/tools/EscalateToLeadsTool.ts
A job that posts into a thread stored during an earlier turn is in Send proactive messages. When Slack refuses. On the REST send, an X-Idempotency-Key makes a retry safe when a call got no answer: a replayed key returns the first send’s outcome instead of posting again. Elsewhere. user.send() never picks Slack. A workflow run started with replyTo: { channel: 'slack', threadId } posts its result through the same bot (Workflows REST). With the outboundChannels feature on, the model gets sendSlackMessage.

Limits

  • Permissions: twelve required, eight optional, and chat:write.public, listed with what each is for on Slack permissions.
  • One private connection per workspace and app; one public connection per App ID per agent.
  • images renders up to 10 images.
  • documents uploads up to 10 files per answer, each up to 25 MB and stored on Lua’s CDN; the rest arrive as links.
  • Channels.send posts about one message per second per channel, Slack’s own limit for the bot.
  • Link commands (link-me-to:, remove-my-link) and the links they create apply to direct messages only, not to channels or group DMs.

Troubleshooting

Right after the app is created, Slack’s first check of the events address can fail. Open the app’s Event Subscriptions page and select Retry next to Request URL; it shows Verified.
The bot token was issued before the manifest was applied. Apply the manifest, reinstall the app to the workspace, and link again with the new xoxb- token.
The app’s credentials are saved but it was never installed through Lua. Select Finish setup on the card, or get a new link in the CLI (List channels → the connection → Get an install link).
The saved secret isn’t the one Slack signs with, often because the Client Secret was pasted. Copy Basic Information → App Credentials → Signing Secret again and save it on the card. The bot keeps answering meanwhile.
Lua rejected an event that didn’t match the confirmed signing secret. If you regenerated the secret in Slack, paste the new one; until then Lua rejects every event from the app. If you didn’t, it was most likely a forged request: Lua rejected it, and the message clears with the next real event.
Disconnecting a Slack channel in Lua revokes its bot token, and a revoked token can’t be connected again. Reinstall the app in Slack (Install App → Reinstall to Workspace) to get a new one.
That is the default dms_and_mentions mode. Mention the app, or set responseMode to auto_respond and add the channel’s ID to autoRespondChannels.
Each direct-message thread is its own conversation, and a new top-level message starts a fresh one. To continue, reply in the thread under the agent’s earlier answer. In a channel, mention the agent in the thread where the discussion happened; another thread is a separate conversation.
The agent’s bot is not a member of that channel. Run /invite @your-bot in the channel and send again.
That is expected. In a Slack Connect channel, people from other workspaces are answered like anyone else in the channel. Personal memory is off there, so nothing anyone told the agent in a direct message is brought in. To keep the agent out of a shared channel, remove the app from it.

Next steps

Slack permissions

Every permission and event the app asks for, and why.

Connect Microsoft Teams

The other team-chat channel, admin dashboard only.

Response formatting

What each component becomes in Slack.

lua channels

Every menu item and what it prints.

About channels

Channels and integrations, and why Slack is on both lists.