matrix-hookshot
Previously matrix-github
A bridge between Matrix and multiple project management services, such as GitHub, GitLab and JIRA.
Featureset
This bridge supports:
- Figma
- File comments
- GitHub
- Webhooks (new issues, pull requests, releases etc)
- Commands (create issues, assign issues, start workflows etc)
- GitLab
- Webhooks (new issues, merge requests etc)
- Commands
- Jira
- Webhooks (new issues, issue changes)
- Commands (create new issues)
- Generic webhooks
- Webhooks (via GET, PUT or POST with optional transformation functions)
Setup
Documentation
Documentation can be found on GitHub Pages.
You can build the documentation yourself by:
# cargo install mdbook
mdbook build
sensible-browser book/index.html
Contact
We have a bridge support room you can drop into at #hookshot:half-shot.uk, or you can reach me at @Half-Shot:half-shot.uk
Getting set up
This page explains how to set up Hookshot for use with a Matrix homeserver.
Requirements
Hookshot is fairly light on resources, and can run in as low as 100MB or so of memory. Hookshot memory requirements may increase depending on the traffic and the number of rooms bridged.
You must have administrative access to an existing homeserver in order to set up Hookshot, as Hookshot requires the homeserver to be configured with its appservice registration.
Local installation
This bridge requires at least Node 14 (though 16 is preferred), and Rust installed.
To install Node.JS, nvm is a good option.
To install Rust, rustup is the preferred solution to stay up to date.
To clone and install, run:
git clone git@github.com:matrix-org/matrix-hookshot.git
cd matrix-hookshot
yarn # or npm i
Starting the bridge (after configuring it), is a matter of running yarn start
.
Installation via Docker
To get started quickly, you can use the Docker image halfshot/matrix-hookshot
.
docker run \
--name matrix-hookshot \
-d \
-p 9993:9993 \ # Homeserver port
-p 9000:9000 \ # Webhook port
-p 9002:9002 \ # Metrics port
-v /etc/matrix-hookshot:/data \
halfshot/matrix-hookshot:latest
Where /etc/matrix-hookshot
would contain the configuration files config.yml
and registration.yml
. The passKey
file should also be stored alongside these files. In your config, you should use the path /data/passkey.pem
.
Configuration
Copy the config.sample.yml
to a new file config.yml
. The sample config is also hosted
here for your convienence.
You should read and fill this in as the bridge will not start without a complete config.
You may validate your config without starting the service by running yarn validate-config
.
For Docker you can run docker run --rm -v /absolute-path-to/config.yml:/config.yml halfshot/matrix-hookshot node Config/Config.js /config.yml
Copy registration.sample.yml
into registration.yml
and fill in:
- At a minimum, you will need to replace the
as_token
andhs_token
and change the domain part of the namespaces.
You will need to link the registration file to the homeserver. Consult your homeserver documentation on how to add appservices. Synapse documents the process here.
Homeserver Configuration
In addition to providing the registration file above, you also need to tell Hookshot how to reach the homeserver which is hosting it. For clarity, hookshot expects to be able to connect to an existing homeserver which has the Hookshot registration file configured.
bridge:
domain: example.com # The homeserver's server name.
url: http://localhost:8008 # The URL where Hookshot can reach the client-server API.
mediaUrl: https://example.com # Optional. The url where media hosted on the homeserver is reachable (this should be publically reachable from the internet)
port: 9993 # The port where hookshot will listen for appservice requests.
bindAddress: 127.0.0.1 # The address which Hookshot will bind to. Docker users should set this to `0.0.0.0`.
The port
and bindAddress
must not conflict with the other listeners in the bridge config. This listeners should not be reachable
over the internet to users, as it's intended to be used by the homeserver exclusively. This service listens on /_matrix/app/
.
Permissions
The bridge supports fine grained permission control over what services a user can access. By default, any user on the bridge's own homeserver has full permission to use it.
permissions:
- actor: example.com
services:
- service: "*"
level: admin
You must configure a set of "actors" with access to services. An actor
can be:
- A MxID (also known as a User ID) e.g.
@Half-Shot:half-shot.uk
- A homeserver domain e.g.
matrix.org
- A roomId. This will allow any member of this room to complete actions. e.g.
!TlZdPIYrhwNvXlBiEk:half-shot.uk
*
, to match all users.
Each permission set can have a services. The service
field can be:
github
gitlab
jira
feed
figma
webhooks
*
, for any service.
The level
can be:
commands
Can run commands within connected rooms, but NOT log in to the bridge.login
All the above, and can also log in to the bridge.notifications
All the above, and can also bridge their notifications.manageConnections
All the above, and can create and delete connections (either via the provisioner, setup commands, or state events).admin
All permissions. Currently, there are no admin features so this exists as a placeholder.
When permissions are checked, if a user matches any of the permission set and one of those grants the right level for a service, they are allowed access. If none of the definitions match, they are denined.
Example
A typical setup might be.
permissions:
# Allo all users to send commands to existing services
- actor: *
services:
- service: *
level: commands
# Allow any user that is part of this space to manage github connections
- actor: !TlZdPIYrhwNvXlBiEk:half-shot.uk
services:
- service: github
level: manageConnections
# Allow users on this domain to log in to jira and github.
- actor: support.example.com
services:
- service: jira
level: login
- service: github
level: commands
# Allow users on this domain to enable notifications on any service.
- actor: engineering.example.com
services:
- service: *
level: notifications
# Allow users on this domain to create connections.
- actor: management.example.com
services:
- service: *
level: manageConnections
# Allow this specific user to do any action
- actor: @alice:example.com
services:
- service: *
level: admin
Listeners configuration
You will need to configure some listeners to make the bridge functional.
listeners:
# (Optional) HTTP Listener configuration.
# Bind resource endpoints to ports and addresses.
# 'resources' may be any of webhooks, widgets, metrics, provisioning
#
- port: 9000
bindAddress: 0.0.0.0
resources:
- webhooks
- port: 9001
bindAddress: 127.0.0.1
resources:
- metrics
- provisioning
At a minimum, you should bind the webhooks
resource to a port and address. You can have multiple resources on the same
port, or one on each. Each listener MUST listen on a unique port.
You will also need to make this port accessible to the internet so services like GitHub can reach the bridge. It is recommended to factor hookshot into your load balancer configuration, but currrently this process is left as an excercise to the user.
In terms of API endpoints:
- The
webhooks
resource handles resources under/
, so it should be on its own listener. Note that OAuth requests also go through this listener. - The
metrics
resource handles resources under/metrics
. - The
provisioning
resource handles resources under/v1/...
. - The
widgets
resource handles resources under/widgetapi/v1...
. This may only be bound to one listener at present.
Services configuration
You will need to configure some services. Each service has its own documentation file inside the setup subdirectory.
Logging
The bridge supports some basic logging options. The section is optional, and by default will log at an info
level.
logging:
# Level of information to report to the logs. Can be `debug`, `info`, `warn` or `error.
level: info
# Should the logs output in human-readable format or JSON. If you are using a third-party ingestion service like logstash, use this.
json: false
# Ignored if `json` is enabled. Should the logs print the levels in color. This will print extra characters around the logs which may not be suitable for some systems.
colorize: true
# Ignored if `json` is enabled. The timestamp format to use in log lines. See https://github.com/taylorhakes/fecha#formatting-tokens for help on formatting tokens.
timestampFormat: HH:mm:ss:SSS
Sample Configuration
Below is a sample bridge configuration file. The configuration file can be tweaked to change the behaviour of your bridge. A bridge of the server is required to apply any changes made to this file.
# This is an example configuration file
bridge:
# Basic homeserver configuration
#
domain: example.com
url: http://localhost:8008
mediaUrl: http://example.com
port: 9993
bindAddress: 127.0.0.1
github:
# (Optional) Configure this to enable GitHub support
#
auth:
# Authentication for the GitHub App.
#
id: 123
privateKeyFile: github-key.pem
webhook:
# Webhook settings for the GitHub app.
#
secret: secrettoken
oauth:
# (Optional) Settings for allowing users to sign in via OAuth.
#
client_id: foo
client_secret: bar
redirect_uri: https://example.com/bridge_oauth/
defaultOptions:
# (Optional) Default options for GitHub connections.
#
showIssueRoomLink: false
hotlinkIssues:
prefix: "#"
userIdPrefix:
# (Optional) Prefix used when creating ghost users for GitHub accounts.
#
_github_
gitlab:
# (Optional) Configure this to enable GitLab support
#
instances:
gitlab.com:
url: https://gitlab.com
webhook:
secret: secrettoken
publicUrl: https://example.com/hookshot/
userIdPrefix:
# (Optional) Prefix used when creating ghost users for GitLab accounts.
#
_gitlab_
figma:
# (Optional) Configure this to enable Figma support
#
publicUrl: https://example.com/hookshot/
instances:
your-instance:
teamId: your-team-id
accessToken: your-personal-access-token
passcode: your-webhook-passcode
jira:
# (Optional) Configure this to enable Jira support. Only specify `url` if you are using a On Premise install (i.e. not atlassian.com)
#
webhook:
# Webhook settings for JIRA
#
secret: secrettoken
oauth:
# (Optional) OAuth settings for connecting users to JIRA. See documentation for more information
#
client_id: foo
client_secret: bar
redirect_uri: https://example.com/bridge_oauth/
generic:
# (Optional) Support for generic webhook events.
#'allowJsTransformationFunctions' will allow users to write short transformation snippets in code, and thus is unsafe in untrusted environments
#
#
enabled: false
urlPrefix: https://example.com/webhook/
userIdPrefix: _webhooks_
allowJsTransformationFunctions: false
waitForComplete: false
feeds:
# (Optional) Configure this to enable RSS/Atom feed support
#
enabled: false
pollIntervalSeconds: 600
provisioning:
# (Optional) Provisioning API for integration managers
#
secret: "!secretToken"
passFile:
# A passkey used to encrypt tokens stored inside the bridge.
# Run openssl genpkey -out passkey.pem -outform PEM -algorithm RSA -pkeyopt rsa_keygen_bits:4096 to generate
#
passkey.pem
bot:
# (Optional) Define profile information for the bot user
#
displayname: GitHub Bot
avatar: mxc://half-shot.uk/2876e89ccade4cb615e210c458e2a7a6883fe17d
metrics:
# (Optional) Prometheus metrics support
#
enabled: true
queue:
# (Optional) Message queue / cache configuration options for large scale deployments
#
monolithic: true
port: 6379
host: localhost
logging:
# (Optional) Logging settings. You can have a severity debug,info,warn,error
#
level: info
colorize: true
json: false
timestampFormat: HH:mm:ss:SSS
widgets:
# (Optional) EXPERIMENTAL support for complimentary widgets
#
addToAdminRooms: false
disallowedIpRanges:
- 127.0.0.0/8
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
- 100.64.0.0/10
- 192.0.0.0/24
- 169.254.0.0/16
- 192.88.99.0/24
- 198.18.0.0/15
- 192.0.2.0/24
- 198.51.100.0/24
- 203.0.113.0/24
- 224.0.0.0/4
- ::1/128
- fe80::/10
- fc00::/7
- 2001:db8::/32
- ff00::/8
- fec0::/10
roomSetupWidget:
addOnInvite: false
publicUrl: http://example.com/widgetapi/v1/static
branding:
widgetTitle: Hookshot Configuration
permissions:
# (Optional) Permissions for using the bridge. See docs/setup.md#permissions for help
#
- actor: example.com
services:
- service: "*"
level: admin
listeners:
# (Optional) HTTP Listener configuration.
# Bind resource endpoints to ports and addresses.
# 'port' must be specified. Each listener must listen on a unique port.
# 'bindAddress' will default to '127.0.0.1' if not specified, which may not be suited to Docker environments.
# 'resources' may be any of webhooks, widgets, metrics, provisioning
#
- port: 9000
bindAddress: 0.0.0.0
resources:
- webhooks
- port: 9001
bindAddress: 127.0.0.1
resources:
- metrics
- provisioning
- port: 9002
bindAddress: 0.0.0.0
resources:
- widgets
Feeds
You can configure hookshot to bridge RSS/Atom feeds into Matrix.
Configuration
feeds:
# (Optional) Configure this to enable RSS/Atom feed support
#
enabled: true
pollIntervalSeconds: 600
pollIntervalSeconds
specifies how often each feed will be checked for updates.
It may be checked less often if under exceptional load, but it will never be checked more often than every pollIntervalSeconds
.
Each feed will only be checked once, regardless of the number of rooms to which it's bridged.
No entries will be bridged upon the “initial sync” -- all entries that exist at the moment of setup will be considered to be already seen.
Usage
Adding new feeds
To add a feed to your room:
- Invite the bot user to the room.
- Make sure the bot able to send state events (usually the Moderator power level in clients)
- Say
!hookshot feed <URL>
where<URL>
links to an RSS/Atom feed you want to subscribe to.
Listing feeds
You can list all feeds that a room you're in is currently subscribed to with !hookshot feed list
.
It requires no special permissions from the user issuing the command.
Removing feeds
To remove a feed from a room, say !hookshot feed remove <URL>
, with the URL specifying which feed you want to unsubscribe from.
Figma
Setting up
To bridge Figma webhooks with Hookshot, you will need:
- A personal access token with admin access to the team you intend to bridge.
- A figma account that is on the professional tier, as the free tier does provide webhook access.
- Your team ID. You can get this by going to the team page on Figma, and looking for the ID in the url (e.g. 12345 in
https://www.figma.com/files/team/12345/...
)
Configuration
You can now set some configuration in the bridge config.yml
figma:
publicUrl: https://example.com/hookshot/
instances:
your-instance:
teamId: your-team-id
accessToken: your-personal-access-token
passcode: your-webhook-passcode
your-instance
should be a friendly name for your instance E.g. matrix-dot-org
.
The publicUrl
value must be the public path to /figma/webhook
on the webhooks listener. E.g. if your load balancer points https://example.com/hookshot
to the bridge's webhooks listener, you should use the path https://example.com/hookshot/figma/webhook
.
The accessToken
should be the personal access token for your account.
The passcode
should be a randomly generated code which is used to authenticate requests from Figma.
The bridge will automatically set up a webhook on Figma for you upon startup, and will automatically reconfigure that webhook if the publicUrl
or passcode
changes.
Next steps
If you have followed these steps correctly, Figma should now be configured with hookshot 🥳.
To bridge a figma file into your room, you should:
- Invite the bot user to the room.
- Make sure the bot able to send state events (usually the Moderator power level in clients)
- Say
!hookshot figma file fileUrl
wherefileUrl
is the URL to the figma file e.ghttps://www.figma.com/files/project/12345/...
- Figma comments will now be bridged into the room.
Setting up GitHub
GitHub App
This bridge requires a GitHub App. You will need to create one.
Webhook
The Webhook URL should point to the public address of your hookshot instance, at the /
path.
You MUST also provide a secret, which should match the github.webhook.secret
value in your config.
Permissions
You will need to enable the following permissions:
- Repository
- Actions (
read
) - Contents (
read
) - Discussions (
read & write
) - Issues (
read & write
) - Metadata
- Projects (
read & write
) - Pull requests (
read & write
)
- Actions (
- Organisation
- Team Discussions (
read & write
)
- Team Discussions (
Hookshot handles the following webhook event types:
- Commit comment
- Create
- Delete
- Discussion
- Discussion comment
- Issue comment
- Issues
- Project
- Project card
- Project column
- Pull request
- Pull request review
- Pull request review comment
- Push
- Release
- Repository
- Workflow run
You can disable any of these to disable the events being handled in Hookshot.
Once you have setup your app, you can move onto configuring the bridge:
Bridge Configuration
The GitHub service requires a few connection options.
github:
auth:
id: 123
privateKeyFile: github-key.pem
webhook:
secret: secrettoken
oauth:
client_id: foo
client_secret: bar
redirect_uri: https://example.com/bridge_oauth/
defaultOptions:
showIssueRoomLink: false
In the auth
section, you will need to supply the App ID given in your GitHub App page.
The privateKeyFile
can be generated by clicking "Generate a private key" under the Private keys section on the GitHub app page.
Docker users should store this file alongside config.yml
and provide the path /data/github-key.pem
.
The webhook
section takes a secret, which is Webhook secret on the GitHub App page.
The oauth
section should include both the Client ID and Client Secret on the GitHub App page.
The redirect_uri
value must be the public path to /oauth
on the webhooks path. E.g. if your load balancer
points https://example.com/hookshot
to the bridge webhooks
listener, you should use the path https://example.com/hookshot/oauth
.
This value MUST exactly match the Callback URL on the GitHub App page.
defaultOptions
allows you to set some defaults for room connections. Options listed on this page
are supported.
Next steps
If you have followed these steps correctly, GitHub should now be configured with hookshot 🥳.
You can now follow the guide on authenticating with GitHub, and then bridging a room
GitLab
Configuration
GitLab configuration is fairly straight-forward:
# (Optional) Configure this to enable GitLab support
#
instances:
gitlab:
url: https://gitlab.com
webhook:
secret: secrettoken
publicUrl: https://example.com/webhooks/
You need to list all the instances you plan to connect to in the config.yml
. This is
used so that users can give a short name like gitlab
or matrix.org
when they want
to specify an instance.
You should generate a webhook secret
(e.g. pwgen -n 64 -s 1
) and then use this as your
"Secret token" when adding webhooks.
The publicUrl
must be the URL where GitLab webhook events are received (i.e. the path to /
for your webhooks
listener).
Adding a repository
You can now follow the guide on authenticating with GitLab, and then bridging a room
JIRA
Adding a webhook to a JIRA Organisation
This should be done for all JIRA organisations you wish to bridge. The setup steps are the same for both On-Prem and Cloud.
You need to go to the WebHooks
configuration page under Settings > System.
Next, add a webhook that points to /
on the public webhooks address for hookshot. You should also include a
secret value by appending ?secret=your-webhook-secret
. The secret value can be anything, but should
be reasonably secure and should also be stored in the config.yml
file.
Ensure that you enable all the events that you wish to be bridge.
Configuration
You can now set some configuration in the bridge config.yml
jira:
webhook:
secret: some-secret
oauth:
... # See below
You can omit the oauth
section if you are not planning to allow users to log in and use interactive features (i.e. webhook only mode).
Connecting Matrix users to JIRA
Hookshot allows Matrix users to directly interact with JIRA through the bridge. The setup process differs depending on whether you are running a on-premise/enterprise instance or using Atlassian's cloud service.
JIRA OAuth for Cloud
You will need a Atlassian account with the ability to use the developer tools in order to create the app.
You'll first need to head to https://developer.atlassian.com/console/myapps/create-3lo-app/ to create a "OAuth 2.0 (3LO)" integration.
Once named and created, you will need to:
- Enable the User REST, Jira Platform REST and User Identity APIs under Permissions.
- Use rotating tokens under Authorisation.
- Set a callback url. This will be the public URL to hookshot with a path of
/jira/oauth
. - Copy the client ID and Secret from Settings
You can now set some configuration in the bridge config.yml
jira:
webhook:
# A secret string generated by you.
secret: some-secret
oauth:
client_id: your-client-id
client_secret: your-client-secret
redirect_uri: https://example.com/hookshot/jira/oauth
The redirect_uri
value must be the public path to /jira/oauth
on the webhooks path. E.g. if your load balancer
points https://example.com/hookshot
to the bridge webhooks
listener, you should use the path https://example.com/hookshot/jira/oauth
.
This value MUST exactly match the Callback URL on the JIRA integration page page.
JIRA for On-Premise (Datacenter)
These instructions are written for Jira Datacenter 8.x.
These instructions use openssl
to generate certificates, so users on non-Unix systems will need to find an alternative way to generate these certificates.
To begin, configure your config.yml
:
jira:
url: http://yourjirainstance.com # The location of your jira instance.
webhook:
# A secret string generated by you.
secret: Ieph7iecheiThoo1othaineewieSh1koh2chainohtooyoh4waht1oetoaSoh6oh
oauth:
# Another secret key generated by you.
consumerKey: secret-consumer-key
# Path to a private key. Generate this with `openssl genrsa -out jira_privatekey.pem 4096`
privateKey: jira_privatekey.pem
# The path to your webhooks listener on the "/jira/oauth" path.
redirect_uri: http://localhost:5065/jira/oauth
To start with, set up your JIRA instance to support OAuth.
- Open the Administration page for your JIRA instance.
- Click Applications.
- Click Application Links.
- Newer versions of JIRA need you to click "Create link"
- In the text box, enter "https://github.com/Half-Shot/matrix-hookshot". Jira will complain but click Continue.
- Fill in the details:
- The Application Name can be anything, but for simplicty we usually use
matrix-hookshot
- The Application Type should be Generic Application
- The Consumer key, and shared secret can be any string, they are not used.
- The URLs can be any URL, they are not used (e.g.
http://example.com
) - Ensure you enable Create incoming link
- Click Continue
- The Application Name can be anything, but for simplicty we usually use
- On the next step:
- Enter your
consumerKey
from the config file above. - The
consumerName
can be anything, but will be visible to users of your app. You could use something likeMatrix
,Hookshot
or anything else. - The
publicKey
can be generated by runningopenssl rsa -in jira_privatekey.pem -pubout
on the key you created earlier.
- Enter your
- Congratulations, you now have OAuth set up.
Next steps
If you have followed these steps correctly, JIRA should now be configured with hookshot 🥳.
You can now follow the guide on authenticating with JIRA.
Webhooks
Hookshot supports generic webhook support so that services can send messages into Matrix rooms without being aware of the Matrix protocol. This works by having services hit a unique URL that then transforms a HTTP payload into a Matrix message.
Configuration
You will need to add the following configuration to the config file.
generic:
enabled: true
urlPrefix: https://example.com/mywebhookspath/
allowJsTransformationFunctions: false
waitForComplete: false
# userIdPrefix: webhook_
The webhooks listener listens on the path /webhook
.
The bridge listens for incoming webhooks requests on the host and port provided in the listeners
config.
urlPrefix
describes the public facing URL of your webhook handler. For instance, if your load balancer redirected
webhook requests from https://example.com/mywebhookspath
to the bridge (on /webhook
), an example webhook URL would look like:
https://example.com/mywebhookspath/abcdef
.
waitForComplete
causes the bridge to wait until the webhook is processed before sending a response. Some services prefer you always
respond with a 200 as soon as the webhook has entered processing (false
) while others prefer to know if the resulting Matrix message
has been sent (true
). By default this is false
.
You may set a userIdPrefix
to create a specific user for each new webhook connection in a room. For example, a connection with a name
like example
for a prefix of webhook_
will create a user called @webhook_example:example.com
. If you enable this option,
you need to configure the user to be part of your registration file e.g.:
# registration.yaml
...
namespaces:
users:
- regex: "@webhook_.+:example.com" # Where example.com is your domain name.
exclusive: true
Adding a webhook
To add a webhook to your room:
- Invite the bot user to the room.
- Make sure the bot able to send state events (usually the Moderator power level in clients)
- Say
!hookshot webhook example
whereexample
is a name for your hook. - The bot will respond with the webhook URL to be sent to services.
Webhook Handling
Hookshot handles HTTP requests with a method of GET
, POST
or PUT
.
If the request is a GET
request, the query parameters are assumed to be the body. Otherwise, the body of the request should be a JSON payload.
If the body contains a text
key, then that key will be used as a message body in Matrix (aka body
). This text will be automatically converted from Markdown to HTML (unless
a html
key is provided.).
If the body contains a html
key, then that key will be used as the HTML message body in Matrix (aka formatted_body
). A text
key fallback MUST still be provided.
If the body also contains a username
key, then the message will be prepended by the given username. This will be prepended to both text
and html
.
If the body does NOT contain a text
field, the full JSON payload will be sent to the room. This can be adapted into a message by creating a JavaScript transformation function.
JavaScript Transformations
This bridge supports creating small JavaScript snippets to translate an incoming webhook payload into a message for the room, giving you a very powerful ability to generate messages based on whatever input is coming in.
The input is parsed and exectuted within a seperate JavaScript Virtual Machine context, and is limited to an execution time of 2 seconds.
With that said, the feature is disabled by default and allowJsTransformationFunctions
must be enabled in the config.
The code snippets can be edited by editing the Matrix state event corresponding to this connection (with a state type of uk.half-shot.matrix-hookshot.generic.hook
).
Because this is a fairly advanced feature, this documentation won't go into how to edit state events from your client.
Please seek out documentation from your client on how to achieve this.
The script string should be set within the state event under the transformationFunction
key.
Script API
Transformation scripts have a versioned API. You can check the version of the API that the hookshot instance supports
at runtime by checking the HookshotApiVersion
variable. If the variable is undefined, it should be considered v1
.
The execution environment will contain a data
variable, which will be the body of the incoming request (JSON will be parsed into an Object
).
Scripts are executed syncronously and expect the result
variable to be set.
If the script contains errors or is otherwise unable to work, the bridge will send an error to the room. You can check the logs of the bridge for a more precise error.
V2 API
The v2
api expects an object to be returned from the result
variable.
{
"version": "v2" // The version of the schema being returned from the function. This is always "v2".
"empty": true|false, // Should the webhook be ignored and no output returned. The default is false (plain must be provided).
"plain": "Some text", // The plaintext value to be used for the Matrix message.
"html": "<b>Some</b> text", // The HTML value to be used for the Matrix message. If not provided, plain will be interpreted as markdown.
"msgtype": "some.type", // The message type, such as m.notice or m.text, to be used for the Matrix message. If not provided, m.notice will be used.
}
Example script
Where data
= {"counter": 5, "maxValue": 4}
if (data.counter === undefined) {
// The API didn't give us a counter, send no message.
result = {empty: true, version: "v2"};
} else if (data.counter > data.maxValue) {
result = {plain: `**Oh no!** The counter has gone over by ${data.counter - data.maxValue}`, version: "v2"};
} else {
result = {plain: `*Everything is fine*, the counter is under by ${data.maxValue - data.counter}`, version: "v2"};
}
V1 API
The v1 API expects result
to be a string. The string will be automatically transformed into markdown. All webhook messages
will be prefix'd with Received webhook:
. If result
is falsey (undefined, false or null) then the message will be No content
.
Example script
Where data
= {"counter": 5, "maxValue": 4}
if (data.counter > data.maxValue) {
result = `**Oh no!** The counter has gone over by ${data.counter - data.maxValue}`
} else {
result = `*Everything is fine*, the counter is under by ${data.maxValue - data.counter}`
}
Usage
This section covers how to use the bridge, once it's set up. We break these down into categories:
- Authentication: How to connect your user account to a remote service to use rich commands.
- Room Connections: How to start connecting rooms to services.
If you are looking for help on what bot commands you can run in a room, you can refer to the help information by saying*:
!gh help
in rooms connected to GitHub.!gl help
in rooms connected to GitLab.!jira help
in rooms connected to JIRA.
*the prefix may vary if the commandPrefix
configuration in the room is set.
Dynamic Rooms
Some bridges support dynamically creating rooms that point to resources based on an alias given by a user.
Presently, the following are supported:
#github_$owner:example.com
- For a Matrix space containing a user's discussions and repositories#github_$owner_$repo:example.com
- For GitHub repositories#github_$owner_$repo_$issuenumber:example.com
- For GitHub issues#github_disc_$owner_$repo:example.com
- For GitHub discussions for a repository
Where $word is replaced by the appropriate value.
(Some of these may not be supported, depending on bridge configuration and registration file changes)
Disabling support
This feature can be disabled simply by removing alias fields from the registration file.
Authenticating
To authenticate with services, you must first have a DM room with the bridge set up. In this guide,
we are going to assume the bot is called @hookshot:example.com
but this will vary for your setup. For all
the instructions below, commands should only be executed in the DM room.
GitHub
You can authenticate via OAuth or a Personal Access Token (PAT) when using GitHub. Authentication is required when trying to bridge GitHub resources into rooms.
To authenticate with a personal access token:
-
Open https://github.com/settings/tokens (Github > Settings > Developer Settings / Personal access tokens)
-
Click Generate new token
-
Give it a good name, and a sensible expiration date. For scopes you will need:
- Repo (to access repo information)
- public_repo
- repo:status
- Workflow (if you want to be able to launch workflows / GitHub actions from Matrix)
- Notifications (if you want to bridge in your notifications to Matrix)
- User
- read:user
- write:discussion (for GitHub discussion support)
- read:discussion
- Repo (to access repo information)
-
Send the generated token to the bridge by saying
github setpersonaltoken %your-token%
. You can redact the message afterwards if you like. -
The bridge will have connected you. You can check the status at any time by saying
github hastoken
GitLab
You can authenticate with GitLab by supplying a Personal Access Token. OAuth-style authentication isn't supported yet.
- You will need to have configured a GitLab instance in your config.yml for the instance you want to log in to.
- Open https://%instance%/-/profile/personal_access_tokens (GitLab > User Settings > Access Tokens), where instance is your GitLab instance address.
- For the public GitLab server, this would be "gitlab.com"
- Give it a good name, and a sensible expiration date. For scopes you will need to tick
api
. - Send the generated token to the bridge by saying
gitlab personaltoken %instance% %your-token%
. You can redact the message afterwards if you like. - The bridge will have connected you. You can check the status at any time by saying
gitlab hastoken %instance%
JIRA
You can log in to JIRA via OAuth. This means you will need to have configured OAuth support in your config.yml
, and
have the endpoints required accessible from the internet. Authentication is required when trying to bridge JIRA resources into rooms.
- Say
jira login
to get the URL to authenticate via. - Click the URL sent by the bot.
- Follow the steps, ensuring you authenticate with the right user.
- If all goes well, you will now be connected. You can check the status and authorisatied instances by saying
jira whoami
Room Configuration
Hookshot works off the principle of Connections.
A room can have many connections to different services. The connections are defined in the room state of a room. A connection defines the service it connects to, the type of integration (e.g. GitHub repo, Jira Project) and any additional configuration.
Hookshot supports several connection types, which are defined under the Room Configuration heading.
The availablilty of connection types depends on the configuration provided to hookshot.
The !hookshot
command
Rooms can be bridged by inviting the hookshot bot into a room, and then running the
!hookshot
command. Running !hookshot help
will give you some details, but you should
see the documentation provided for information on each connection type.
GitHub Repository
This connection type connects a GitHub repository (e.g. https://github.com/matrix-org/matrix-hookshot) to a room.
You can run commands to create and manipulate issues, and receive notifications when something changes such as a new release.
Setting up
To set up a connection to a GitHub Repository in a new room:
(NB you must have permission to bridge GitHub repositories before you can use this command, see auth.)
- The bridge will need to either:
- Have a GitHub installation registered with the organisation (or GitHub user account)
- The requesting user must be authenticated with the bridge via OAuth and the repository must be part of their GitHub account.
- Create a new, unencrypted room. It can be public or private.
- Invite the bridge bot (e.g.
@hookshot:example.com
). - Give the bridge bot moderator permissions or higher (power level 50) (or otherwise configure the room so the bot can edit room state).
- Send the command
!hookshot github repo https://github.com/my/project
. - If you have permission to bridge this repo, the bridge will respond with a confirmation message.
Configuration
This connection supports a few options which can be defined in the room state:
Option | Description | Allowed values | Default |
---|---|---|---|
ignoreHooks | Choose to exclude notifications for some event types | Array of: Supported event types | empty |
commandPrefix | Choose the prefix to use when sending commands to the bot | A string, ideally starts with "!" | !gh |
showIssueRoomLink | When new issues are created, provide a Matrix alias link to the issue room | true/false | false |
prDiff | Show a diff in the room when a PR is created, subject to limits | {enabled: boolean, maxLines: number} | {enabled: false} |
includingLabels | Only notify on issues matching these label names | Array of: String matching a label name | empty |
excludingLabels | Never notify on issues matching these label names | Array of: String matching a label name | empty |
hotlinkIssues | Send a link to an issue/PR in the room when a user mentions a prefix followed by a number | { prefix: string } | {prefix: "#"} |
newIssue | Configuration options for new issues | { labels: string[] } | empty |
newIssue.labels | Automatically set these labels on issues created via commands | Array of: String matching a label name | empty |
Supported event types
This connection supports sending messages when the following actions happen on the repository.
- issue
- issue.created
- issue.changed
- issue.edited
- issue.labeled
- pull_request
- pull_request.closed
- pull_request.merged
- pull_request.opened
- pull_request.ready_for_review
- pull_request.reviewed
- release
- release.created
GitLab Project
This connection type connects a GitLab project (e.g. https://gitlab.matrix.org/matrix-org/olm) to a room.
Setting up
To set up a connection to a GitLab project in a new room:
(NB you must have permission to bridge GitLab repositories before you can use this command, see auth.)
- Create a new, unencrypted room. It can be public or private.
- Invite the bridge bot (e.g.
@hookshot:example.com
). - Give the bridge bot moderator permissions or higher (power level 50) (or otherwise configure the room so the bot can edit room state).
- Send the command
!hookshot gitlab project https://mydomain/my/project
. - If you have permission to bridge this repo, the bridge will respond with a confirmation message. (Users with
Developer
permissions or greater can bridge projects.) - If you have configured the bridge with a
publicUrl
insidegitlab.webhook
, it will automatically provision the webhook for you. - Otherwise, you'll need to manually configure the webhook to point to your public address for the webhooks listener.
Configuration
This connection supports a few options which can be defined in the room state:
Option | Description | Allowed values | Default |
---|---|---|---|
ignoreHooks | Choose to exclude notifications for some event types | Array of: Supported event types | empty |
commandPrefix | Choose the prefix to use when sending commands to the bot | A string, ideally starts with "!" | !gh |
pushTagsRegex | Only mention pushed tags which match this regex | Regex string | empty |
prDiff | Show a diff in the room when a PR is created, subject to limits | {enabled: boolean, maxLines: number} | {enabled: false} |
includingLabels | Only notify on issues matching these label names | Array of: String matching a label name | empty |
excludingLabels | Never notify on issues matching these label names | Array of: String matching a label name | empty |
Supported event types
This connection supports sending messages when the following actions happen on the repository.
- merge_request
- merge_request.close
- merge_request.merge
- merge_request.open
- merge_request.review.comments
- merge_request.review
- push
- release
- release.created
- tag_push
- wiki
Prometheus Metrics
You can configure metrics support by adding the following to your config:
metrics:
enabled: true
bindAddress: 127.0.0.1
port: 9002
Below is the generated list of Prometheus metrics for Hookshot.
hookshot
Metric | Help | Labels |
---|---|---|
hookshot_webhooks_http_request | Number of requests made to the hookshot webhooks handler | path, method |
hookshot_provisioning_http_request | Number of requests made to the hookshot webhooks handler | path, method |
hookshot_queue_event_pushes | Number of events pushed through the queue | event |
hookshot_connection_event_failed | The number of events that failed to process | event, connectionId |
hookshot_connections | The number of active hookshot connections | service |
hookshot_notifications_push | Number of notifications pushed | service |
hookshot_notifications_service_up | Is the notification service up or down | service |
hookshot_notifications_watchers | Number of notifications watchers running | service |
matrix
Metric | Help | Labels |
---|---|---|
matrix_api_calls | The number of Matrix client API calls made | method |
matrix_api_calls_failed | The number of Matrix client API calls which failed | method |
matrix_appservice_events | The number of events sent over the AS API |
feed
Metric | Help | Labels |
---|---|---|
feed_count | The number of RSS feeds that hookshot is subscribed to | |
feed_fetch_ms | The time taken for hookshot to fetch all feeds |
process
Metric | Help | Labels |
---|---|---|
process_cpu_user_seconds_total | Total user CPU time spent in seconds. | |
process_cpu_system_seconds_total | Total system CPU time spent in seconds. | |
process_cpu_seconds_total | Total user and system CPU time spent in seconds. | |
process_start_time_seconds | Start time of the process since unix epoch in seconds. | |
process_resident_memory_bytes | Resident memory size in bytes. | |
process_virtual_memory_bytes | Virtual memory size in bytes. | |
process_heap_bytes | Process heap size in bytes. | |
process_open_fds | Number of open file descriptors. | |
process_max_fds | Maximum number of open file descriptors. |
nodejs
Metric | Help | Labels |
---|---|---|
nodejs_eventloop_lag_seconds | Lag of event loop in seconds. | |
nodejs_eventloop_lag_min_seconds | The minimum recorded event loop delay. | |
nodejs_eventloop_lag_max_seconds | The maximum recorded event loop delay. | |
nodejs_eventloop_lag_mean_seconds | The mean of the recorded event loop delays. | |
nodejs_eventloop_lag_stddev_seconds | The standard deviation of the recorded event loop delays. | |
nodejs_eventloop_lag_p50_seconds | The 50th percentile of the recorded event loop delays. | |
nodejs_eventloop_lag_p90_seconds | The 90th percentile of the recorded event loop delays. | |
nodejs_eventloop_lag_p99_seconds | The 99th percentile of the recorded event loop delays. | |
nodejs_active_handles | Number of active libuv handles grouped by handle type. Every handle type is C++ class name. | type |
nodejs_active_handles_total | Total number of active handles. | |
nodejs_active_requests | Number of active libuv requests grouped by request type. Every request type is C++ class name. | type |
nodejs_active_requests_total | Total number of active requests. | |
nodejs_heap_size_total_bytes | Process heap size from Node.js in bytes. | |
nodejs_heap_size_used_bytes | Process heap size used from Node.js in bytes. | |
nodejs_external_memory_bytes | Node.js external memory size in bytes. | |
nodejs_heap_space_size_total_bytes | Process heap space size total from Node.js in bytes. | space |
nodejs_heap_space_size_used_bytes | Process heap space size used from Node.js in bytes. | space |
nodejs_heap_space_size_available_bytes | Process heap space size available from Node.js in bytes. | space |
nodejs_version_info | Node.js version info. | version, major, minor, patch |
nodejs_gc_duration_seconds | Garbage collection duration by kind, one of major, minor, incremental or weakcb. | kind |
Provisioning
This section is not complete yet for end users. For developers, you can read the documentation for the API below:
Provisioning API for matrix-hookshot
Overview
This document describes how to integrate with matrix-hookshot
's provisoning API.
Requests made to the bridge must be against the API listener defined in the config under provisioning
, not
the appservice or webhook listeners.
Requests should always be authenticated with the secret given in the config, inside the Authorization
header.
Requests being made on behalf of users (most provisioning APIs) should include the userId as a query parameter.
GET /v1/health?userId=%40Half-Shot%3Ahalf-shot.uk
Authorization: Bearer secret
APIs are versioned independently so two endpoints on the latest version may not always have the same version.
APIs
GET /v1/health
Request the status of the provisoning API.
Response
HTTP 200
{}
Any other response should be considered a failed request (e.g. 404, 502 etc).
GET /v1/connectiontypes
Request the connection types enabled for this bridge.
Response
{
"JiraProject": {
"type": "JiraProject", // The name of the connection
"eventType": "uk.half-shot.matrix-hookshot.jira.project", // Corresponds to the state type for the connection
"service": "jira", // or github, webhook. A human-readable service name to make things look pretty
"botUserId": "@hookshot:yourdomain.com", // The bot mxid for the service. Currently, this is the sender_localpart, but may change in the future.
}
}
GET /v1/{roomId}/connections
Request the connections for a given room. The {roomId}
parameter is the target Matrix room.
Response
[{
"type": "JiraProject", // The name of the connection
"eventType": "uk.half-shot.matrix-hookshot.jira.project", // Corresponds to the state type in the connection
"id": "opaque-unique-id", // An opaque ID used to refer to this connection. Should **NOT** be assumed to be stable.
"service": "jira", // or github, webhook. A human-readable service name to make things look pretty
"botUserId": "@hookshot:yourdomain.com", // The bot mxid for the service. Currently, this is the sender_localpart, but may change in the future.
"config": {
// ... connection specific details, can be configured.
}
}]
GET /v1/{roomId}/connections/{id}
Request details of a single connection. The {roomId}
parameter is the target Matrix room.
Response
{
"type": "JiraProject", // The name of the connection
"eventType": "uk.half-shot.matrix-hookshot.jira.project", // Corresponds to the state type in the connection
"id": "opaque-unique-id", // An opaque ID used to refer to this connection. Should **NOT** be assumed to be stable.
"service": "jira", // or github, webhook. A human-readable service name to make things look pretty
"botUserId": "@hookshot:yourdomain.com", // The bot mxid for the service. Currently, this is the sender_localpart, but may change in the future.
"config": {
// ... connection specific details, can be configured.
}
}
PUT /v1/{roomId}/connections/{type}
Create a new connection of a given type. The type refers to the eventType
(IConnection.CanonicalEventType
). The {roomId}
parameter is the target Matrix room.
The body of the request is the configuration for the connection, which will be the "ConnectionState" interface for each connection.
Request body
{
// ... connection specific details, can be configured.
}
Response
{
"type": "JiraProject", // The name of the connection
"eventType": "uk.half-shot.matrix-hookshot.jira.project", // Corresponds to the state type in the connection
"id": "opaque-unique-id", // An opaque ID used to refer to this connection. Should **NOT** be assumed to be stable.
"service": "jira", // or github, webhook. A human-readable service name to make things look pretty
"botUserId": "@hookshot:yourdomain.com", // The bot mxid for the service. Currently, this is the sender_localpart, but may change in the future.
"config": {
// ... connection specific details, can be configured.
}
}
PATCH /v1/{roomId}/connections/{id}
Update a connection's configuration. The id
refers to the id
returned in the GET response.
The body of the request is the configuration for the connection, which will be the "ConnectionState" interface for each connection.
Request body
{
// ... connection specific details, can be configured.
}
Response
{
"type": "JiraProject", // The name of the connection
"eventType": "uk.half-shot.matrix-hookshot.jira.project", // Corresponds to the state type in the connection
"id": "opaque-unique-id", // An opaque ID used to refer to this connection. Should **NOT** be assumed to be stable.
"service": "jira", // or github, webhook. A human-readable service name to make things look pretty
"botUserId": "@hookshot:yourdomain.com", // The bot mxid for the service. Currently, this is the sender_localpart, but may change in the future.
"config": {
// ... connection specific details, can be configured.
}
}
DELETE /v1/{roomId}/connections/{id}
Delete a connection. The id
refers to the id
returned in the GET response.
Response
{
"ok": true
}
Service specific APIs
Some services have specific APIs for additional functionality, like OAuth.
GitHub
GET /v1/github/oauth?userId={userId}
Request an OAuth url for the given user. Once the user has completed the steps in the OAuth process, the bridge will be granted access.
Response
[{
"user_url": "https://github.com/login/oauth/authorize?...",
"org_url": "https://github.com/apps/matrix-bridge/installations/new",
}]
GET /v1/github/account?userId={userId}
Request the status of the users account. This will return a loggedIn
value to determine if the
bridge has a GitHub identity stored for the user, and any organisations they have access to.
Response
{
"loggedIn": true,
"organisations":[{
"name": "half-shot",
"avatarUrl": "https://avatars.githubusercontent.com/u/8418310?v=4"
}]
}
GET /v1/github/orgs/{orgName}/repositories?userId={userId}&page={page}&perPage={perPage}
Request a list of all repositories a user is a member of in the given org. The owner
and name
value of a repository can be given to create a new GitHub connection.
This request is paginated, and page
sets the page (defaults to 1
) while perPage
(defaults to 10
) sets the number of entries per page.
This request can be retried until the number of entries is less than the value of perPage
.
Response
{
"loggedIn": true,
"repositories":[{
"name": "matrix-hookshot",
"owner": "matrix-org",
"fullName": "matrix-org/matrix-hookshot",
"avatarUrl": "https://avatars.githubusercontent.com/u/8418310?v=4",
"description": "A bridge between Matrix and multiple project management services, such as GitHub, GitLab and JIRA. "
}]
}
GET /v1/github/repositories?userId={userId}&page={page}&perPage={perPage}
Request a list of all repositories a user is a member of (including those not belonging to an org). The owner
and name
value of a repository can be given to create a new GitHub connection.
If the user has only allowed a subset of repositories to be bridged, changeSelectionUrl
will be defined and can be used to expand the search query.
This request is paginated, and page
sets the page (defaults to 1
) while perPage
(defaults to 10
) sets the number of entries per page.
This request can be retried until the number of entries is less than the value of perPage
.
Response
{
"loggedIn": true,
"changeSelectionUrl": "https://github.com/settings/installations/12345",
"repositories":[{
"name": "matrix-hookshot",
"owner": "matrix-org",
"fullName": "matrix-org/matrix-hookshot",
"avatarUrl": "https://avatars.githubusercontent.com/u/8418310?v=4",
"description": "A bridge between Matrix and multiple project management services, such as GitHub, GitLab and JIRA. "
}]
}
JIRA
GET /v1/jira/oauth?userId={userId}
Request an OAuth url for the given user. Once the user has completed the steps in the OAuth process, the bridge will be granted access.
Response
{
"url": "https://auth.atlassian.com/authorize?..."
}
GET /v1/jira/account?userId={userId}
Request the status of the users account. This will return a loggedIn
value to determine if the
bridge has a JIRA identity stored for the user, and any instances they have access to. Note that if a
user does not have access to an instance, they can authenticate again to gain access to it (if they are able
to consent).
Response
{
"loggedIn": true,
"instances":[{
"name": "acme",
"url": "https://acme.atlassian.net"
}]
}
GET /v1/jira/instances/{instanceName}/projects?userId={userId}
Request a list of all projects a user can see in a given instance. The url
value of a project can be given to create
a new JIRA connection.
Response
[{
"key": "PLAY",
"name": "Jira Playground",
"url": "https://acme.atlassian.net/projects/PLAY"
}]
Workers
Hookshot supports running in a worker configuration, using Redis as the middleman process to handle traffic between processes.
Running in multi-process mode
You must first have a working redis instance somewhere which can talk between processes. For example, in Docker you can run:
docker run --name github-bridge-redis -p 6379:6379 -d redis
.
The processes should all share the same config, which should contain the correct config enable redis:
queue:
monolithic: false
port: 6379
host: github-bridge-redis
Once that is done, you can simply start the processes by name using yarn:
yarn start:webhooks
yarn start:matrixsender
yarn start:app
Be aware that you will need to start all worker types when running in worker mode, as the service does not allow a hybrid worker approach.
Widgets
Hookshot supports using widgets to configure connections in rooms. Widgets allow users to view and configure rooms without the need to type commands. The widget feature is designed to complement the existing command system, rather than replace it.
Configuration
widgets:
addToAdminRooms: false
roomSetupWidget:
addOnInvite: false
# disallowedIpRanges:
# - 127.0.0.0/8
# - 10.0.0.0/8
# - 172.16.0.0/12
# - 192.168.0.0/16
# - 100.64.0.0/10
# - 192.0.0.0/24
# - 169.254.0.0/16
# - 192.88.99.0/24
# - 198.18.0.0/15
# - 192.0.2.0/24
# - 198.51.100.0/24
# - 203.0.113.0/24
# - 224.0.0.0/4
# - ::1/128
# - fe80::/10
# - fc00::/7
# - 2001:db8::/32
# - ff00::/8
# - fec0::/10
publicUrl: http://example.com/widgetapi/v1/static
branding:
widgetTitle: Hookshot Configuration
The admin room feature is still very barebones so while it's included here for completeness, most instances
should leave addToAdminRooms
off (as it is by default). This flag will add an "admin room" widget to user admin rooms.
The room setup feature is more complete, supporting generic webhook configuration (with more options coming soon).
This can be enabled by setting roomSetupWidget
to an object. You can add the widget by saying !hookshot setup-widget
in any room.
When addOnInvite
is true, the bridge will add a widget to rooms when the bot is invited, and the room has no existing connections.
disallowedIpRanges
describes which IP ranges should be disallowed when resolving homeserver IP addresses (for security reasons).
Unless you know what you are doing, it is recommended to not include this key. The default blocked IPs are listed above for your convienence.
publicUrl
should be set to the publically reachable address for the widget public
content. By default, hookshot hosts this content on the
widgets
listener under /widgetapi/v1/static
.
branding
allows you to change the strings used for various bits of widget UI. At the moment you can:
- Set
widgetTitle
to change the title of the widget that is created.
In addition to setting up the widgets config, you must bind a listener for the widgets resource in your listeners
config.
listeners:
- port: 5069
bindAddress: 0.0.0.0
resources:
- widgets
See the setup page for more information on listeners.
API
The API for widgets is currently in flux due to being fairly new, and it's not reccomended to develop against it at this time. At a future date this API will be merged with the existing provisioning API and the details will be published.