mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	integrations: Update incoming webhooks overview and walkthrough.
- Updates incoming webhooks overview and walkthrough to be consistent with the `zerver/webhooks/` codebase. - Tweaks the documentation for better readability.
This commit is contained in:
		@@ -1,7 +1,7 @@
 | 
				
			|||||||
# Incoming webhook integrations
 | 
					# Incoming webhook integrations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
An incoming webhook allows a third-party service to push data to Zulip when
 | 
					An incoming webhook allows a third-party service to push data to Zulip when
 | 
				
			||||||
something happens.  There's several ways to do an incoming webhook in
 | 
					something happens. There are several ways to set up an incoming webhook in
 | 
				
			||||||
Zulip:
 | 
					Zulip:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* Use our [REST API](/api/rest) endpoint for [sending
 | 
					* Use our [REST API](/api/rest) endpoint for [sending
 | 
				
			||||||
@@ -11,9 +11,9 @@ Zulip:
 | 
				
			|||||||
* Use one of our supported [integration
 | 
					* Use one of our supported [integration
 | 
				
			||||||
  frameworks](/integrations/meta-integration), such as the
 | 
					  frameworks](/integrations/meta-integration), such as the
 | 
				
			||||||
  [Slack-compatible incoming webhook](/integrations/doc/slack_incoming),
 | 
					  [Slack-compatible incoming webhook](/integrations/doc/slack_incoming),
 | 
				
			||||||
  [Zapier integration](/integrations/docs/zapier), or
 | 
					  [Zapier integration](/integrations/doc/zapier), or
 | 
				
			||||||
  [IFTTT integration](/integrations/doc/ifttt).
 | 
					  [IFTTT integration](/integrations/doc/ifttt).
 | 
				
			||||||
* Adding an incoming webhook integration (detailed on this page),
 | 
					* Implementing an incoming webhook integration (detailed on this page),
 | 
				
			||||||
  where all the logic for formatting the Zulip messages lives in the
 | 
					  where all the logic for formatting the Zulip messages lives in the
 | 
				
			||||||
  Zulip server.  This is how most of [Zulip's official
 | 
					  Zulip server.  This is how most of [Zulip's official
 | 
				
			||||||
  integrations](/integrations/) work, because they enable Zulip to
 | 
					  integrations](/integrations/) work, because they enable Zulip to
 | 
				
			||||||
@@ -22,7 +22,7 @@ Zulip:
 | 
				
			|||||||
  Zulip).
 | 
					  Zulip).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
In an incoming webhook integration, the third-party service's
 | 
					In an incoming webhook integration, the third-party service's
 | 
				
			||||||
"outgoing webhook" feature sends an `HTTP POST`s to a special URL when
 | 
					"outgoing webhook" feature sends an `HTTP POST` to a special URL when
 | 
				
			||||||
it has something for you, and then the Zulip "incoming webhook"
 | 
					it has something for you, and then the Zulip "incoming webhook"
 | 
				
			||||||
integration handles that incoming data to format and send a message in
 | 
					integration handles that incoming data to format and send a message in
 | 
				
			||||||
Zulip.
 | 
					Zulip.
 | 
				
			||||||
@@ -40,18 +40,18 @@ process.
 | 
				
			|||||||
  <https://webhook.site/>, or a similar site to capture an example
 | 
					  <https://webhook.site/>, or a similar site to capture an example
 | 
				
			||||||
  webhook payload from the third-party service. Create a
 | 
					  webhook payload from the third-party service. Create a
 | 
				
			||||||
  `zerver/webhooks/<mywebhook>/fixtures/` directory, and add the
 | 
					  `zerver/webhooks/<mywebhook>/fixtures/` directory, and add the
 | 
				
			||||||
  captured payload as a test fixture.
 | 
					  captured JSON payload as a test fixture.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* Create an `Integration` object, and add it to `WEBHOOK_INTEGRATIONS` in
 | 
					* Create an `Integration` object, and add it to the `WEBHOOK_INTEGRATIONS`
 | 
				
			||||||
  `zerver/lib/integrations.py`. Search for `webhook` in that file to find an
 | 
					  list in `zerver/lib/integrations.py`. Search for `WebhookIntegration` in that
 | 
				
			||||||
  existing one to copy.
 | 
					  file to find an existing one to copy.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* Write a draft webhook handler under `zerver/webhooks/`. There are a lot of
 | 
					* Write a draft webhook handler in `zerver/webhooks/<mywebhook>/view.py`. There
 | 
				
			||||||
  examples in that directory that you can copy. We recommend templating off
 | 
					  are a lot of examples in the `zerver/webhooks/` directory that you can copy.
 | 
				
			||||||
  a short one, like `zendesk`.
 | 
					  We recommend templating from a short one, like `zendesk`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* Add a test for your fixture at `zerver/webhooks/<mywebhook>/tests.py`.
 | 
					* Write a test for your fixture in `zerver/webhooks/<mywebhook>/tests.py`.
 | 
				
			||||||
  Run the tests for your integration like this:
 | 
					  Run the test for your integration like this:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ```
 | 
					    ```
 | 
				
			||||||
    tools/test-backend zerver/webhooks/<mywebhook>/
 | 
					    tools/test-backend zerver/webhooks/<mywebhook>/
 | 
				
			||||||
@@ -64,10 +64,10 @@ process.
 | 
				
			|||||||
  service will make, and add tests for them; usually this part of the
 | 
					  service will make, and add tests for them; usually this part of the
 | 
				
			||||||
  process is pretty fast.
 | 
					  process is pretty fast.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* Document the integration (required for getting it merged into Zulip). You
 | 
					* Document the integration in `zerver/webhooks/<mywebhook>/doc.md`(required for
 | 
				
			||||||
  can template off an existing guide, like
 | 
					  getting it merged into Zulip). You can use existing documentation, like
 | 
				
			||||||
  [this one](https://raw.githubusercontent.com/zulip/zulip/main/zerver/webhooks/github/doc.md).
 | 
					  [this one](https://raw.githubusercontent.com/zulip/zulip/main/zerver/webhooks/github/doc.md),
 | 
				
			||||||
  This should not take more than 15 minutes, even if you don't speak English
 | 
					  as a template. This should not take more than 15 minutes, even if you don't speak English
 | 
				
			||||||
  as a first language (we'll clean up the text before merging).
 | 
					  as a first language (we'll clean up the text before merging).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Hello world walkthrough
 | 
					## Hello world walkthrough
 | 
				
			||||||
@@ -84,9 +84,9 @@ below are for a webhook named `MyWebHook`.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
* `zerver/webhooks/mywebhook/__init__.py`: Empty file that is an obligatory
 | 
					* `zerver/webhooks/mywebhook/__init__.py`: Empty file that is an obligatory
 | 
				
			||||||
   part of every python package.  Remember to `git add` it.
 | 
					   part of every python package.  Remember to `git add` it.
 | 
				
			||||||
* `zerver/webhooks/mywebhook/view.py`: The main webhook integration function
 | 
					* `zerver/webhooks/mywebhook/view.py`: The main webhook integration function,
 | 
				
			||||||
  as well as any needed helper functions.
 | 
					  called `api_mywebhook_webhook`, along with any necessary helper functions.
 | 
				
			||||||
* `zerver/webhooks/mywebhook/fixtures/messagetype.json`: Sample json payload data
 | 
					* `zerver/webhooks/mywebhook/fixtures/message_type.json`: Sample JSON payload data
 | 
				
			||||||
  used by tests. Add one fixture file per type of message supported by your
 | 
					  used by tests. Add one fixture file per type of message supported by your
 | 
				
			||||||
  integration.
 | 
					  integration.
 | 
				
			||||||
* `zerver/webhooks/mywebhook/tests.py`: Tests for your webhook.
 | 
					* `zerver/webhooks/mywebhook/tests.py`: Tests for your webhook.
 | 
				
			||||||
@@ -95,7 +95,7 @@ below are for a webhook named `MyWebHook`.
 | 
				
			|||||||
* `static/images/integrations/logos/mywebhook.svg`: A square logo for the
 | 
					* `static/images/integrations/logos/mywebhook.svg`: A square logo for the
 | 
				
			||||||
  platform/server/product you are integrating. Used on the documentation
 | 
					  platform/server/product you are integrating. Used on the documentation
 | 
				
			||||||
  pages as well as the sender's avatar for messages sent by the integration.
 | 
					  pages as well as the sender's avatar for messages sent by the integration.
 | 
				
			||||||
* `static/images/integrations/mywebhook/001.svg`: A screenshot of a message
 | 
					* `static/images/integrations/mywebhook/001.png`: A screenshot of a message
 | 
				
			||||||
  sent by the integration, used on the documentation page. This can be
 | 
					  sent by the integration, used on the documentation page. This can be
 | 
				
			||||||
  generated by running `tools/generate-integration-docs-screenshot --integration mywebhook`.
 | 
					  generated by running `tools/generate-integration-docs-screenshot --integration mywebhook`.
 | 
				
			||||||
* `static/images/integrations/bot_avatars/mywebhook.png`: A square logo for the
 | 
					* `static/images/integrations/bot_avatars/mywebhook.png`: A square logo for the
 | 
				
			||||||
@@ -125,8 +125,8 @@ below are for a webhook named `MyWebHook`.
 | 
				
			|||||||
## General advice
 | 
					## General advice
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* Consider using our Zulip markup to make the output from your
 | 
					* Consider using our Zulip markup to make the output from your
 | 
				
			||||||
  integration especially attractive or useful (e.g.  emoji, Markdown
 | 
					  integration especially attractive or useful (e.g. emoji, Markdown
 | 
				
			||||||
  emphasis or @-mentions).
 | 
					  emphasis, or @-mentions).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* Use topics effectively to ensure sequential messages about the same
 | 
					* Use topics effectively to ensure sequential messages about the same
 | 
				
			||||||
  thing are threaded together; this makes for much better consumption
 | 
					  thing are threaded together; this makes for much better consumption
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -69,7 +69,7 @@ integration uses.
 | 
				
			|||||||
## Step 1: Initialize your webhook python package
 | 
					## Step 1: Initialize your webhook python package
 | 
				
			||||||
 | 
					
 | 
				
			||||||
In the `zerver/webhooks/` directory, create new subdirectory that will
 | 
					In the `zerver/webhooks/` directory, create new subdirectory that will
 | 
				
			||||||
contain all of corresponding code.  In our example it will be
 | 
					contain all of the corresponding code.  In our example it will be
 | 
				
			||||||
`helloworld`. The new directory will be a python package, so you have
 | 
					`helloworld`. The new directory will be a python package, so you have
 | 
				
			||||||
to create an empty `__init__.py` file in that directory via e.g.
 | 
					to create an empty `__init__.py` file in that directory via e.g.
 | 
				
			||||||
`touch zerver/webhooks/helloworld/__init__.py`.
 | 
					`touch zerver/webhooks/helloworld/__init__.py`.
 | 
				
			||||||
@@ -82,25 +82,24 @@ python file, `zerver/webhooks/mywebhook/view.py`.
 | 
				
			|||||||
The Hello World integration is in `zerver/webhooks/helloworld/view.py`:
 | 
					The Hello World integration is in `zerver/webhooks/helloworld/view.py`:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```python
 | 
					```python
 | 
				
			||||||
from typing import Any, Dict, Sequence
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.http import HttpRequest, HttpResponse
 | 
					from django.http import HttpRequest, HttpResponse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from zerver.decorator import webhook_view
 | 
					from zerver.decorator import webhook_view
 | 
				
			||||||
from zerver.lib.request import REQ, has_request_variables
 | 
					 | 
				
			||||||
from zerver.lib.response import json_success
 | 
					from zerver.lib.response import json_success
 | 
				
			||||||
 | 
					from zerver.lib.typed_endpoint import JsonBodyPayload, typed_endpoint
 | 
				
			||||||
 | 
					from zerver.lib.validator import WildValue, check_string
 | 
				
			||||||
from zerver.lib.webhooks.common import check_send_webhook_message
 | 
					from zerver.lib.webhooks.common import check_send_webhook_message
 | 
				
			||||||
from zerver.models import UserProfile
 | 
					from zerver.models import UserProfile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@webhook_view("HelloWorld")
 | 
					@webhook_view("HelloWorld")
 | 
				
			||||||
@has_request_variables
 | 
					@typed_endpoint
 | 
				
			||||||
def api_helloworld_webhook(
 | 
					def api_helloworld_webhook(
 | 
				
			||||||
    request: HttpRequest,
 | 
					    request: HttpRequest,
 | 
				
			||||||
    user_profile: UserProfile,
 | 
					    user_profile: UserProfile,
 | 
				
			||||||
    payload: Dict[str, Sequence[Dict[str, Any]]] = REQ(argument_type="body"),
 | 
					    *,
 | 
				
			||||||
 | 
					    payload: JsonBodyPayload[WildValue],
 | 
				
			||||||
) -> HttpResponse:
 | 
					) -> HttpResponse:
 | 
				
			||||||
 | 
					 | 
				
			||||||
    # construct the body of the message
 | 
					    # construct the body of the message
 | 
				
			||||||
    body = "Hello! I am happy to be here! :smile:"
 | 
					    body = "Hello! I am happy to be here! :smile:"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -108,7 +107,10 @@ def api_helloworld_webhook(
 | 
				
			|||||||
    body_template = (
 | 
					    body_template = (
 | 
				
			||||||
        "\nThe Wikipedia featured article for today is **[{featured_title}]({featured_url})**"
 | 
					        "\nThe Wikipedia featured article for today is **[{featured_title}]({featured_url})**"
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    body += body_template.format(**payload)
 | 
					    body += body_template.format(
 | 
				
			||||||
 | 
					        featured_title=payload["featured_title"].tame(check_string),
 | 
				
			||||||
 | 
					        featured_url=payload["featured_url"].tame(check_string),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    topic = "Hello World"
 | 
					    topic = "Hello World"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -120,9 +122,8 @@ def api_helloworld_webhook(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
The above code imports the required functions and defines the main webhook
 | 
					The above code imports the required functions and defines the main webhook
 | 
				
			||||||
function `api_helloworld_webhook`, decorating it with `webhook_view` and
 | 
					function `api_helloworld_webhook`, decorating it with `webhook_view` and
 | 
				
			||||||
`has_request_variables`. The `has_request_variables` decorator allows you to
 | 
					`typed_endpoint`. The `typed_endpoint` decorator allows you to
 | 
				
			||||||
access request variables with `REQ()`. You can find more about `REQ` and request
 | 
					access request variables with `JsonBodyPayload()`. You can find more about `JsonBodyPayload` and request variables in [Writing views](
 | 
				
			||||||
variables in [Writing views](
 | 
					 | 
				
			||||||
https://zulip.readthedocs.io/en/latest/tutorials/writing-views.html#request-variables).
 | 
					https://zulip.readthedocs.io/en/latest/tutorials/writing-views.html#request-variables).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
You must pass the name of your integration to the
 | 
					You must pass the name of your integration to the
 | 
				
			||||||
@@ -191,7 +192,7 @@ WEBHOOK_INTEGRATIONS: List[WebhookIntegration] = [
 | 
				
			|||||||
And you'll find the entry for Hello World:
 | 
					And you'll find the entry for Hello World:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```python
 | 
					```python
 | 
				
			||||||
  WebhookIntegration('helloworld', ['misc'], display_name='Hello World'),
 | 
					  WebhookIntegration("helloworld", ["misc"], display_name="Hello World"),
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This tells the Zulip API to call the `api_helloworld_webhook` function in
 | 
					This tells the Zulip API to call the `api_helloworld_webhook` function in
 | 
				
			||||||
@@ -199,7 +200,7 @@ This tells the Zulip API to call the `api_helloworld_webhook` function in
 | 
				
			|||||||
`/api/v1/external/helloworld`.
 | 
					`/api/v1/external/helloworld`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This line also tells Zulip to generate an entry for Hello World on the Zulip
 | 
					This line also tells Zulip to generate an entry for Hello World on the Zulip
 | 
				
			||||||
integrations page using `static/images/integrations/logos/helloworld.png` as its
 | 
					integrations page using `static/images/integrations/logos/helloworld.svg` as its
 | 
				
			||||||
icon. The second positional argument defines a list of categories for the
 | 
					icon. The second positional argument defines a list of categories for the
 | 
				
			||||||
integration.
 | 
					integration.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -323,19 +324,23 @@ class `HelloWorldHookTests`:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
```python
 | 
					```python
 | 
				
			||||||
class HelloWorldHookTests(WebhookTestCase):
 | 
					class HelloWorldHookTests(WebhookTestCase):
 | 
				
			||||||
    STREAM_NAME = 'test'
 | 
					    STREAM_NAME = "test"
 | 
				
			||||||
    URL_TEMPLATE = "/api/v1/external/helloworld?&api_key={api_key}"
 | 
					    URL_TEMPLATE = "/api/v1/external/helloworld?&api_key={api_key}&stream={stream}"
 | 
				
			||||||
    WEBHOOK_DIR_NAME = 'helloworld'
 | 
					    DIRECT_MESSAGE_URL_TEMPLATE = "/api/v1/external/helloworld?&api_key={api_key}"
 | 
				
			||||||
 | 
					    WEBHOOK_DIR_NAME = "helloworld"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Note: Include a test function per each distinct message condition your integration supports
 | 
					    # Note: Include a test function per each distinct message condition your integration supports
 | 
				
			||||||
    def test_hello_message(self) -> None:
 | 
					    def test_hello_message(self) -> None:
 | 
				
			||||||
        expected_topic = "Hello World";
 | 
					        expected_topic = "Hello World"
 | 
				
			||||||
        expected_message = "Hello! I am happy to be here! :smile: \nThe Wikipedia featured article for today is **[Marilyn Monroe](https://en.wikipedia.org/wiki/Marilyn_Monroe)**";
 | 
					        expected_message = "Hello! I am happy to be here! :smile:\nThe Wikipedia featured article for today is **[Marilyn Monroe](https://en.wikipedia.org/wiki/Marilyn_Monroe)**"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # use fixture named helloworld_hello
 | 
					        # use fixture named helloworld_hello
 | 
				
			||||||
        self.check_webhook('hello', expected_topic, expected_message,
 | 
					        self.check_webhook(
 | 
				
			||||||
                           content_type="application/x-www-form-urlencoded")
 | 
					            "hello",
 | 
				
			||||||
 | 
					            expected_topic,
 | 
				
			||||||
 | 
					            expected_message,
 | 
				
			||||||
 | 
					            content_type="application/x-www-form-urlencoded",
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
In the above example, `STREAM_NAME`, `URL_TEMPLATE`, and `WEBHOOK_DIR_NAME` refer
 | 
					In the above example, `STREAM_NAME`, `URL_TEMPLATE`, and `WEBHOOK_DIR_NAME` refer
 | 
				
			||||||
@@ -363,12 +368,16 @@ class called something like `test_goodbye_message`:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
```python
 | 
					```python
 | 
				
			||||||
    def test_goodbye_message(self) -> None:
 | 
					    def test_goodbye_message(self) -> None:
 | 
				
			||||||
        expected_topic = "Hello World";
 | 
					        expected_topic = "Hello World"
 | 
				
			||||||
        expected_message = "Hello! I am happy to be here! :smile:\nThe Wikipedia featured article for today is **[Goodbye](https://en.wikipedia.org/wiki/Goodbye)**";
 | 
					        expected_message = "Hello! I am happy to be here! :smile:\nThe Wikipedia featured article for today is **[Goodbye](https://en.wikipedia.org/wiki/Goodbye)**"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # use fixture named helloworld_goodbye
 | 
					        # use fixture named helloworld_goodbye
 | 
				
			||||||
        self.check_webhook('goodbye', expected_topic, expected_message,
 | 
					        self.check_webhook(
 | 
				
			||||||
                           content_type="application/x-www-form-urlencoded")
 | 
					            "goodbye",
 | 
				
			||||||
 | 
					            expected_topic,
 | 
				
			||||||
 | 
					            expected_message,
 | 
				
			||||||
 | 
					            content_type="application/x-www-form-urlencoded",
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
As well as a new fixture `goodbye.json` in
 | 
					As well as a new fixture `goodbye.json` in
 | 
				
			||||||
@@ -491,7 +500,7 @@ request:
 | 
				
			|||||||
5. Submit a pull request to zulip/zulip.
 | 
					5. Submit a pull request to zulip/zulip.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
If you would like feedback on your integration as you go, feel free to post a
 | 
					If you would like feedback on your integration as you go, feel free to post a
 | 
				
			||||||
message on the [public Zulip instance](https://chat.zulip.org/#narrow/stream/bots).
 | 
					message on the [public Zulip instance](https://chat.zulip.org/#narrow/stream/integrations).
 | 
				
			||||||
You can also create a [draft pull request](
 | 
					You can also create a [draft pull request](
 | 
				
			||||||
https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests#draft-pull-requests) while you
 | 
					https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests#draft-pull-requests) while you
 | 
				
			||||||
are still working on your integration. See the
 | 
					are still working on your integration. See the
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user