matrix-webhook-gateway
lets you create and manage webhooks for Matrix channels. The application is configured with a simple and clearly documented configuration file, and webhooks can be added and removed directly in Matrix. A powerful plugin system is available (including several examples), allowing you to easily add handlers for various types of webhooks.
The recommended way to run the application service is using Docker. All releases are published as Docker images on https://hub.docker.com/r/geluk/matrix-webhook-gateway.
To get started quickly, an example compose file is provided below:
version: "3.6"
services:
matrix-webhooks:
image: geluk/matrix-webhook-gateway:latest
# Default settings shown here for reference:
# environment:
# WEBHOOK_CONFIG: /config/gateway-config.yaml
# WEBHOOK_APPSERVICE_CONFIG: /data/appservice-webhook-gateway.yaml
volumes:
# Dynamic data, the application will write to this directory
- ./data:/data
# Static configuration, can be mounted read-only
- ./config:/config:ro
ports:
# This port is used to listen for incoming webhooks.
- 8020:8020
# This port is used for communication with your homeserver.
- 8023:8023
restart: unless-stopped
# Defaults shown below, these can be uncommented and edited if required:
#user: 953:953
It is also possible to run the application directly. Download the
latest release
and extract it. Start the application by running node entry.js
Commandline options can be used to alter the default configuration file
location. For a list of available options, try node entry.js --help
.
A new configuration file (/config/gateway-config.yaml
in Docker, or
./gateway-config.yaml
outside Docker) is generated on first startup. The
webhook gateway will use the settings in this file to automatically generate an
appservice registration file (default location:
/data/appservice-webhook-gateway.yaml
in Docker, ./appservice.yaml
outside).
You should copy this file to your Matrix server, and add it to your Synapse
configuration:
app_service_config_files:
- './appservices/appservice-webhook-gateway.yaml'
If your webhook and Matrix server live on the same host, you can also choose to directly point Matrix to the generated appservice file.
Invite the bot to a new channel:
/invite @_webhook:yourmatrixserver.tld
To create a webhook in the channel, enter the following command:
-hook create <webhook_name>
The bot will create a new user for this webhook, invite it, and send you the webhook URL in a private message.
To list all enabled webhooks in a channel, use:
-hook list
To delete a webhook, use:
-hook delete <id>
Where <id>
is the numeric ID of a webhook as returned by -hook list
.
A webhook user will automatically update its username and avatar if the relevant fields in the webhook are set. Alternatively, you can use commands to set the profile details of a user.
-hook set name <id> <username>
or:
-hook set avatar <id> <url>
To call the webhook, send a POST
request with a JSON body to the webhook URL.
Several formats are supported, such as Slack:
{
"text": "I am a webhook message",
"username": "Webhook",
"icon_url": "https://example.com/hook.png"
}
Discord:
{
"content": "I am a webhook message",
"username": "Webhook",
"icon_url": "https://example.com/hook.png"
}
Apprise:
{
"version": "1.0",
"title": "A new message",
"message": "I am a webhook message"
}
As well as the format exposed by turt2live/matrix-appservice-webhooks:
{
"text": "Hello world!",
"format": "plain",
"displayName": "My Cool Webhook",
"avatarUrl": "http://i.imgur.com/IDOBtEJ.png"
}
Other formats can be supported by writing a custom plugin, see below.
When a new webhook is created, the gateway will generate an ID for the webhook
user using the pattern specified in app_service.user_pattern
in the
configuration file.
It is possible to create multiple separate webhooks with the same name in the same channel. Those webhooks will all be handled by the same user.
If you remove the {room}
variable from the user_pattern
, webhooks with the
same name will map to the same user everywhere, which may lead to a single
webhook user being active in multiple rooms at once. Despite this, webhooks
will still only be posted to the room in which they were created.
To support arbitrary JSON POSTs, you can write a custom plugin to interpret the message body and format it into something the webhook gateway understands.
A plugin is a Typescript file dropped into /data/plugins
(or ./plugins
if
you don't use the Docker release), which needs to conform to a specific format.
The format of a plugin file is best explained by looking at an example, which you can find here.
this plugin may also be interesting to look at. It generates a message from an alert notification as sent by Prometheus Alertmanager.
Several plugins are available in the plugin/develop directory. Note that these plugins are intended as a starting point and are usually based on the default configuration for the application they support.
To execute a plugin, you must append its name (as specified by the format
key
in your plugin definition) to the webhook URL. For instance, to execute the
sample plugin, send a POST to
https://your-webhook-gateway-url/hook/your-webhook/sample
If you're just looking to write a simple plugin, the easiest way to get started is by copying the sample plugin, adapting it to your needs, and installing it in the right directory.
If you're writing a more complicated plugin and would like to have access to
code analysis, you can clone the repository and write your plugin in the
./plugins/develop
directory. This way, features like type hints and automatic
imports will work correctly.
To simplify the process of generating messages with both HTML and plaintext content, a formatting API is available. This lets you build messages that will be rendered both in plaintext and as HTML.
For example, creating an ordered list:
ol(a('https://example.com', 'first'), 'second', 'third');
This generates the following plaintext:
1. first (https://example.com)
2. second
3. third
While generating the following HTML content:
- first
- second
- third
Various convenience functions are available. Take a look at the sample plugins to see how the formatting API can be used, and see here for a complete list of all available formatting functions.
The loading process of plugins is a bit involved, and not necessarily optimal. In other words, I'm open to suggestions! If you know of a way to streamline it, please do create an issue or a pull request.
Here's how it currently works:
On startup, the directory specified in the configuration file
(webhooks.plugin_directory
) is scanned recursively, and the plugins in it are
loaded one by one. The loading process of a plugin looks like this:
- The contents of the plugin file are hashed.
webhooks.plugin_cache_directory
is checked for a file with a matching hash. If a file is found, the cached plugin is loaded immediately (see step 5).- If no file is found, the plugin is copied to
./plugins/__workdir
and compiled. This path needs to be within the working directory of the application, otherwise type resolutions during compilation will fail. - The compiled plugin is written to
webhooks.plugin_cache_directory
. - The cached plugin is copied to
./plugins/__workdir
to ensure any imports are resolved correctly during the loading process. - The plugin is
require()
d and some basic integrity checks are performed against the resulting object to validate that it is correctly formed. - The plugin class is instantiated, the
init()
function is executed, and the plugin is added to the list of active plugins.
Run start-matrix.sh
(start-matrix.ps
on Windows) in ./local-dev
to set
up a local appservice development environment with Docker. This will start up
a Synapse and an Element instance, which you can use for testing the appservice.
cd local-dev
./start-matrix.sh
Go to http://localhost:8009 and log in using the following credentials:
Username: dev
Password: appservice-dev
Start the application service:
npm ci
npm run start
Invite the webhook management user using the following Matrix command:
/invite @webhook:matrix.local
- If the appservice shows an
ECONNREFUSED
error, it cannot connect to the Synapse server. Make sure Synapse is accessible at http://127.0.0.1:8008 or modify the configuration file to point it to the right URL. - If the appservice starts successfully, but doesn't respond to invitations,
Synapse can't reach the appservice. Make sure the appservice can be reached
from the Synapse container. You can test this by executing the following command
within the
local-dev
directory:docker-compose exec synapse curl 172.31.0.1:8023/health
On Windows, if you're using Docker with the WSL backend, you may need to update the IP address inlocal-dev/synapse-config/appservices/webhook-dev.yml
fromhttp://172.31.0.1:8023
(the default gateway of the Docker network) to your computer's IP address.