Discord Gateway Events Webhooks


discordhooks is a tiny open source server that is super simple. So simple that you might ask me why I would even share something like this and write about it. In fact, I think this simplicity is one of the best things about this entire project. It’s so simple that anyone with even a little bit of programming knowledge could dive in and turn it into something useful for them.

I’ve been an active part of the Val Town community for quite a while now. I’ve even done some work with them on their docs. It’s an amazing platform that takes snippets of typescript and turns them, instantly, into deployed functions. They can even have their own endpoint or cron schedule. Hacking on Val Town makes coding feel brand new again.

But this post isn’t about how good Val Town is (even though I should probably write that up). Instead, it’s about how glueing together small, simple services can make otherwise complicated problems pretty simple. Incidentally, that’s what tools like Val Town are all about; enabling people to grow their own little online garden that is just the way they want it.

Val Town or not, this should be helpful for anyone who wants to run some simple code in response to events on Discord.

Discord

Sometimes you use a library, app or framework and it just clicks. All the concepts fit together well, it’s designed with its users in mind and when you want to do something with it all you have to do is imagine how you might do that thing and exactly what you’re looking for already exists in the documentation. You might have had that feeling from Express, Rails, Phoenix, Stripe or many others depending on your language and type of environment.

The Discord API is the opposite of that. Almost indecipherable for beginners and barely any better for more experienced people, there are so many layers of complication in there that it’s a marvel that anyone uses it at all. Alas, Discord is used so widely that I’ve run into it a fair amount recently, especially in my time writing docs for Val Town. Users turn to Val Town for making small incremental gains where they already are; one of those places is Discord.

XKCD comic: complex documentation creates problems
It's not far off having a "how to read this" section.

One of the key things that people want to do with their Discord bots is react to things happening in their server. Things like “when a new user joins, send them a message” or “when there’s a message in channel X, turn that message into a thread”. To do this in Discord, you have to engage with Discord Gateway Events. This gateway uses the websocket protocol and persistent connections to deliver events quickly and in near realtime. Websockets, especially those with persistent connections, do not play nicely with serverless functions that are designed to live, run and die all in response to a single request.

Why persistent websockets are hard

These persistent websocket connections bring with them a great deal of complexity. The first hurdle for people new to the web is understanding websockets; a relatively rare topic when compared to simple HTTP requests and responses. The differences in the mode of communication has some pretty large consequences that aren’t obvious unless you have a fair amount of experience with web technology. If you’ve ever tried to learn a new language, you might find some similarities there; it’s a bit like the jump from translating phrases in an app or a book to having a conversation with a real human being. There are things that you don’t even know you need to know and if you’re not careful, one daunting experience can end your language learning for good.

One of the most difficult parts for newcomers is the knowledge about how infrastructure supports the lifecycle of the service itself. What happens when my server dies, for example. In serverless platforms, all of this is taken care of already to eliminate this exact issue. They might not understand how a server works - and why should they if they simply want to run a little snippet of code in response to an event anyway? With the stateful, persistent connections required for the Discord Gateway users are forced to understand how to manage the persistent part with infrastructure; namely, servers.

How discordhooks works

discordhooks allows you to skip all of the hard parts by simply installing a bot on your server. The bot runs on my server, listens for events and sends them to a regular HTTP endpoint of your choosing. That’s all it does. Simple, right? So simple, you don’t even need to understand how it works, you just treat it as if you’re getting familiar webhooks from any other service.

Want to understand how it works? Here’s the entire code:

import { WebSocketManager, WebSocketShardEvents, CompressionMethod } from '@discordjs/ws';
import { REST } from '@discordjs/rest';

const ALL_INTENTS = 3276541;

// 1. Set up the persistent websocket connection manager
const rest = new REST().setToken(process.env.DISCORD_TOKEN);
const manager = new WebSocketManager({
  token: process.env.DISCORD_TOKEN,
  intents: process.env.INTENTS || ALL_INTENTS,
  rest,
});

// 2. On each event, forward that event to an endpoint
manager.on(WebSocketShardEvents.Dispatch, async (event) => {
  const t = event?.data?.t;
  const s = event?.data?.s;
  console.info(`${s}: ${t} event dispatching...`);
  await fetch(process.env.ENDPOINT, {
     method: "POST",
     body: JSON.stringify(event),
  })
  console.info(`${s}: ${t} event dispatched.`);
});

// 3. Start the service
await manager.connect();

To give credit to Discord, because their REST API is so comprehensive, it’s possible to perform pretty much any action that could be performed as a websocket response through the REST API instead, making it unecessary to actually do anything with this persistent connection but listen. This is the special constraint that makes this simplicity possible.

Once the events hit the endpoint specified, I can send them on their merry way to each users desired endpoint using a small, simple handler. For now, I handle this with a really simple val in Val Town.

You can absolutely host discordhooks yourself. The repository even has a configuration file for fly.io so it’s possible in a few short steps. However, the point of this post is to make this simple for people who don’t want to or don’t know how to host it themselves. The beauty of these little services/functions/endpoints is all in the simplicity.

I’m running the service for free at the moment to understand how useful this is and get people going with their bots. If you’re a Val Town user and would like to use this, send me an email at [email protected] or DM me on the Val Town Discord.