A lightweight Slack bot toolkit for Elixir. One event-handling contract, two interchangeable transports:

  • Socket Mode (Slink.SocketMode) — dials out over a WebSocket, no public endpoint needed. Great for development and internal/behind-firewall apps.
  • Events API (Slink.EventsApi.Plug) — a Plug that receives Slack's HTTP event callbacks. Slack's recommended transport for production and distribution.

Write your bot once; pick the transport per environment. Built on Mint.WebSocket and Req, with JSON handled by Elixir's built-in JSON module — no hand-rolled WebSocket protocol, no Jason dependency of our own.

Why not slack, slack_elixir, or raw WebSockex?

There is no official Slack SDK for Elixir (Bolt covers JS/Python/Java), and the existing options each leave a gap:

  • slack (Elixir-Slack) is built on Slack's RTM API, which Slack has deprecated — it no longer works for new apps or bots.
  • slack_elixir is a solid, modern client but Socket Mode only — there's no HTTP Events API transport, which Slack requires for Marketplace/distributed apps and recommends for production. Slink gives you both behind one handler contract.
  • Rolling your own on WebSockex/Mint means reimplementing envelopes, ACKs, reconnection, and signature verification yourself. You don't need to — Slink is the thin Slack-specific layer on top of the maintained Mint.WebSocket: a few hundred lines you own, not a few thousand.

Installation

def deps do
  [
    {:slink, "~> 0.1"}
  ]
end

Requires Elixir ~> 1.19 (built-in JSON module, refined compiler type warnings).

Define a bot

defmodule MyBot do
  use Slink
  alias Slink.Event

  @impl true
  def handle_event(%Slink.Event{type: :app_mention} = event, context) do
    # Reply to what triggered us — threaded if it was in a thread, else inline.
    reply(context, "hi <@#{Event.user(event)}> 👋")
  end

  def handle_event(_event, _context), do: :ok
end

Handlers are stateless. Known Slack event types arrive as atoms (:app_mention, :message, …); unknown ones stay strings. To respond, call reply(context, text, opts) (imported by use Slink) — the channel and thread come from the event carried in the context, opts[:to] picks placement (:auto, :thread, :channel), and extra keys like blocks: ride along for rich replies. reply/3 returns :ok, so a clause can end with it. You can also just return {:reply, text} (or {:reply, text, opts}) and slink sends it. Replies route through a per-channel rate limiter (Slack's ~1 msg/sec/channel). For other Web API calls use Slink.API directly.

Both transports acknowledge the event to Slack before your handler runs and dispatch it off-process, so a slow handler never blows Slack's 3-second ACK window.

Quickstart — connect to Slack in 5 minutes

Socket Mode needs no public URL, no ngrok, no webhooks — the bot dials out to Slack. This is the fastest way to see it work.

1. Create the Slack app

  1. Go to https://api.slack.com/appsCreate New AppFrom a manifest.
  2. Pick your workspace.
  3. Paste the contents of manifest.json (shipped in this repo) → NextCreate.

That's all your scopes, event subscriptions, and Socket Mode configured in one shot.

Name your bot. The name is entirely yours — slink (the library) never hardcodes it, it runs purely off tokens. Before creating, edit these fields in manifest.json (and use your name in place of @slink below):

FieldControls
display_information.namethe app's name in Slack's directory
features.bot_user.display_namethe bot's @handle (what members @mention)
features.slash_commands[].commandthe slash command, e.g. /yourbot

2. Grab two tokens

  1. Bot token — left sidebar → OAuth & PermissionsInstall to WorkspaceAllow → copy the Bot User OAuth Token (starts with xoxb-).
  2. App token — left sidebar → Basic Information → scroll to App-Level TokensGenerate Token and Scopes → add the connections:write scope → Generate → copy the token (starts with xapp-).

3. Export them

export SLACK_BOT_TOKEN=xoxb-your-bot-token
export SLACK_APP_TOKEN=xapp-your-app-token

4. Run your bot

Add it to your application's supervision tree:

children = [
  {Slink.SocketMode,
   module: MyBot,
   app_token: System.fetch_env!("SLACK_APP_TOKEN"),
   bot_token: System.fetch_env!("SLACK_BOT_TOKEN")}
]

Supervisor.start_link(children, strategy: :one_for_one)

…or try it right now in IEx, no app needed:

iex -S mix
{:ok, _} =
  Slink.SocketMode.start_link(
    module: Slink.ExampleBot,
    app_token: System.fetch_env!("SLACK_APP_TOKEN"),
    bot_token: System.fetch_env!("SLACK_BOT_TOKEN")
  )

5. Say hi

In Slack, invite the bot to a channel with /invite @slink, then mention it: @slink hello — it replies 👋. Done.

Want the bot to auto-join channels on boot? Pass join: ["C0123456789"] to Slink.SocketMode. Tune the outbound rate limit with config :slink, :rate_interval_ms, 1_000.

Going to production — Events API (HTTP)

For production Slack recommends HTTP over Socket Mode (see the table below). You'll need a public HTTPS endpoint. Run the plug with Bandit:

Bandit.start_link(
  plug: {Slink.EventsApi.Plug,
         module: MyBot,
         signing_secret: System.fetch_env!("SLACK_SIGNING_SECRET"),
         bot_token: System.fetch_env!("SLACK_BOT_TOKEN")},
  port: 4000
)

…or mount it in an existing Plug/Phoenix router:

forward "/slack/events", to: Slink.EventsApi.Plug,
  init_opts: [module: MyBot,
              signing_secret: System.fetch_env!("SLACK_SIGNING_SECRET"),
              bot_token: System.fetch_env!("SLACK_BOT_TOKEN")]

Then in the app's manifest/settings: set "socket_mode_enabled": false, add your public URL as the Request URL under Event Subscriptions (https://your-host/slack/events), and copy the Signing Secret from Basic Information into SLACK_SIGNING_SECRET. Slink answers Slack's url_verification handshake and verifies every request's signature automatically.

The same MyBot works unchanged across both transports.

Transport choice, per Slack's own guidance

Socket ModeEvents API (HTTP)
Public URL requiredNoYes
Slack recommends fordevelopment, internal, behind-firewallproduction, reliability, scale
Marketplace / distributed apps✗ not allowed✓ required
Concurrencycapped at 10 connections/appscales horizontally

Development

mix deps.get
mix test

License

MIT — see LICENSE.