Skip to content

Document generic webhooks #8609

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 26 commits into from
Nov 8, 2021
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
c0689f1
Document generic webhooks
astrojuanlu Oct 21, 2021
c6c499b
Whitespace
astrojuanlu Oct 25, 2021
0a031b3
Describe legacy webhooks and migration path
astrojuanlu Oct 25, 2021
9405d4b
Add examples
astrojuanlu Oct 25, 2021
2dd6b39
Fix external version disclaimer
astrojuanlu Oct 25, 2021
3e988a5
Add example to validate webhook signature
astrojuanlu Oct 25, 2021
80446dd
Merge branch 'webhooks' into webhooks-documentation
astrojuanlu Oct 25, 2021
ce31189
Fix markup for Discord webhook example
astrojuanlu Oct 25, 2021
43f9dd4
Clarify that validation is optional
astrojuanlu Oct 25, 2021
b6fd66e
Style improvements
astrojuanlu Oct 25, 2021
b443b4f
Fix digest comparison
astrojuanlu Oct 25, 2021
8f55e27
Webhook example improvements
astrojuanlu Oct 25, 2021
39c8ab2
Update external documentation links
astrojuanlu Oct 25, 2021
fe45d31
Fix screenshot zoom
astrojuanlu Oct 25, 2021
33f9557
Move payload validation down
astrojuanlu Nov 8, 2021
1077d61
Update template syntax
astrojuanlu Nov 8, 2021
481380d
Merge branch 'webhooks' into webhooks-documentation
astrojuanlu Nov 8, 2021
19eec46
Complete webhook example payload
astrojuanlu Nov 8, 2021
088e629
Add screenshot for custom payload
astrojuanlu Nov 8, 2021
2a0a031
Style improvements
astrojuanlu Nov 8, 2021
ec9b4fc
Switch to one space for template substitutions
astrojuanlu Nov 8, 2021
b049047
Add examples for URLs
astrojuanlu Nov 8, 2021
94f9fc1
Fix parameter name
astrojuanlu Nov 8, 2021
a928097
Complete remaining variables
astrojuanlu Nov 8, 2021
8f20472
Fix title length
astrojuanlu Nov 8, 2021
04e2953
Fix JSON
astrojuanlu Nov 8, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added docs/_static/images/webhooks-activity.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/_static/images/webhooks-events.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/_static/images/webhooks-payload.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/_static/images/webhooks-secret.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
269 changes: 247 additions & 22 deletions docs/build-notifications.rst
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
Enabling Build Notifications
============================
Build Notifications and Webhooks
================================

.. note::

Currently we don't send notifications when
a :doc:`build from a pull request fails </pull-requests>`.
Currently we don't send notifications nor trigger webhooks
on :doc:`builds from pull requests </pull-requests>`.

Using email
-----------
Email notifications
-------------------

Read the Docs allows you to configure emails that can be sent on failing builds.
This makes sure you know when your builds have failed.
Expand All @@ -20,31 +20,256 @@ Take these steps to enable build notifications using email:

You should now get notified by email when your builds fail!

Using webhook
-------------
Webhooks
--------

Read the Docs can also send webhooks when builds are triggered, successful or failed.

Take these steps to enable build notifications using a webhook:

* Go to :guilabel:`Admin` > :guilabel:`Notifications` in your project.
* Fill in the **URL** field under the **New Webhook Notifications** heading
* Submit the form
* Go to :guilabel:`Admin` > :guilabel:`Webhooks` in your project.
* Fill in the **URL** field and select what events will trigger the webhook
* Modify the payload or leave the default (see below)
* Click on :guilabel:`Save`

.. figure:: /_static/images/webhooks-events.png
:align: center
:alt: URL and events for a webhook

URL and events for a webhook

Every time one of the checked events triggers,
Read the Docs will send a POST request to your webhook URL.
The default payload will look like this:

.. code-block:: json

{
"event": "build:triggered",
"name": "docs",
"slug": "docs",
"version": "latest",
"commit": "2552bb609ca46865dc36401dee0b1865a0aee52d",
"build": 15173336,
"start_date": "2021-11-03T16:23:14",
"build_url": "https://readthedocs.org/projects/docs/builds/15173336/",
"docs_url": "http://docs.readthedocs.io/en/latest/"
}

When a webhook is sent, a new entry will be added to the
"Recent Activity" table. By clicking on each individual entry,
you will see the server response, the webhook request, and the payload.

.. figure:: /_static/images/webhooks-activity.png
:align: center
:alt: Activity of a webhook

Activity of a webhook

Custom payload examples
~~~~~~~~~~~~~~~~~~~~~~~

You can customize the payload of the webhook to suit your needs,
as long as it is valid JSON. Below you have a couple of examples,
and in the following section you will find all the available variables.

.. figure:: /_static/images/webhooks-payload.png
:width: 80%
:align: center
:alt: Custom payload

The project name, slug and its details for the build will be sent as POST request to your webhook URL:
Custom payload

Slack
+++++

.. code-block:: json

{
"attachments": [
{
"color": "#db3238",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Read the Docs build failed*"
}
},
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": "*Project*: <{{project.url}}|{{project.name}}>"
},
{
"type": "mrkdwn",
"text": "*Version*: {{version.name}} ({{build.commit}})"
},
{
"type": "mrkdwn",
"text": "*Build*: <{{build.url}}|{{build.id}}>"
}
]
}
]
}
]
}

More information on `the Slack Incoming Webhooks documentation <https://api.slack.com/messaging/webhooks>`_.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like bad UX. I would strongly suggest that we implement a standard Slack webhook option, instead of forcing users to copy & paste large bits of JSON into our dashboard. /cc @stsewd

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would this be an option to pre-fill the content, or just an option where users don't have control over?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have this for automation rules for example

Screenshot from 2021-10-26 13-27-16

But I'd prefer the option to pre-fill the content

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and user would still need to setup webhook from their side on slack/discord.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stsewd I think something that we control should be the default. Many users don't actually want to think about this, and having something that we control and can improve over time without users having to touch it feels like better UX?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the same fashion, imagine that Slack changed their webhook format or something. It would be a huge pain if we had to email every user to ask them to update the blob of JSON in the config. We should definitely support "Slack" as a first-class option that we control the format of.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess if that happens slack will provide users a different url or option, then we are forced to support both formats using an option or something.

My other worry here is if we change something, it will affect everyone, users may not like something from the new change and will request for options, and we end up adding a lot of options in the UI or the users will end up creating their own custom payload at the end.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stsewd Valid concerns, but I think there are a large number of users who just want "Slack notifications" that work well. Asking them to customize them is an actively negative experience. I think we want both the simple & custom, so users can choose what works better for them.


Discord
+++++++

.. code-block:: json

{
"username": "Read the Docs",
"content": "Read the Docs build failed",
"embeds": [
{
"name": "Read the Docs",
"slug": "rtd",
"build": {
"id": 6321373,
"commit": "e8dd17a3f1627dd206d721e4be08ae6766fda40",
"state": "finished",
"success": false,
"date": "2017-02-15 20:35:54"
}
"title": "Build logs",
"url": "{{build.url}}",
"color": 15258703,
"fields": [
{
"name": "*Project*",
"value": "{{project.url}}",
"inline": true
},
{
"name": "*Version*",
"value": "{{version.name}} ({{build.commit}})",
"inline": true
},
{
"name": "*Build*",
"value": "{{build.url}}"
}
]
}
]
}

More information on `the Discord webhooks documentation <https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks>`_.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should maybe start each section with the links to the docs to create the webhhok on slack/discord and then give them the payload to use (Follow this steps on discord/slack, use the given URL when creating a webhook on rtd and add this payload).


Variable substitutions reference
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

``{{event}}``
Event that triggered the webhook, one of ``build:triggered``, ``build:failed``, or ``build:passed``.

``{{build.id}}``
Build ID.

``{{build.commit}}``
Commit corresponding to the build, if present (otherwise ``""``).

``{{build.url}}``
URL of the build.

``{{build.docsurl}}``
URL of the documentation corresponding to the build.

``{{organization.name}}``
Organization name (Commercial only).

``{{organization.slug}}``
Organization slug (Commercial only).

``{{project.slug}}``
Project slug.

``{{project.name}}``
Project name.

``{{project.url}}``
URL of the project :term:`dashboard`.

``{{version.slug}}``
Version slug.

``{{version.name}}``
Version name.

Validating the payload
~~~~~~~~~~~~~~~~~~~~~~

After you add a new webhook, Read the Docs will generate a secret key for it
and uses it to generate a hash signature (HMAC-SHA256) for each payload
that is included in the ``X-Hub-Signature`` header of the request.

.. figure:: /_static/images/webhooks-secret.png
:width: 80%
:align: center
:alt: Webhook secret

Webhook secret

We highly recommend using this signature
to verify that the webhook is coming from Read the Docs.
To do so, you can add some custom code on your server,
like this:

.. code-block:: python

import hashlib
import hmac
import os


def verify_signature(payload, request_headers):
"""
Verify that the signature of payload is the same as the one coming from request_headers.
"""
digest = hmac.new(
key=os.environ["WEBHOOK_SECRET"].encode(),
msg=payload.encode(),
digestmod=hashlib.sha256,
)
expected_signature = digest.hexdigest()

return hmac.compare_digest(
request_headers["X-Hub-Signature"].encode(),
expected_signature.encode(),
)

Legacy webhooks
~~~~~~~~~~~~~~~

Webhooks created before the custom payloads functionality was added to Read the Docs
send a payload with the following structure:

.. code-block:: json

{
"name": "Read the Docs",
"slug": "rtd",
"build": {
"id": 6321373,
"commit": "e8dd17a3f1627dd206d721e4be08ae6766fda40",
"state": "finished",
"success": false,
"date": "2017-02-15 20:35:54"
}
}

To migrate to the new webhooks and keep a similar structure,
you can use this payload:

You should now get notified on your webhook when your builds start and finish (failure/success)!
.. code-block:: json

{
"name": "{{project.name}}",
"slug": "{{project.slug}}",
"build": {
"id": "{{build.id}}",
"commit": "{{build.commit}}",
"state": "{{build.state}}",
"success": "{{build.success}}",
"date": "{{build.date}}"
}
}