Open Source Discord Interactions SDK for Elixir: Build Bots with Outbound Webhooks
David Hrdlicka
4 weeks ago 17.6.2025

Discord is a communication platform designed for creating communities, allowing users to chat via text, voice, or video. It’s especially popular among gamers and online creators but has expanded into education, business, and general social use.
Here at profiq, we are also using Discord with some of our clients. Due to the fact that Discord is free and it provides a lot of features similar to Slack or Microsoft Teams, it’s an ideal tool in a Startup environment where every buck counts. With modern projects, the chat platform becomes the heartbeat of the company, sometimes even more important than email itself. That’s why it’s crucial to be able to extend these chat platforms with bots, applications and other custom integrations.
How to extend Discord?
The Discord ecosystem supports multiple ways to extend its functionality and to integrate your product into the app.
- Inbound webhooks are an easy way to send Discord messages automatically by sending HTTP requests to a Discord provided URL. This is a great way to implement Discord notifications in response to events happening in your app but as a one-way channel it does not support any form of interactivity.
- WebSocket client is the most flexible way to make a Discord bot. The client can listen to incoming Discord messages or other events and reply to them, allowing full two-way communication. However, it requires the client to maintain the WebSocket connection the whole time.
- Outbound webhooks are the newest way to write a Discord app. They are the middle ground between regular inbound webhooks and a full WebSocket client. Upon triggering an “interaction” (user executing a command or interacting with the components in an already-sent response to a command), Discord sends a HTTP request to the application-provided URL. It does not support the full range of functionality as the WebSocket client but still is a very attractive solution for existing apps that want to add integration with the platform.
What did we do?
When looking at Elixir libraries for Discord, we noticed that all of them target the WebSocket client. Therefore, we decided to make a library for the new outbound interaction webhooks.
Why Elixir? Because we have experience with the language since 2016, have used it on projects for customers like Bill.com and PDQ, and because developers love it! In fact, parts of Discord’s backend are also written in Elixir.
Introduction to the SDK
The SDK package can be added to your project by adding the following line to the deps section of mix.exs:
def deps do [ {:discord_interactions, "~> 0.1.0"} ] end
Now we can start writing our own Discord command handler. Let’s start with a simple example, a /hello command which merely greets the user who sent it. Create a new module in your application and add the following:
defmodule YourApp.Discord do use DiscordInteractions interactions do application_command "hello" do description("A friendly greeting command") handler(&hello/1) end end end
The above is our command definition. Now we need to implement the command handler itself:
def hello(itx) do # Access user information from the interaction user = get_in(itx, ["member", "user", "username"]) response = InteractionResponse.channel_message_with_source() |> InteractionResponse.content("Hello, " <> user) {:ok, response} end
Now that we defined our command and implemented it, we can use the SDK to do the rest. There are two main parts to our SDK, command registration and interaction handling itself. Let’s register our commands with Discord on application startup by adding a task to the supervisor tree:
def start(_type, _args) do children = [ # Other children... YourAppWeb.Endpoint, {DiscordInteractions.CommandRegistration, YourApp.Discord} # added this line ] opts = [strategy: :one_for_one, name: YourApp.Supervisor] Supervisor.start_link(children, opts) end
We now need to add an endpoint which will handle the incoming interactions. Add the following line to your application’s Phoenix router:
forward "/discord", DiscordInteractions.Plug, YourApp.Discord
The plug also requires access to both the parsed request JSON and the raw body. The Phoenix application template helpfully already configures a Plug.Parsers plug for JSON, we just need to add our body reader implementation to cache the raw request body. Update the plug definition in the endpoint module as follows:
plug Plug.Parsers, parsers: [:urlencoded, :multipart, :json], pass: ["*/*"], json_decoder: Phoenix.json_library(), body_reader: {DiscordInteractions.CacheBodyReader, :read_body, []} # added this line
The only thing left is configuring the Discord secrets and variables. There are three values that you need to retrieve from the Discord developer portal: the public key, application ID and bot token.
Let’s configure the application to get these values from environment variables by adding the following to the runtime config:
config :discord_interactions, public_key: System.get_env("DISCORD_PUBLIC_KEY"), bot_token: System.get_env("DISCORD_BOT_TOKEN"), application_id: System.get_env("DISCORD_APPLICATION_ID")
This finishes the setup in our application. Let’s deploy it and add it to our Discord server! (You might need to restart your client in order for our new command to appear in the command auto-complete suggestions.)
After executing the command, you should see something like this in the chat window:
Conclusion
One of the things we learned during this project is the great flexibility and versatility that Discord offers to bot authors. Creating a Discord application can be as simple as exposing a simple REST endpoint, and when combined with services like Cloud Run, your changes can go out in literal seconds.
It is also impressive how the Elixir ecosystem offloads work from library developers by making it so simple to publish packages to Hex.pm, which aside from hosting your packages also automatically generates documentation from your source code.
You can find the full library with example code on our GitHub, be sure to also check HexDocs for full API documentation. The library is of course completely open source under MIT license, so feel free to contribute or open new feature requests.