Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
542 lines
22 KiB
Markdown
542 lines
22 KiB
Markdown
# Writing an incoming webhook
|
|
|
|
This guide goes through the process of creating a simple incoming webhook
|
|
integration, using the [Zulip Hello World](https://zulip.com/integrations/helloworld)
|
|
integration as an example.
|
|
|
|
The example integration receives an HTTP POST request with JSON data
|
|
about Wikipedia's featured article of the day from a fictional
|
|
third-party service, formats that data into a "hello" message, and sends
|
|
that message to the specified conversation in Zulip.
|
|
|
|
Before you get started, you'll want to set up the [Zulip development
|
|
environment](../development/overview.md).
|
|
|
|
## Step 0: Create fixtures
|
|
|
|
The first step in creating an incoming webhook integration is to examine
|
|
the data that the third-party service you're working with will send to
|
|
Zulip.
|
|
|
|
Use [Zulip's JSON integration](https://zulip.com/integrations/json),
|
|
<https://webhook.site/>, or a similar tool to capture outgoing webhook
|
|
payload(s) from the service. Examining this data allows you to:
|
|
|
|
- Determine how you will structure your webhook integration code,
|
|
including what event types your integration should support and how.
|
|
- Create fixtures. A test fixture is a small file containing test data
|
|
for one kind of event or scenario. Fixtures enable the testing of
|
|
webhook integration code without the need to actually contact the
|
|
service being integrated.
|
|
|
|
You'll want to write a test for each distinct event type your incoming
|
|
webhook integration supports, and you'll need fixtures for these
|
|
tests. Depending on the type of data the third-party service sends,
|
|
your fixtures may contain JSON, URL encoded text, or some other kind
|
|
of data. [Step 5: Create automated tests](#step-5-create-automated-tests)
|
|
and [our testing documentation](../testing/testing.md) have further
|
|
details about writing tests and using test fixtures.
|
|
|
|
Because the Zulip Hello World is very simple and does only one thing,
|
|
it requires only one fixture,
|
|
`zerver/webhooks/helloworld/fixtures/hello.json`.
|
|
|
|
```json
|
|
{
|
|
"featured_title":"Marilyn Monroe",
|
|
"featured_url":"https://en.wikipedia.org/wiki/Marilyn_Monroe"
|
|
}
|
|
```
|
|
|
|
### Fixture guidelines
|
|
|
|
Fixtures for a real third-party service must always be actual
|
|
captured payloads, or payloads from the service's official API
|
|
documentation, never hand-written, invented, or AI-generated. A
|
|
fabricated payload does not accurately reflect what the service
|
|
really sends, so an integration developed against one may not work
|
|
with real events.
|
|
|
|
Collect only the fixtures your integration's tests need. If you want
|
|
to test how the integration handles different values of a URL query
|
|
parameter, prefer writing tests that reuse one fixture, instead of
|
|
capturing several fixtures that are almost identical.
|
|
|
|
When setting up the integration, make sure not to use any personal
|
|
details that you would not want to be part of the fixtures. Use
|
|
fixtures exactly as you captured them; do not edit their contents. An
|
|
edited fixture no longer records what the service really sends, and
|
|
subtle mistakes are easy to introduce. If you need a variation,
|
|
capture a new fixture instead.
|
|
|
|
If you absolutely must remove personal details from a captured
|
|
payload, commit the captured fixture locally first, then replace the
|
|
personal details with equally realistic strings and amend the commit.
|
|
This makes the scrub a reviewable diff, and the original data never
|
|
reaches the public history.
|
|
|
|
Fixture data appears in the messages and topics that your integration
|
|
produces, and in the auto-generated example screenshots in the
|
|
integration's documentation, so when setting up the third-party
|
|
service, use realistic names for projects, tasks, and other entities.
|
|
Where you can, use the same names and email addresses as the shared
|
|
example content in `zerver/webhooks/fixtureless_integrations.py`, so
|
|
that the example screenshots are consistent across integrations.
|
|
|
|
Fixture files are named descriptively, in snake_case, after the event
|
|
(and variant) they capture. The Hello World integration's fixture is
|
|
`hello.json`; for a richer service, a fixture might be named
|
|
something like `issue_created_with_assignee.json`.
|
|
|
|
For integrations that send the event type or other important
|
|
information as part of the HTTP header, the value of the header is
|
|
encoded in the first part of the fixture's filename, separated from
|
|
the rest by a double underscore. Refer to
|
|
[the section on custom HTTP headers](incoming-webhooks-reference.md#custom-http-headers)
|
|
in the reference guide for more details.
|
|
|
|
Once captured, fixtures rarely need to be updated: as long as the
|
|
fields your integration reads are still what the service sends, there
|
|
is no need to update fixtures just because the service has added new
|
|
fields over the years.
|
|
|
|
## Step 1: Initialize the python package
|
|
|
|
In the `zerver/webhooks/` directory, create new subdirectory for the
|
|
incoming webhook integration. In our example, it is `helloworld`. The new
|
|
directory will be a python package, so you should also create an empty
|
|
`__init__.py` file in that directory via, for
|
|
example, `touch zerver/webhooks/helloworld/__init__.py`.
|
|
|
|
## Step 2: Write the main webhook code
|
|
|
|
The majority of the code for your new integration will be in a single
|
|
python file named `view.py`. The Zulip Hello World integration code is
|
|
found in `zerver/webhooks/helloworld/view.py`:
|
|
|
|
```python
|
|
from django.http import HttpRequest, HttpResponse
|
|
|
|
from zerver.decorator import webhook_view
|
|
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.models import UserProfile
|
|
|
|
|
|
@webhook_view("HelloWorld")
|
|
@typed_endpoint
|
|
def api_helloworld_webhook(
|
|
request: HttpRequest,
|
|
user_profile: UserProfile,
|
|
*,
|
|
payload: JsonBodyPayload[WildValue],
|
|
) -> HttpResponse:
|
|
# construct the body of the message
|
|
body = "Hello! I am happy to be here! :smile:"
|
|
|
|
# try to add the Wikipedia article of the day
|
|
body_template = (
|
|
"\nThe Wikipedia featured article for today is **[{featured_title}]({featured_url})**"
|
|
)
|
|
body += body_template.format(
|
|
featured_title=payload["featured_title"].tame(check_string),
|
|
featured_url=payload["featured_url"].tame(check_string),
|
|
)
|
|
|
|
topic_name = "Hello World"
|
|
|
|
# send the message
|
|
check_send_webhook_message(request, user_profile, topic_name, body)
|
|
|
|
return json_success(request)
|
|
```
|
|
|
|
The above code imports the required functions and defines the main
|
|
webhook handler, `api_helloworld_webhook`, decorating it with
|
|
`webhook_view` and `typed_endpoint`.
|
|
|
|
### Decorators
|
|
|
|
The `typed_endpoint` decorator allows the integration to access request
|
|
variables with `JsonBodyPayload[WildValue]`. You can find more about
|
|
`JsonBodyPayload` and request variables in [the tutorial on writing
|
|
views](../tutorials/writing-views.md#request-variables).
|
|
|
|
You must pass the name of the integration to the `webhook_view` decorator.
|
|
That name will be used to describe the integration in Zulip's analytics
|
|
pages. In the example, the integration's name is `HelloWorld`. To be
|
|
consistent with other incoming webhook integrations, you should use the
|
|
name of the third-party service in camel case, spelled as the service
|
|
spells its own name, with the exception that the first letter should be
|
|
upper case even if it's lower case in the service's name/brand.
|
|
|
|
The `webhook_view` decorator indicates that the third-party service will
|
|
send the authorization as an API key in the query parameters. If the
|
|
third-party service uses HTTP basic authentication, you would instead use
|
|
the `authenticated_rest_api_view` decorator.
|
|
|
|
### Main webhook handler
|
|
|
|
The webhook function should be named as in the example above,
|
|
`api_helloworld_webhook`, replacing `helloworld` with the name of the
|
|
integration, in lower case.
|
|
|
|
At minimum, the webhook function must accept `request` (Django
|
|
[HttpRequest](https://docs.djangoproject.com/en/5.0/ref/request-response/#django.http.HttpRequest)
|
|
object), and `user_profile` (Zulip's user object). You can also define
|
|
additional parameters using the `typed_endpoint` decorator.
|
|
|
|
In the Zulip Hello World example above, there is also a `payload`
|
|
parameter, which is populated from the body of the HTTP POST request.
|
|
|
|
The main function defines the body of the message using [Zulip's message
|
|
formatting](https://zulip.com/api/message-formatting). For example,
|
|
`:smile:` in the initial sentence of the message body indicates an emoji.
|
|
The data from the JSON payload is used for the link to the Wikipedia
|
|
featured article of the day.
|
|
|
|
Sometimes, it may occur that a JSON payload does not contain all the
|
|
required keys that an integration checks for. In such a case, any
|
|
`KeyError` that is thrown is handled by the Zulip server's backend, which
|
|
will create an appropriate response.
|
|
|
|
A default topic name should be defined for cases when the [webhook
|
|
URL](incoming-webhooks-overview.md#url-specification) specifies a
|
|
channel recipient, via the `stream` query parameter, but does not specify
|
|
a topic.
|
|
|
|
The formatted message is sent with `check_send_webhook_message`, which
|
|
will validate the message and do the following:
|
|
|
|
- Send a channel message if the `stream` query parameter is specified in
|
|
the [webhook URL](incoming-webhooks-overview.md#url-specification).
|
|
- Send a direct message to the owner of the webhook bot if the `stream`
|
|
query parameter isn't specified.
|
|
|
|
Finally, a response is sent with a 200 HTTP status with a JSON format
|
|
success message via `json_success(request)`.
|
|
|
|
## Step 3: Create an API endpoint for the webhook
|
|
|
|
In order for an incoming webhook integration to be externally available,
|
|
it must be mapped to a URL. This "registration" is done in
|
|
`zerver/lib/integrations.py`.
|
|
|
|
Look for the lines beginning with:
|
|
|
|
```python
|
|
INCOMING_WEBHOOK_INTEGRATIONS: List[IncomingWebhookIntegration] = [
|
|
```
|
|
|
|
In that list, find the entry for the example Hello World integration:
|
|
|
|
```python
|
|
IncomingWebhookIntegration(
|
|
"helloworld", ["misc"], [WebhookScreenshotConfig("hello.json")], display_name="Hello World"
|
|
),
|
|
```
|
|
|
|
This tells the Zulip API to call the `api_helloworld_webhook` function in
|
|
`zerver/webhooks/helloworld/view.py` when it receives a request at
|
|
`/api/v1/external/helloworld`.
|
|
|
|
This entry also adds the example Hello World integration to Zulip's main
|
|
[integrations documentation](https://zulip.com/integrations/). The second
|
|
argument defines the categories that include the integration in said
|
|
documentation. The third argument defines the configuration for
|
|
generating (and updating) the example screenshot included in the
|
|
integration's documentation page. And the final argument defines the
|
|
display name for the integration in the documentation. [Step 6: Create
|
|
documentation](#step-6-document-the-integration) has more details about
|
|
creating end-user documentation.
|
|
|
|
In cases where an incoming webhook integration supports optional URL
|
|
parameters, use the `url_options` argument described in
|
|
[the custom URL parameters section](incoming-webhooks-reference.md#custom-url-query-parameters)
|
|
of the reference guide to create the corresponding UI element in the modal
|
|
for [generating an integration URL](https://zulip.com/help/generate-integration-url).
|
|
|
|
## Step 4: Manually test the webhook
|
|
|
|
You'll want to manually test your webhook implementation in the Zulip
|
|
development environment.
|
|
|
|
### Command line tools
|
|
|
|
There are two command line tools you can use. For either one of them,
|
|
you'll need to [get an API key](https://zulip.com/api/api-keys) for
|
|
an [incoming webhook bot](https://zulip.com/help/add-a-bot-or-integration)
|
|
in your Zulip development environment. Replace `<api_key>` with the
|
|
bot's API key in the examples below. This is how the server knows that
|
|
the request was made by an authorized user.
|
|
|
|
#### curl
|
|
|
|
Using curl, with the Zulip development server running in a separate
|
|
console window:
|
|
|
|
```bash
|
|
curl -X POST -H "Content-Type: application/json" -d '{ "featured_title":"Marilyn Monroe", "featured_url":"https://en.wikipedia.org/wiki/Marilyn_Monroe" }' http://localhost:9991/api/v1/external/helloworld\?api_key\=<api_key>
|
|
```
|
|
|
|
After running the above command, you should see something similar to:
|
|
|
|
```json
|
|
{"msg":"","result":"success"}
|
|
```
|
|
|
|
And if you log-in to the web app as the bot's owner, you should see a new
|
|
direct message from the bot.
|
|
|
|
#### `send_webhook_fixture_message` management command
|
|
|
|
Using `manage.py` from within the Zulip development environment:
|
|
|
|
```console
|
|
(zulip-server) vagrant@vagrant:/srv/zulip$
|
|
./manage.py send_webhook_fixture_message \
|
|
--fixture=zerver/webhooks/helloworld/fixtures/hello.json \
|
|
'--url=http://localhost:9991/api/v1/external/helloworld?api_key=<api_key>'
|
|
```
|
|
|
|
After running the above command, you should see something similar to:
|
|
|
|
```
|
|
2016-07-07 15:06:59,187 INFO 127.0.0.1 POST 200 143ms (mem: 6ms/13) (md: 43ms/1) (db: 20ms/9q) (+start: 147ms) /api/v1/external/helloworld (helloworld-bot@zulip.com via ZulipHelloWorldWebhook)
|
|
```
|
|
|
|
Some webhooks require custom HTTP headers, which can be passed using
|
|
`./manage.py send_webhook_fixture_message --custom-headers`. For
|
|
example:
|
|
|
|
--custom-headers='{"X-Custom-Header": "value"}'
|
|
|
|
The format is a JSON object, so make sure that the header names do
|
|
not contain any spaces in them and that you use the precise quoting
|
|
approach shown above.
|
|
|
|
For more information about `manage.py` command-line tools in Zulip, see
|
|
the [management commands](../production/management-commands.md)
|
|
documentation.
|
|
|
|
### Integrations dev panel
|
|
|
|
This is a GUI tool that you can use to test your webhook in the Zulip
|
|
development environment.
|
|
|
|
1. Run `./tools/run-dev`, and use a web browser to go to
|
|
`http://localhost:9991/devtools/integrations/`.
|
|
1. Select a bot, an integration and a fixture from the dropdown menus.
|
|
1. Click **Send**. The webhook notification message will be sent to the
|
|
default Zulip organization in your development environment.
|
|
|
|
By having Zulip open in one browser tab and this tool in another, you can
|
|
quickly tweak your webhook code and send sample messages for different
|
|
test fixtures.
|
|
|
|
Custom HTTP headers must be entered as a JSON object, if you want to
|
|
use any. Feel free to use 4-spaces as tabs for indentation if you'd like.
|
|
|
|
### Testing with the third-party service
|
|
|
|
:::{warning}
|
|
The development environment is not hardened against hostile traffic. Only
|
|
expose it while you are actively testing.
|
|
:::
|
|
|
|
To have the third-party service deliver the webhooks itself, it needs to
|
|
be able to reach your development server over the internet.
|
|
|
|
You don't need to reconfigure the development server for this. You can
|
|
use a tunneling tool, such as [UltraHook](https://www.ultrahook.com/) or
|
|
[localtunnel](https://github.com/localtunnel/localtunnel), to expose
|
|
`http://localhost:9991` at a temporary public URL. When you configure the
|
|
webhook in the third-party service, use that temporary public URL with the
|
|
path for your incoming webhook integration, e.g.,
|
|
`<temporary-public-url>/api/v1/external/helloworld?api_key=<api_key>`.
|
|
|
|
End-to-end testing with live data can help you spot why something isn't
|
|
working, or whether the service is using [custom HTTP
|
|
headers](incoming-webhooks-reference.md#custom-http-headers) that your
|
|
integration needs to handle.
|
|
|
|
If you instead need the development server to be reachable from another
|
|
device on your own network, the mobile project's guide to [running the app
|
|
against a development
|
|
server](https://github.com/zulip/zulip-flutter/blob/main/docs/howto/dev-server.md)
|
|
covers configuring the server to listen on all network interfaces.
|
|
|
|
## Step 5: Create automated tests
|
|
|
|
Every incoming webhook integration should have a corresponding `tests.py`
|
|
file. The Hello World integration's tests are in
|
|
`zerver/webhooks/helloworld/tests.py`.
|
|
|
|
The test class should be named `<WebhookName>HookTests`, and it should
|
|
inherit the `WebhookTestCase` base class. For Zulip's Hello World
|
|
integration, the test class is `HelloWorldHookTests`.
|
|
|
|
```python
|
|
class HelloWorldHookTests(WebhookTestCase):
|
|
DIRECT_MESSAGE_URL_TEMPLATE = "/api/v1/external/helloworld?&api_key={api_key}"
|
|
|
|
# Note: Include a test function per each distinct message condition your integration supports
|
|
def test_hello_message(self) -> None:
|
|
expected_topic_name = "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)**"
|
|
|
|
# use fixture named hello.json
|
|
self.check_webhook(
|
|
"hello",
|
|
expected_topic_name,
|
|
expected_message,
|
|
content_type="application/x-www-form-urlencoded",
|
|
)
|
|
|
|
def test_pm_to_bot_owner(self) -> None:
|
|
# Note that this is really just a test for check_send_webhook_message
|
|
self.url_template = self.DIRECT_MESSAGE_URL_TEMPLATE
|
|
self.url = self.build_webhook_url()
|
|
expected_message = "Hello! I am happy to be here! :smile:\nThe Wikipedia featured article for today is **[Goodbye](https://en.wikipedia.org/wiki/Goodbye)**"
|
|
|
|
self.send_and_test_private_message(
|
|
"goodbye",
|
|
expected_message=expected_message,
|
|
content_type="application/x-www-form-urlencoded",
|
|
)
|
|
```
|
|
|
|
When writing tests, you'll want to include one test function for each
|
|
distinct event type and condition that the integration supports.
|
|
|
|
If, for example, we added support for sending a goodbye message to the
|
|
Hello World webhook, then we would add another test function to
|
|
`HelloWorldHookTests` class called something like `test_goodbye_message`:
|
|
|
|
```python
|
|
def test_goodbye_message(self) -> None:
|
|
expected_topic_name = "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)**"
|
|
|
|
# use fixture named goodbye.json
|
|
self.check_webhook(
|
|
"goodbye",
|
|
expected_topic_name,
|
|
expected_message,
|
|
content_type="application/x-www-form-urlencoded",
|
|
)
|
|
```
|
|
|
|
As well as a new fixture `goodbye.json` in
|
|
`zerver/webhooks/helloworld/fixtures/`:
|
|
|
|
```json
|
|
{
|
|
"featured_title":"Goodbye",
|
|
"featured_url":"https://en.wikipedia.org/wiki/Goodbye"
|
|
}
|
|
```
|
|
|
|
Also, consider if the integration should have negative tests, i.e., tests
|
|
where the data from the test fixture should result in an error. For
|
|
details, see [negative tests](incoming-webhooks-reference.md#negative-tests).
|
|
|
|
Once you have written some tests, you can run just these new tests from
|
|
within the Zulip development environment with this command:
|
|
|
|
```console
|
|
./tools/test-backend zerver/webhooks/helloworld
|
|
```
|
|
|
|
You will see some script output in the console. And if all the tests have
|
|
passed, you will see:
|
|
|
|
```console
|
|
Running zerver.webhooks.helloworld.tests.HelloWorldHookTests.test_goodbye_message
|
|
Running zerver.webhooks.helloworld.tests.HelloWorldHookTests.test_hello_message
|
|
DONE!
|
|
```
|
|
|
|
## Step 6: Document the integration
|
|
|
|
In order for an incoming webhook integration to be used, there needs to
|
|
be documentation for end-users. All incoming webhooks are included in
|
|
Zulip's main [integrations documentation](https://zulip.com/integrations).
|
|
|
|
There are two parts to the user-facing integrations documentation.
|
|
|
|
The first is the lozenge in the grid of integrations, which shows the
|
|
integration logo and name, and is a link to the detailed documentation
|
|
for each integration.
|
|
|
|
Each integration needs a square, svg logo of the third-party service that
|
|
has been integrated with Zulip, which is saved in the
|
|
`static/images/integrations/logos` directory. The Zulip Hello World logo
|
|
can be found at `static/images/integrations/logos/helloworld.svg`.
|
|
|
|
The lozenge is generated automatically once the integration is added to
|
|
`INCOMING_WEBHOOK_INTEGRATIONS` in `zerver/lib/integrations.py`, which
|
|
supports some customization via options to the `IncomingWebhookIntegration`
|
|
class.
|
|
|
|
Second, is the detailed documentation content for the integration, which
|
|
is in a file named `doc.md` in the integration's directory. The Zulip
|
|
Hello World documentation can be found at `zerver/webhooks/helloworld/doc.md`.
|
|
|
|
Zulip has a macro-based Markdown/Jinja2 framework that includes macros for
|
|
common instructions in Zulip's webhooks/integrations documentation (e.g.,
|
|
`{!create-an-incoming-webhook.md!}` and `{!congrats.md!}`).
|
|
|
|
See [the guide on documenting an integration](../documentation/integrations.md)
|
|
for further details, including how to easily create the example
|
|
screenshot for the documentation page.
|
|
|
|
## Step 7: Prepare a pull request
|
|
|
|
When you have finished your incoming webhook integration, follow these
|
|
guidelines before pushing the code to your Zulip fork and submitting a
|
|
pull request to [zulip/zulip](https://github.com/zulip/zulip):
|
|
|
|
- Run tests, including linters, to ensure you have addressed any issues
|
|
they report. See [testing](../testing/testing.md) and
|
|
[linters](../testing/linters.md) for details.
|
|
- Read through [code styles and conventions](../contributing/code-style.md)
|
|
and review your code to double-check that you've followed Zulip's
|
|
guidelines.
|
|
- Take a look at your Git history to ensure your commits have been clear
|
|
and logical (see [commit discipline](../contributing/commit-discipline.md)
|
|
for tips). If not, consider revising them with `git rebase --interactive`.
|
|
For most incoming webhook integrations, you'll want to submit a pull
|
|
request with a single commit that has a good, clear commit message.
|
|
|
|
If you would like feedback on your integration as you go, feel free to
|
|
post a message in the [Zulip development community][czo-integrations-channel]
|
|
You can also create a [draft pull request][github-draft-pull-requests]
|
|
while you are still working on your integration. See the [git
|
|
guide](../git/pull-requests.md#create-a-pull-request) for more on Zulip's
|
|
pull request process.
|
|
|
|
[czo-integrations-channel]: https://chat.zulip.org/#narrow/channel/integrations
|
|
[github-draft-pull-requests]: 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
|
|
|
|
## Advanced topics
|
|
|
|
More complex implementation or testing needs may require additional code,
|
|
beyond what the standard helper functions provide. This section discusses
|
|
some of these situations.
|
|
|
|
### Handling unexpected webhook event types
|
|
|
|
Many third-party services have dozens of different event types. In some
|
|
cases, we may choose to explicitly ignore specific events. In other cases,
|
|
there may be events that are new or events that we don't know about. In
|
|
such cases, we recommend raising `UnsupportedWebhookEventTypeError`
|
|
(found in `zerver/lib/exceptions.py`), with a string describing the
|
|
unsupported event type, like so:
|
|
|
|
```
|
|
raise UnsupportedWebhookEventTypeError(event_type)
|
|
```
|