<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Curtis Summers</title>
    <description>The latest articles on DEV Community by Curtis Summers (@curtissummers).</description>
    <link>https://dev.to/curtissummers</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3762363%2F886fc90d-ed24-48b7-bafa-1fd2e8e2c445.png</url>
      <title>DEV Community: Curtis Summers</title>
      <link>https://dev.to/curtissummers</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/curtissummers"/>
    <language>en</language>
    <item>
      <title>How to Build an AI Slack Bot with Claude, GPT, Grok, &amp; Gemini Using Hot Dev</title>
      <dc:creator>Curtis Summers</dc:creator>
      <pubDate>Thu, 12 Feb 2026 18:30:35 +0000</pubDate>
      <link>https://dev.to/curtissummers/how-to-build-an-ai-slack-bot-with-claude-gpt-grok-gemini-using-hot-dev-91k</link>
      <guid>https://dev.to/curtissummers/how-to-build-an-ai-slack-bot-with-claude-gpt-grok-gemini-using-hot-dev-91k</guid>
      <description>&lt;p&gt;What if you could build a Slack bot that talks to Claude, GPT, Grok, &lt;em&gt;and&lt;/em&gt; Gemini — and switch between them with a single chat command? And what if the whole thing fit in a single file?&lt;/p&gt;

&lt;p&gt;That's what we're building today. No framework. No boilerplate. Just &lt;a href="https://hot.dev" rel="noopener noreferrer"&gt;Hot Dev&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faj2ife008xyypyqdf0li.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faj2ife008xyypyqdf0li.png" alt="The bot responding in Slack with an AI reply" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full source code:&lt;/strong&gt; &lt;a href="https://github.com/hot-dev/hot-demos/tree/main/slack-bot" rel="noopener noreferrer"&gt;github.com/hot-dev/hot-demos/tree/main/slack-bot&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What You'll Build
&lt;/h2&gt;

&lt;p&gt;An AI-powered Slack bot that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Talks to four AI providers&lt;/strong&gt; — Claude, GPT, Grok, and Gemini&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Switches models live&lt;/strong&gt; — Type &lt;code&gt;!ai gpt&lt;/code&gt; in the channel and the next reply comes from GPT&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reads conversation context&lt;/strong&gt; — The bot knows what was said recently&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploys with one command&lt;/strong&gt; — &lt;code&gt;hot deploy&lt;/code&gt; to go live on Hot Dev Cloud&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll get this running locally first, then deploy it to production with real-time webhooks.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we start, you'll need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hot Dev CLI&lt;/strong&gt; — Download from &lt;a href="https://hot.dev/download" rel="noopener noreferrer"&gt;hot.dev/download&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VS Code extension&lt;/strong&gt; (optional) — Search "Hot" by &lt;code&gt;hot-dev&lt;/code&gt; in the Extensions panel for syntax highlighting, autocomplete, and error checking&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Slack workspace&lt;/strong&gt; where you can create apps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;At least one AI API key&lt;/strong&gt; — Anthropic (Claude) is the default, but OpenAI, xAI, or Google Gemini work too&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 1: Create a Slack App
&lt;/h2&gt;

&lt;p&gt;First, we need a Slack app with the right permissions.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://api.slack.com/apps" rel="noopener noreferrer"&gt;api.slack.com/apps&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create New App&lt;/strong&gt; → &lt;strong&gt;From scratch&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Give it a name (e.g., "Hot AI Bot") and select your workspace&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe89m63871urlp4magu5q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe89m63871urlp4magu5q.png" alt="Create New App dialog at api.slack.com" width="521" height="541"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, add the bot permissions. Go to &lt;strong&gt;OAuth &amp;amp; Permissions&lt;/strong&gt; and add these &lt;strong&gt;Bot Token Scopes&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scope&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;channels:history&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Read messages from public channels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;channels:read&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Get channel info&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;chat:write&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Post messages and replies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;groups:history&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Read messages from private channels &lt;em&gt;(optional)&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;im:history&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Read direct messages &lt;em&gt;(optional)&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxjtkffb8y68romn5i36t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxjtkffb8y68romn5i36t.png" alt="Bot Token Scopes in the Slack app settings" width="688" height="748"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now install the app to your workspace:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Install App&lt;/strong&gt; in the sidebar&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Install to Workspace&lt;/strong&gt; and approve&lt;/li&gt;
&lt;li&gt;Copy the &lt;strong&gt;Bot User OAuth Token&lt;/strong&gt; — it starts with &lt;code&gt;xoxb-&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Finally, invite the bot to a channel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/invite @HotAIBot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the &lt;strong&gt;Channel ID&lt;/strong&gt; — you can find it by right-clicking the channel name → "View channel details" → the ID is at the bottom (starts with &lt;code&gt;C&lt;/code&gt;).&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Set Up the Project
&lt;/h2&gt;

&lt;p&gt;Clone the demo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/hot-dev/hot-demos.git
&lt;span class="nb"&gt;cd &lt;/span&gt;hot-demos/slack-bot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The project configuration lives in &lt;code&gt;hot.hot&lt;/code&gt;. Here's the key part — the dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hot.project.slack-bot.deps {
    "hot.dev/hot-ai": "1.0.0",
    "hot.dev/slack": "1.0.4",
    "hot.dev/anthropic": "1.0.3",
    "hot.dev/openai": "1.0.4",
    "hot.dev/xai": "1.0.3",
    "hot.dev/gemini": "1.0.3"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are &lt;a href="https://hot.dev/pkg" rel="noopener noreferrer"&gt;Hot Dev packages&lt;/a&gt; — first-class integrations for Slack and all four AI providers. You don't need to install them separately. Hot Dev resolves them automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Understand the Bot Code
&lt;/h2&gt;

&lt;p&gt;The entire bot lives in one file: &lt;code&gt;hot/src/slack-bot/bot.hot&lt;/code&gt;. Let's look at the key parts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Imports
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;::slack-bot::bot ns

// Namespace aliases — short names for the packages we use
::channels  ::slack::channels
::messaging ::slack::messaging
::misc      ::slack::misc
::webhooks  ::slack::webhooks
// ... other aliases (see full code on GitHub)

// AI provider aliases
::anthropic-chat ::anthropic::messages
::openai-chat    ::openai::chat
::xai-chat       ::xai::responses
::gemini-chat    ::gemini::chat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're new to Hot: &lt;code&gt;::&lt;/code&gt; denotes a namespace path. &lt;code&gt;::channels ::slack::channels&lt;/code&gt; creates a short alias so we can write &lt;code&gt;::channels/conversations-history(...)&lt;/code&gt; instead of the full &lt;code&gt;::slack::channels/conversations-history(...)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;No &lt;code&gt;import&lt;/code&gt; keyword, no curly braces — just &lt;code&gt;alias full-namespace-path&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI Model Selection
&lt;/h3&gt;

&lt;p&gt;The bot lets users switch AI models live in the Slack channel with &lt;code&gt;!ai&lt;/code&gt; commands. Here's how that's configured:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MODEL_ALIASES {
  "claude":   {service: "Anthropic", model: "claude-sonnet-4-5"},
  "opus":     {service: "Anthropic", model: "claude-opus-4-6"},
  "gpt":      {service: "OpenAi",    model: "gpt-5.2"},
  "grok":     {service: "Xai",       model: "grok-4-1-fast"},
  "gemini":   {service: "Gemini",    model: "gemini-3-flash-preview"}
  // ... plus shorthand aliases (see full code on GitHub)
}

DEFAULT_SELECTION {service: "Anthropic", model: "claude-sonnet-4-5"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice: in Hot, assignment uses a space, not &lt;code&gt;=&lt;/code&gt;. &lt;code&gt;MODEL_ALIASES {…}&lt;/code&gt; means "bind the name &lt;code&gt;MODEL_ALIASES&lt;/code&gt; to this map." This is one of Hot's core syntax rules — no equals sign.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI Dispatch
&lt;/h3&gt;

&lt;p&gt;This is the cleanest part. Hot's &lt;code&gt;match&lt;/code&gt; flow dispatches to the right AI provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AiService enum { Anthropic, OpenAi, Xai, Gemini }

ask-ai fn match (service: AiService, model: Str, message: Str, system: Str): Str {
  AiService.Anthropic =&amp;gt; { ::anthropic-chat/chat(model, message, system) }
  AiService.OpenAi    =&amp;gt; { ::openai-chat/chat(model, message, system) }
  AiService.Xai       =&amp;gt; { ::xai-chat/chat(model, message, system) }
  AiService.Gemini    =&amp;gt; { ::gemini-chat/chat(model, message, system) }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four AI providers, four lines. Each Hot Dev AI package exposes the same &lt;code&gt;chat(model, message, system)&lt;/code&gt; interface, so switching providers is just matching on the enum.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling Messages
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;handle-message&lt;/code&gt; function does the heavy lifting. It checks for &lt;code&gt;!ai&lt;/code&gt; commands first, then falls through to the AI reply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;handle-message fn (channel: Str, message: Map, bot-user-id: Str): Map {
  text or(message.text, "")
  cmd parse-ai-command(text)

  cond {
    // !ai — show current model and available options
    eq(cmd, "help") =&amp;gt; {
      // ... format and post model list (see full code on GitHub)
    }

    // !ai &amp;lt;selection&amp;gt; — acknowledge the switch
    is-some(cmd) =&amp;gt; {
      svc get(SERVICE_INFO, cmd.service)
      ::messaging/chat-post-message(::messaging/ChatPostMessageRequest({
        channel: channel,
        text: `${svc.emoji} Switched to *${svc.name}* \`${cmd.model}\``
      }))
    }

    // Regular message — detect model from history and reply with context
    =&amp;gt; {
      sel detect-selection(channel, bot-user-id)
      service to-service(sel.service)
      context fetch-context(channel, bot-user-id)
      prompt if(is-empty(context),
        text,
        `Recent conversation:\n\n${context}\n\n---\nRespond to the latest message.`
      )

      ai-response ask-ai(service, sel.model, prompt, SYSTEM_PROMPT)
      ::messaging/chat-post-message(::messaging/ChatPostMessageRequest({
        channel: channel,
        text: ai-response
      }))
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;cond&lt;/code&gt; flow is Hot's branching construct — it evaluates conditions top-to-bottom and takes the first match. The &lt;code&gt;=&amp;gt;&lt;/code&gt; with no condition at the end is the default branch.&lt;/p&gt;

&lt;p&gt;One clever detail: &lt;code&gt;detect-selection&lt;/code&gt; scans the last 100 messages in the channel for the most recent &lt;code&gt;!ai&lt;/code&gt; command. The model selection is stored in the chat history itself — no database, no state file, no Redis. Just Slack messages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Polling for Messages
&lt;/h3&gt;

&lt;p&gt;For local development, the bot polls the channel on a schedule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;check-channel-poll
meta {
  schedule: "every 15 seconds",  // comment out scheduled polling in Hot Dev Cloud in favor of webhooks + Slack Events API
  on-event: "slack-bot:check"
}
fn (event) {
  channel get-channel-id()
  bot-user-id get-bot-user-id()
  // ... fetch and filter messages (see full code on GitHub)
  for-each(new-messages, (msg) { handle-message(channel, msg, bot-user-id) })
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;meta&lt;/code&gt; block is how Hot attaches metadata to functions. Here it says: "Run this every 15 seconds, and also run it when someone sends the &lt;code&gt;slack-bot:check&lt;/code&gt; event." You can also trigger it manually at any time with &lt;code&gt;hot eval 'send("slack-bot:check")'&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;One thing you might notice: there are no &lt;code&gt;println&lt;/code&gt; or logging statements anywhere in the code. That's because Hot Dev lets you inspect every function call, its arguments, return values, and timing — all from the app. No manual logging needed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fix674epvae13x1qoy76m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fix674epvae13x1qoy76m.png" alt="Hot Dev function call trace" width="800" height="546"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's all the code you need to understand for now. Let's run it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Run Locally
&lt;/h2&gt;

&lt;p&gt;Start the dev server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hot dev &lt;span class="nt"&gt;--open&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This starts the Hot Dev runtime locally and opens the app in your browser at &lt;code&gt;http://localhost:4680&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8wreti0pwar3gsday5bi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8wreti0pwar3gsday5bi.png" alt="Terminal showing hot dev output" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frjrm80ys75qcsfiyczzg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frjrm80ys75qcsfiyczzg.png" alt="The local Hot Dev app in the browser" width="800" height="633"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Set Context Variables
&lt;/h3&gt;

&lt;p&gt;Hot Dev uses &lt;strong&gt;context variables&lt;/strong&gt; for configuration and secrets. In the app, go to &lt;strong&gt;Context Variables&lt;/strong&gt; and set the following:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;slack.api.key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Your Bot User OAuth Token (&lt;code&gt;xoxb-...&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;slack.channel.id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The channel ID (&lt;code&gt;C...&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;anthropic.api.key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Your Anthropic API key&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you're using a different AI provider as your default, set that provider's key instead (e.g., &lt;code&gt;openai.api.key&lt;/code&gt;, &lt;code&gt;xai.api.key&lt;/code&gt;, or &lt;code&gt;gemini.api.key&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa3lrzrkhlo1uatbck2gj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa3lrzrkhlo1uatbck2gj.png" alt="Context Variables page with API keys configured" width="800" height="562"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Test It
&lt;/h3&gt;

&lt;p&gt;The bot will check the channel every 15 seconds. To trigger a check immediately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hot &lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s1"&gt;'send("slack-bot:check")'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Type a message in your Slack channel and wait for the bot to reply.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk5p8moq0aajcb3bxkgaf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk5p8moq0aajcb3bxkgaf.png" alt="The bot replying to a message in Slack" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Switch AI Models
&lt;/h3&gt;

&lt;p&gt;Type &lt;code&gt;!ai&lt;/code&gt; in the Slack channel to see the current model and all options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;!ai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bot replies with the active model and a list of available providers. To switch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;!ai gpt          → GPT-5.2
!ai grok         → Grok 4.1 Fast
!ai gemini       → Gemini 3 Flash
!ai claude       → Claude Sonnet 4.5
!ai opus         → Claude Opus 4.6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The selection sticks — the bot scans the channel history for the most recent &lt;code&gt;!ai&lt;/code&gt; command and uses that model for all replies. No config change, no restart. Just type a command.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1jm21jpqkqevmjw4tnol.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1jm21jpqkqevmjw4tnol.png" alt="The !ai command showing available models and switching to GPT" width="800" height="503"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Taking It to Production
&lt;/h2&gt;

&lt;p&gt;At this point you have a working AI Slack bot. But there's a catch: it's polling every 15 seconds. That's fast enough for testing, but not ideal for production — it makes unnecessary API calls when no one is talking, and there's still a small delay.&lt;/p&gt;

&lt;p&gt;The fix is &lt;strong&gt;webhooks&lt;/strong&gt;. Instead of polling, Slack sends your bot a message the instant someone types in the channel. The response is immediate. The reason we can't use webhooks during local development is simple: Slack's Events API needs a public URL to send events to, and it can't reach &lt;code&gt;localhost&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To use webhooks, you need to deploy your bot to &lt;a href="https://hot.dev" rel="noopener noreferrer"&gt;Hot Dev Cloud&lt;/a&gt;. Hot Dev gives your bot a public URL and handles scaling — all with a single command.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sign Up and Get an API Key
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Create an account at &lt;a href="https://app.hot.dev" rel="noopener noreferrer"&gt;app.hot.dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;API Keys&lt;/strong&gt; and create a new key&lt;/li&gt;
&lt;li&gt;Set it in your terminal or in a &lt;code&gt;.env&lt;/code&gt; file in your project root:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;HOT_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-api-key-here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu8yvobiji6o2i9v82gki.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu8yvobiji6o2i9v82gki.png" alt="The API Keys page in the Hot Dev App" width="797" height="824"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Set Context Variables
&lt;/h3&gt;

&lt;p&gt;Same as local — go to &lt;a href="https://app.hot.dev/contexts" rel="noopener noreferrer"&gt;Context Variables&lt;/a&gt; in the Hot Dev App and set the same keys:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;slack.api.key&lt;/code&gt; — your bot token&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;slack.channel.id&lt;/code&gt; — the channel ID&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;slack.signing.secret&lt;/code&gt; — your Signing Secret (find it under &lt;strong&gt;Basic Information&lt;/strong&gt; in your Slack app — needed for webhook verification)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;anthropic.api.key&lt;/code&gt; — your AI provider key(s)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foiy5wtvpk2tripw41hfe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foiy5wtvpk2tripw41hfe.png" alt="Context Variables in the Hot Dev App" width="800" height="707"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Comment Out Polling
&lt;/h3&gt;

&lt;p&gt;Since webhooks handle messages in real time, you don't need the polling schedule in production. Comment it out in &lt;code&gt;bot.hot&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;check-channel-poll
meta {
  // schedule: "every 15 seconds",  // comment out scheduled polling in Hot Dev Cloud in favor of webhooks + Slack Events API
  on-event: "slack-bot:check"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function still exists — you can trigger it manually with &lt;code&gt;hot eval&lt;/code&gt; if you ever need to — but it won't run on a schedule.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hot deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ hot deploy
No build ID provided, creating new bundle build from current source...
Discovering namespaces in: hot/src
Found namespace ::slack-bot::bot with 11 functions, 1 types
Discovered 1 namespaces
  ### package doc gen lines omitted ###
  Inserted 5 event handler(s)
  Inserted 1 webhook(s)
✓ Created bundle build 019c51a7-a5b9-7450-8025-43f5787d8bc1
  Size: 229013 bytes
✓ Successfully uploaded build 019c51a7-a5b9-7450-8025-43f5787d8bc1
✓ Successfully deployed build 019c51a7-a5b9-7450-8025-43f5787d8bc1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Your bot is live.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure Slack Webhooks
&lt;/h3&gt;

&lt;p&gt;Now tell Slack to send events to your bot in real time:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to your Slack app → &lt;strong&gt;Event Subscriptions&lt;/strong&gt; → toggle &lt;strong&gt;Enable Events&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Set the &lt;strong&gt;Request URL&lt;/strong&gt; to your webhook endpoint — find the webhook URL at &lt;a href="https://app.hot.dev/webhooks" rel="noopener noreferrer"&gt;Hot Dev App Webhooks&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8gfx6v7t5rhvnfv3uqzj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8gfx6v7t5rhvnfv3uqzj.png" alt="The Webhooks page in the Hot Dev App" width="800" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Subscribe to &lt;strong&gt;Bot Events&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;message.channels&lt;/code&gt; — messages in public channels&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;message.groups&lt;/code&gt; — messages in private channels &lt;em&gt;(optional — requires &lt;code&gt;groups:history&lt;/code&gt; scope)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;message.im&lt;/code&gt; — direct messages to the bot &lt;em&gt;(optional — requires &lt;code&gt;im:history&lt;/code&gt; scope)&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Save Changes&lt;/strong&gt; — Slack will prompt you to reinstall the app to pick up the new event permissions.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Subscribing to an event locks its required scope — you won't be able to remove the scope until you remove the event subscription first.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuwjto8amg1gfpl9ybzcx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuwjto8amg1gfpl9ybzcx.png" alt="Slack Event Subscriptions page with webhook URL and bot events" width="800" height="807"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How the Webhook Handler Works
&lt;/h3&gt;

&lt;p&gt;The bot already has the webhook code — it just wasn't doing anything during local development. Here's what kicks in when deployed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;on-slack-event
meta {
  webhook: {
    service: "slack-bot",
    path: "/events",
    method: "POST",
    description: "Receive Slack Events API callbacks"
  }
}
fn (request: HttpRequest): HttpResponse {
  cond {
    // Slack URL verification challenge (one-time setup)
    eq(request.body.type, "url_verification") =&amp;gt; {
      HttpResponse({status: 200, headers: {"content-type": "application/json"},
        body: {challenge: request.body.challenge}})
    }

    // Verify the request signature
    not(::webhooks/verify-request(request)) =&amp;gt; {
      HttpResponse({status: 401, body: {error: "invalid signature"}})
    }

    // Handle the event — top-level messages only (simplified — see full code on GitHub)
    eq(request.body.type, "event_callback") =&amp;gt; {
      event request.body.event
      cond {
        and(eq(event.type, "message"), is-null(event.subtype), is-null(event.bot_id), is-null(event.thread_ts)) =&amp;gt; {
          bot-user-id get-bot-user-id()
          handle-message(or(event.channel, get-channel-id()), event, bot-user-id)
        }
      }
      HttpResponse({status: 200, body: {ok: true}})
    }

    =&amp;gt; { HttpResponse({status: 200, body: {ok: true}}) }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;meta { webhook: {...} }&lt;/code&gt; block tells Hot Dev to register this function as an HTTP endpoint. In production, it gets a public URL automatically. The handler verifies Slack's request signature before processing — this prevents unauthorized requests.&lt;/p&gt;

&lt;p&gt;Since you commented out the polling schedule, only the webhook handler is active in production.&lt;/p&gt;

&lt;p&gt;Here's the full picture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Local Dev (polling):                    Production (webhooks):

  Schedule (every 15s)                     Slack Events API
         │                                       │
         ▼                                       ▼
  check-channel-poll()               on-slack-event(request)
         │                                       │
         ├─ fetch recent messages                ├─ verify signature
         ├─ filter bot/system msgs               ├─ filter to top-level msgs
         └─ for each message:                    └─ handle-message()
              └─ handle-message()                      │
                       │                               ├─ !ai command? → switch
                       ├─ !ai command? → switch model  └─ regular msg? → ask AI → reply
                       └─ regular msg? → ask AI → reply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both sides call the same &lt;code&gt;handle-message()&lt;/code&gt; function. The only difference is how messages arrive.&lt;/p&gt;




&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Can I use this with a private channel?&lt;/strong&gt;&lt;br&gt;
Yes. Add the &lt;code&gt;groups:history&lt;/code&gt; and &lt;code&gt;groups:read&lt;/code&gt; scopes to your Slack app and invite the bot to the private channel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I change the default AI model?&lt;/strong&gt;&lt;br&gt;
Edit the &lt;code&gt;DEFAULT_SELECTION&lt;/code&gt; line in &lt;code&gt;bot.hot&lt;/code&gt;. Set &lt;code&gt;service&lt;/code&gt; to one of &lt;code&gt;"Anthropic"&lt;/code&gt;, &lt;code&gt;"OpenAi"&lt;/code&gt;, &lt;code&gt;"Xai"&lt;/code&gt;, or &lt;code&gt;"Gemini"&lt;/code&gt; and &lt;code&gt;model&lt;/code&gt; to the model name you want.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if my AI API key is missing?&lt;/strong&gt;&lt;br&gt;
The bot will return an error for that provider. It won't crash — Hot's error handling propagates the failure as a Result. The other providers still work fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is Hot Dev free to use?&lt;/strong&gt;&lt;br&gt;
Hot Dev is free for local development. See &lt;a href="https://hot.dev/pricing" rel="noopener noreferrer"&gt;hot.dev/pricing&lt;/a&gt; for cloud deployment options.&lt;/p&gt;




&lt;h2&gt;
  
  
  Get Started
&lt;/h2&gt;

&lt;p&gt;Install Hot Dev and try it yourself: &lt;a href="https://hot.dev/download" rel="noopener noreferrer"&gt;hot.dev/download&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resources:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/hot-dev/hot-demos/tree/main/slack-bot" rel="noopener noreferrer"&gt;Full demo source code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hot.dev/docs" rel="noopener noreferrer"&gt;Hot Dev documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hot.dev/docs/language" rel="noopener noreferrer"&gt;Hot Language Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://x.com/hotdotdev" rel="noopener noreferrer"&gt;Hot Dev on X&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Built something cool with Hot Dev? Share it with us on &lt;a href="https://x.com/hotdotdev" rel="noopener noreferrer"&gt;X @hotdotdev&lt;/a&gt; — we'd love to see it.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What would you build with Hot Dev? Drop a comment — I'd love to hear what integrations you'd tackle first.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>slack</category>
      <category>ai</category>
      <category>chatbot</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
