• Feature Name: Webhooks
  • Start Date: 10/03/2023
  • RFC PR: https://github.com/revoltchat/rfcs/issues/0001
  • Tracking Issue: https://github.com/revoltchat/revolt/issues/0000
  • Status: accepted

Summary

This RFC adds the ability to be able to send messages from external applications and services through webhooks, this allows intergration with other services.

Motivation

Webhooks make it easy to intergrate with other services, this allows other services to take advantage of Revolt's to send infomation through revolt. A common example is GitHub Webhooks which allow you to send messages when a repository changes.

Guide-level explanation

This RFC adds the ability to create, edit, delete webhooks and send messages with the webhook, webhooks have a name, id, channel id, avatar and token. The token and id are used to send messages with the webhook.

Fetching the webhook can be done with or without authorization, if you fetch with authorization the token will be returned, as the token is secret it should not be given to people without permissions.

There will be a built-in way to use GitHub webhooks from revolt as this is a very commonly used type of webhook, no other types of webhooks have been considered yet.

Creating a webhook will give it a default name of "Webhook" and default to the first channel in the channel list, users can then edit the name, channel and avatar.

Reference-level explanation

The database will have an additional document to store the webhooks

#![allow(unused)]
fn main() {
struct Webhoook {
  // Unique Id
  id: String,

  // The name of the webhook - 1 to 32 chars
  name: String,

  // The avatar of the webhook, if it has one
  avatar: Option<File>,

  // The ID of the channel which this webhook belongs to
  channel: String,

  // The permissions of the webhook
  permissions: u64,

  // The token of the webhook
  token: String
}
}

Addition routes must be added to the API, this includes:

Channel Routes

POST /channel/<target>/webhooks - Create webhook in the channel
GET /channel/<target>/webhooks - Gets all webhooks in the channel

Webhook Routes

GET /webhooks/<target>/<token> - Gets the webhook with a token, does not require permissions
GET /webhooks/<target> - Gets the wehook, requires permissions
PATCH /webhooks/<target>/<token> - Edits the webhook with a token, does not require permissions
PATCH /webhooks/<target> - Edits the webhooks, requires permissions
DELETE /webhooks/<target>/<token> - Deletes the webhook with a token, does not require permissions
DELETE /webhooks/<target> - Deletes the wehook, requires permissions
POST /webhooks/<target>/<token> - Sends a message with the webhook
POST /webhooks/<target>/<token>/github - GitHub compatible route for sending a message with the webhook

The PATCH routes take a json body with the data for editing the webhook:

#![allow(unused)]
fn main() {
struct WebhookEditBody {
  // The new name for the webhook
  name: Option<String>,

  // The new avatar for the webhook - Autumn ID
  avatar: Option<String>,

  // The new permissions

  permissions: Option<u64>,

  // The fields to remove
  remove: Option<Vec<RemoveFields>>
}

pub enum RemoveFields {
  Avatar
}
}

The POST routes take the same json body as the message send route. The data the GitHub compatible route takes can be seen on the GitHub docs

The message structure must be changed to accommodate these changes, this requires a breaking change of making Message.author either the user ID or the webhook ID, depending on what sent the message, when a webhook sends a message a Message.webhook field will contain the webhook's information.

- author: String  // User ID
+ author: String  // Either user ID or webhook ID
+ webhook: Option<MessageWebhook>

the webhook data inside a message will contain a stripped down version of the Webhook struct to avoid sending unessessary data:

#![allow(unused)]
fn main() {
struct MessageWebhook {
  // The name of the webhook - 1 to 32 chars
  name: String,
  // The id of the avatar of the webhook, if it has one
  avatar: Option<String>
}
}

This will require an update to existing programs which use the API to ensure they do not break with this change.

Information about the webhook which sent the message is included inside the message, you are also able to query GET /webhooks/<target> route which returns the same infomation.

There will be three new events to go along side this, these events do not contain the token as these events are sent to all people who have access to the channel and not just people with permissions

#![allow(unused)]
fn main() {
enum Events {
  WebhookCreate(Webhook),  // Contains all the info about the webhook which was created

  WebhookUpdate {
    id: String,  // The ID of the updated webhook
    data: PartialWebhook,  // Contains the data which was updated
    remove: Vec<RemoveFields>  // A vec of the fields which where cleared
  },

  WebhookDelete {
    id: String  // The ID of the webhook which was deleted
  },

  // existing events
  ...
}
}

Permissions

Webhooks are affected by permissions, to for example stop them from uploading files, webhooks will store the full u64 set of permissions to keep consistancy but only a few permissions will affect the webhook, because of this the client will only show a select few permissions, this includes:

  • Send Messages
  • Send Embeds
  • Masquerade
  • React - affects interaction reactions

When the webhook is created it defaults to having all of these permissions enabled.

Prior art

Discord and Slack both have webhooks as well both with similar implementation, both work well and have wide adoption

Unresolved questions

Currently this RFC includes a breaking change, if possible this would like to be avoided however no solution has been found yet.

Security concerns

No security concerns have been found yet, however this does allow third-parties to send messages to revolt, this could be used maliciously however the affected scope is small as it can only send messages.

Future ideas

Currently there is only a Github compatible route for pre-existing formats, in the future this could be expanded to support more formats such as slack for example.