Webhooks with Serverless Function
Tags: webhook, serverless, discord, bot
Published: 2021-03-13 12:23:40

---
description: How I set up a serverless function on Cloudflare Workers that reposts messages originally published on Habitica's chat to a Discord channel
---

*In this blog post, I'm going to show you how I set up a serverless function on Cloudflare Workers that reposts messages originally published on Habitica's chat to a Discord channel*

## Introduction

Habitica is a great platform for building good habits and boosting productivity. Its social aspect is also one of its key features. Users can form parties in order to hold each other accountable. Every party has its own simple chat page where users can discuss and where system messages are posted (skills used, damages dealt, quests started, etc.). But when it comes to creating a social space for chatting and talking, Discord is currently the best free solution out there. So my party and I moved to a Discord server. The problem is that system messages are still posted on Habitica.

That is why I looked into a way to automatically repost those messages to a dedicated Discord channel. And it turned out that it is quite easy to set up thanks to the flexible webhooks provided by both Discord and Habitica.

## Step 1: Create a Discord Bot

From [Discord's official instructions](https://support.discord.com/hc/en-us/articles/228383668):

1. Open your **Server Settings** and head into the **Integrations** tab:
2. Click the "**Create Webhook**" button to create a new webhook!
3. You can edit the avatar and the name of the webhook
4. **Choose what channel the Webhook posts to.**
5. You can copy the Webhook URL

![Discord webhook settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mr0m9liatycduce65yuc.PNG)

## Step 2: Create a Serverless Function

Habitica cannot post directly to Discord. We need an intermediate. No-code solutions like Zapier are neat, but here we need more fine-grained control over the request in order to format the messages in the way we like and serverless functions are ideals for this use case.

There are many different cloud function providers out there, but for this project, I wanted to try out [Cloudflare Workers](https://workers.cloudflare.com/). Cloudflare Workers have two serious advantages over other providers: they are incredibly cheap and their start-up time (cold start) is near-instant, not that we need those features for the current project.

Cloudflare also provides a simple yet convenient online code editor to code the serverless function. Since our code won't be complex, we'll write our function directly inside the online editor. Here's the complete code:

```js
async function handleRequest(request) {
  try {
    // See below the structure of the data object
    const data = await request.json()

    // Only repost system messages
    if(data.chat.uuid === "system") {

      // Discord expects a JSON payload that looks like this { content: 'hello world'}
      const content = { "content": data.chat.text }
      await fetch(DISCORD_WEBHOOK_URL, {
        body: JSON.stringify(content),
        method: "POST",
        headers: { "Content-Type": "application/json" },
      })
    }
    return new Response('OK')
  } catch(error) {
    console.error(error)

		// Habitica always expects a 200 response, otherwise it will disable the webhook
    return new Response('OK')
  }
}
```

The body of the post request coming from the Habitica webhook will look like this:

```json
{
  "group": {
    "id": "XXXXX-c888-4dbf-aa0e-fc317c9c8f8c",
    "name": "super_squad "
  },
  "chat": {
    "flagCount": 0,
    "flags": {},
    "_id": "03e9b0d6-XXX-442a-9659-fde24aec2842",
    "id": "03e4b0d6-XXX-442a-9659-fde24aec2841",
    "text": "mornir casts Earthquake for the party.",
    "unformattedText": "mornir casts Earthquake for the party.",
    "info": {
      "type": "spell_cast_party",
      "user": "mornir",
      "class": "wizard",
      "spell": "earth"
    },
    "timestamp": "2021-03-10T17:00:40.141Z",
    "likes": {},
    "uuid": "system",
    "groupId": "5f38dbe06649-555-aa0e-fc317c9cbf8c"
  },
  "webhookType": "groupChatReceived",
  "user": { "_id": "5454544ea-134d-4e37-XXX-310351b35729" }
}
```

`DISCORD_WEBHOOK_URL` is the Webhook URL we got in step 1, stored here as an environment variable: 

![Cloudflare environment variables](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/178ws09x1r5j7munnpbv.png)

## Step 3: Create a Habitica Webhook

The official UI for creating webhooks, found [on the settings page](https://habitica.com/user/settings/api), is a bit lacking compared to [what is available on the Habitica API](https://habitica.com/apidoc/#api-Webhook-AddWebhook). We can only add and delete URLs, but in our case, we want to listen to a specific event occurring in a specific chat room. That is why I used an alternative editor for Habitica webhooks, which includes all of the webhook options that are missing from the main site.
Website: [https://robwhitaker.com/habitica-webhook-editor/](https://robwhitaker.com/habitica-webhook-editor/)
Documentation: [https://habitica.fandom.com/wiki/Habitica_Webhook_Editor](https://habitica.fandom.com/wiki/Habitica_Webhook_Editor)

![Habitica alternative webhook editor](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ka98d75gx4ur1457v6x.PNG)

The newly created hook will then appear on our [Habitica account settings page](https://habitica.com/user/settings/api):

![Habitica webhooks settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7azqbsk0k4pnbznsqopx.PNG)

## The result

Voilà! We now have a working Habitica bot that reposts system messages!

![Discord chat preview](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4gxijzxb17kqzbe8rd9q.PNG)

Of course, there are much more things we can do! Here are some ideas:

- Link abilities and bosses to their corresponding wiki page or bosses
- Send warning if member deal too much damage to its party

Cloudflare Workers let us also [store data inside a database](https://developers.cloudflare.com/workers/learning/how-kv-works). Having information that persists between function invocations opens new exciting possibilities!