mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 14:03:30 +00:00 
			
		
		
		
	docs: Move integration docs to /api.
This commit is contained in:
		@@ -1,272 +0,0 @@
 | 
			
		||||
# Documenting an integration
 | 
			
		||||
 | 
			
		||||
Every Zulip integration must be documented in
 | 
			
		||||
`zerver/webhooks/mywebhook/doc.md` (or
 | 
			
		||||
`templates/zerver/integrations/<integration_name>.md`, for non-webhook
 | 
			
		||||
integrations).
 | 
			
		||||
 | 
			
		||||
Usually, this involves a few steps:
 | 
			
		||||
 | 
			
		||||
* Add text explaining all of the steps required to setup the
 | 
			
		||||
  integration, including what URLs to use, etc.  If there are any
 | 
			
		||||
  screens in the product involved, take a few screenshots with the
 | 
			
		||||
  input fields filled out with sample values in order to make the
 | 
			
		||||
  instructions really easy to follow.  For the screenshots, use a bot
 | 
			
		||||
  with a name like "GitHub Bot", and an email address for the bot like
 | 
			
		||||
  `github-bot@zulip.example.com`. Zulip's pre-defined Markdown macros
 | 
			
		||||
  can be used for some of these steps. See
 | 
			
		||||
  [Markdown macros](#markdown-macros) for further details.
 | 
			
		||||
 | 
			
		||||
* Make sure you've added your integration to
 | 
			
		||||
  `zerver/lib/integrations.py`; this results in your integration
 | 
			
		||||
  appearing on the `/integrations` page.
 | 
			
		||||
 | 
			
		||||
* You'll need to add a SVG graphic
 | 
			
		||||
  of your integration's logo under the
 | 
			
		||||
  `static/images/integrations/logos/<name>.svg`, where `<name>` is the
 | 
			
		||||
  name of the integration, all in lower case; you can usually find them in the
 | 
			
		||||
  product branding or press page. Make sure to optimize the SVG graphic by
 | 
			
		||||
  running `svgo -f path-to-file`.
 | 
			
		||||
 | 
			
		||||
  If you cannot find a SVG graphic of the logo, please find and include a PNG
 | 
			
		||||
  image of the logo instead.
 | 
			
		||||
 | 
			
		||||
* Finally, generate a message sent by the integration and take a
 | 
			
		||||
  screenshot of the message to provide an example message in the
 | 
			
		||||
  documentation. If your new integration is a webhook integration,
 | 
			
		||||
  you can generate such a message from your test fixtures
 | 
			
		||||
  using `send_webhook_fixture_message`:
 | 
			
		||||
 | 
			
		||||
  ```
 | 
			
		||||
  ./manage.py send_webhook_fixture_message \
 | 
			
		||||
       --fixture=zerver/webhooks/pingdom/fixtures/imap_down_to_up.json \
 | 
			
		||||
       '--url=/api/v1/external/pingdom?stream=stream_name&api_key=api_key'
 | 
			
		||||
  ```
 | 
			
		||||
 | 
			
		||||
  When generating the screenshot of a sample message, give your test
 | 
			
		||||
  bot a nice name like "GitHub Bot", use the project's logo as the
 | 
			
		||||
  bot's avatar, and take the screenshots showing the stream/topic bar
 | 
			
		||||
  for the message, not just the message body.
 | 
			
		||||
 | 
			
		||||
## Markdown macros
 | 
			
		||||
 | 
			
		||||
**Macros** are elements in the format of `{!macro.md!}` that insert common
 | 
			
		||||
phrases and steps at the location of the macros. Macros help eliminate
 | 
			
		||||
repeated content in our documentation.
 | 
			
		||||
 | 
			
		||||
The source for macros is the Markdown files under
 | 
			
		||||
`templates/zerver/help/include` in the
 | 
			
		||||
[main Zulip server repository](https://github.com/zulip/zulip). If you find
 | 
			
		||||
multiple instances of particular content in the documentation, you can
 | 
			
		||||
always create a new macro by adding a new file to that folder.
 | 
			
		||||
 | 
			
		||||
Here are a few common macros used to document Zulip's integrations:
 | 
			
		||||
 | 
			
		||||
### `{!create-stream.md!}` macro
 | 
			
		||||
 | 
			
		||||
* **About:** Recommends that users create a dedicated stream for a
 | 
			
		||||
  given integration. Usually the first step in setting up an integration
 | 
			
		||||
  or webhook.
 | 
			
		||||
 | 
			
		||||
* **Contents:**
 | 
			
		||||
  See [source][1].
 | 
			
		||||
  **Note:** `{{ integration_display_name }}` is replaced by
 | 
			
		||||
  [Integration.display_name][2] and `{{ recommended_stream_name }}`
 | 
			
		||||
  is replaced by [Integration.stream_name][3].
 | 
			
		||||
 | 
			
		||||
* **Example usage:**
 | 
			
		||||
  ```
 | 
			
		||||
  {!create-stream.md!}
 | 
			
		||||
  ```
 | 
			
		||||
 | 
			
		||||
* **Example rendering:**
 | 
			
		||||
  ``` text
 | 
			
		||||
  First, create the stream you would like to use for GitLab notifications,
 | 
			
		||||
  and subscribe all interested parties to this stream. We recommend the
 | 
			
		||||
  name gitlab.
 | 
			
		||||
 | 
			
		||||
  The integration will use the default stream gitlab if no stream is
 | 
			
		||||
  supplied in the URL; you still need to create the stream even if you
 | 
			
		||||
  are using this default.
 | 
			
		||||
  ```
 | 
			
		||||
 | 
			
		||||
### `{!create-bot-construct-url.md!}` macro
 | 
			
		||||
 | 
			
		||||
* **About:** Instructs users to create a bot for a given integration and
 | 
			
		||||
  construct a webhook URL using the bot API key and stream name. The URL is
 | 
			
		||||
  generated automatically for every webhook by using attributes in the
 | 
			
		||||
  [WebhookIntegration][4] class.
 | 
			
		||||
 | 
			
		||||
* **Contents:** See [source][5]. **Note:** If special configuration is
 | 
			
		||||
  required to set up the URL and you can't use this macro, be sure to use the
 | 
			
		||||
  `{{ api_url }}` template variable, so that your integration
 | 
			
		||||
  documentation will provide the correct URL for whatever server it is
 | 
			
		||||
  deployed on.  If special configuration is required to set the SITE
 | 
			
		||||
  variable, you should document that too.
 | 
			
		||||
 | 
			
		||||
* **Example usage:**
 | 
			
		||||
  ```
 | 
			
		||||
  {!create-bot-construct-url.md!}
 | 
			
		||||
  ```
 | 
			
		||||
  Usually used right after `{!create-stream!}`.
 | 
			
		||||
 | 
			
		||||
* **Example rendering:**
 | 
			
		||||
  ```
 | 
			
		||||
  Next, on your Zulip settings page, create a bot for GitLab. Construct
 | 
			
		||||
  the URL for the GitLab bot using the bot API key and stream name:
 | 
			
		||||
 | 
			
		||||
  https://yourZulipDomain.zulipchat.com/api/v1/external/gitlab?api_key=abcdefgh&stream=gitlab
 | 
			
		||||
 | 
			
		||||
  Modify the parameters of the URL above, where api_key is the API key of
 | 
			
		||||
  your Zulip bot, and stream is the stream name you want the notifications
 | 
			
		||||
  sent to.
 | 
			
		||||
  ```
 | 
			
		||||
 | 
			
		||||
### `{!append-stream-name.md!}` macro
 | 
			
		||||
 | 
			
		||||
* **About:** Recommends appending `&stream=stream_name` to a URL in cases
 | 
			
		||||
  where supplying a stream name in the URL is optional.
 | 
			
		||||
 | 
			
		||||
* **Contents:** See [source][6].
 | 
			
		||||
 | 
			
		||||
* **Example usage:** Usually used right after `{!create-bot-construct-url.md!}`.
 | 
			
		||||
  ```
 | 
			
		||||
  {!append-stream-name.md!}
 | 
			
		||||
  ```
 | 
			
		||||
 | 
			
		||||
* **Example rendering:**
 | 
			
		||||
  ```
 | 
			
		||||
  To specify the stream, you must explicitly append
 | 
			
		||||
  `&stream=stream_name` to the end of the above URL, where
 | 
			
		||||
  `stream_name` is the stream you want the notifications sent to.
 | 
			
		||||
  ```
 | 
			
		||||
 | 
			
		||||
### `{!append-topic.md!}` macro
 | 
			
		||||
 | 
			
		||||
* **About:** Recommends appending `&topic=my_topic` to a URL to supply
 | 
			
		||||
  a custom topic for webhook notification messages.
 | 
			
		||||
 | 
			
		||||
* **Contents:** See [source][7].
 | 
			
		||||
 | 
			
		||||
* **Example usage:** Usually used right after `{!create-bot-construct-url.md!}`.
 | 
			
		||||
  ```
 | 
			
		||||
  {!append-topic.md!}
 | 
			
		||||
  ```
 | 
			
		||||
 | 
			
		||||
* **Example rendering:**
 | 
			
		||||
  ```
 | 
			
		||||
  To change the topic used by the bot, simply append `&topic=name`
 | 
			
		||||
  to the end of the above URL, where `name` is your topic.
 | 
			
		||||
  ```
 | 
			
		||||
 | 
			
		||||
### `{!congrats.md!}` macro
 | 
			
		||||
 | 
			
		||||
* **About:** Inserts congratulatory lines signifying the successful setup
 | 
			
		||||
  of a given integration.
 | 
			
		||||
 | 
			
		||||
* **Contents:** See [source][8].
 | 
			
		||||
 | 
			
		||||
* **Example usage:** Usually used at the end of the documentation, right
 | 
			
		||||
  before the sample message screenshot.
 | 
			
		||||
  ```
 | 
			
		||||
  {!congrats.md!}
 | 
			
		||||
  ```
 | 
			
		||||
 | 
			
		||||
* **Example rendering:**
 | 
			
		||||
  ```
 | 
			
		||||
  **Congratulations! You're done!**
 | 
			
		||||
 | 
			
		||||
  Your WebhookName notifications may look like:
 | 
			
		||||
  ```
 | 
			
		||||
 | 
			
		||||
### `{!download-python-bindings.md!}` macro
 | 
			
		||||
 | 
			
		||||
* **About:** Links to Zulip's [API page](https://zulipchat.com/api/) to download
 | 
			
		||||
  and install Zulip's API bindings.
 | 
			
		||||
 | 
			
		||||
* **Contents:** See [source][9].
 | 
			
		||||
 | 
			
		||||
* **Example usage:** Currently mostly used in non-webhook integrations docs
 | 
			
		||||
  under `templates/zerver/integrations/<integration_name>.md`.
 | 
			
		||||
  ```
 | 
			
		||||
  {!download-python-bindings.md!}
 | 
			
		||||
  ```
 | 
			
		||||
 | 
			
		||||
* **Example rendering:**
 | 
			
		||||
  ``` text
 | 
			
		||||
  Download and install our [Python bindings and example scripts](/api)
 | 
			
		||||
  on the server where the IntegrationName bot will live.
 | 
			
		||||
  ```
 | 
			
		||||
 | 
			
		||||
### `{!change-zulip-config-file.md!}` macro
 | 
			
		||||
 | 
			
		||||
* **About:** Instructs users to create a bot and specify said bot's
 | 
			
		||||
  credentials in the config file for a given non-webhook integration.
 | 
			
		||||
 | 
			
		||||
* **Contents:** See [source][10].
 | 
			
		||||
 | 
			
		||||
* **Example usage:** Usually used in non-webhook integration docs under
 | 
			
		||||
  `templates/zerver/integrations/<integration_name>.md`.
 | 
			
		||||
  ```
 | 
			
		||||
  {!change-zulip-config-file.md!}
 | 
			
		||||
  ```
 | 
			
		||||
 | 
			
		||||
* **Example rendering:**
 | 
			
		||||
  ```
 | 
			
		||||
  On your Zulip settings page, create a bot for Codebase.
 | 
			
		||||
 | 
			
		||||
  Next, open `integrations/codebase/zulip_codebase_config.py` with your
 | 
			
		||||
  favorite editor, and change the following lines to specify the email
 | 
			
		||||
  address and API key for your Codebase bot:
 | 
			
		||||
 | 
			
		||||
      ZULIP_USER = "codebase-bot@example.com"
 | 
			
		||||
      ZULIP_API_KEY = "0123456789abcdef0123456789abcdef"
 | 
			
		||||
      ZULIP_SITE = "http://localhost:9991/api"
 | 
			
		||||
  ```
 | 
			
		||||
 | 
			
		||||
### `{!git-append-branches.md!}` and `{!git-webhook-url-with-branches.md!}`
 | 
			
		||||
 | 
			
		||||
* **About:** These two macros explain how to specify a list of branches
 | 
			
		||||
  in the webhook URL to filter notifications in our Git-related webhooks.
 | 
			
		||||
 | 
			
		||||
* **Contents:** See [git-append-branches][12] and
 | 
			
		||||
  [git-webhook-url-with-branches][13].
 | 
			
		||||
 | 
			
		||||
* **Example usage:** Used exclusively in Git integrations.
 | 
			
		||||
  ```
 | 
			
		||||
  {!git-append-branches.md!}
 | 
			
		||||
  {!git-webhook-url-with-branches.md!}
 | 
			
		||||
  ```
 | 
			
		||||
 | 
			
		||||
### Other useful macros
 | 
			
		||||
 | 
			
		||||
* `{!webhook-url.md!}` - Used internally by `{!create-bot-construct-url.md!}`
 | 
			
		||||
  to generate the webhook URL. See [source][11].
 | 
			
		||||
 | 
			
		||||
* `{!zulip-config.md!}` - Used internally by `{!change-zulip-config-file.md!}`
 | 
			
		||||
  to specify the lines in the config file for a non-webhook integration.
 | 
			
		||||
  See [source][15].
 | 
			
		||||
 | 
			
		||||
* `{!webhook-url-with-bot-email.md!}` - Used in certain non-webhook integrations
 | 
			
		||||
  to generate URLs of the form (see [source][14]):
 | 
			
		||||
  ```
 | 
			
		||||
  https://bot_email:bot_api_key@yourZulipDomain.zulipchat.com/api/v1/external/beanstalk
 | 
			
		||||
  ```
 | 
			
		||||
  A good example is
 | 
			
		||||
  [Zulip's Beanstalk integration](https://zulipchat.com/integrations/doc/beanstalk)
 | 
			
		||||
 | 
			
		||||
[1]: https://github.com/zulip/zulip/blob/master/templates/zerver/help/include/create-stream.md
 | 
			
		||||
[2]: https://github.com/zulip/zulip/blob/708f3a4bb19c8e823c9ea1e577d360ac4229b199/zerver/lib/integrations.py#L51
 | 
			
		||||
[3]: https://github.com/zulip/zulip/blob/708f3a4bb19c8e823c9ea1e577d360ac4229b199/zerver/lib/integrations.py#L55
 | 
			
		||||
[4]: https://github.com/zulip/zulip/blob/708f3a4bb19c8e823c9ea1e577d360ac4229b199/zerver/lib/integrations.py#L78
 | 
			
		||||
[5]: https://github.com/zulip/zulip/blob/master/templates/zerver/help/include/create-bot-construct-url.md
 | 
			
		||||
[6]: https://github.com/zulip/zulip/blob/master/templates/zerver/help/include/append-stream-name.md
 | 
			
		||||
[7]: https://github.com/zulip/zulip/blob/master/templates/zerver/help/include/append-topic.md
 | 
			
		||||
[8]: https://github.com/zulip/zulip/blob/master/templates/zerver/help/include/congrats.md
 | 
			
		||||
[9]: https://github.com/zulip/zulip/blob/master/templates/zerver/help/include/download-python-bindings.md
 | 
			
		||||
[10]: https://github.com/zulip/zulip/blob/master/templates/zerver/help/include/change-zulip-config-file.md
 | 
			
		||||
[11]: https://github.com/zulip/zulip/blob/master/templates/zerver/help/include/webhook-url.md
 | 
			
		||||
[12]: https://github.com/zulip/zulip/blob/master/templates/zerver/help/include/git-append-branches.md
 | 
			
		||||
[13]: https://github.com/zulip/zulip/blob/master/templates/zerver/help/include/git-webhook-url-with-branches.md
 | 
			
		||||
[14]: https://github.com/zulip/zulip/blob/master/templates/zerver/help/include/webhook-url-with-bot-email.md
 | 
			
		||||
[15]: https://github.com/zulip/zulip/blob/master/templates/zerver/help/include/zulip-config.md
 | 
			
		||||
@@ -1,208 +0,0 @@
 | 
			
		||||
# Writing a new integration
 | 
			
		||||
 | 
			
		||||
Integrations are one of the most important parts of a group chat tool
 | 
			
		||||
like Zulip, and we are committed to making integrating with Zulip and
 | 
			
		||||
getting you integration merged upstream so everyone else can benefit
 | 
			
		||||
from it as easy as possible while maintaining the high quality of the
 | 
			
		||||
Zulip integrations library.
 | 
			
		||||
 | 
			
		||||
On this page you'll find:
 | 
			
		||||
 | 
			
		||||
* An overview of the different [types of integrations](#types-of-integrations)
 | 
			
		||||
  possible with Zulip.
 | 
			
		||||
* [General advice](#general-advice) for writing integrations.
 | 
			
		||||
* Details about writing [webhook integrations](#webhook-integrations).
 | 
			
		||||
* Details about writing [Python script and plugin
 | 
			
		||||
  integrations](#python-script-and-plugin-integrations).
 | 
			
		||||
* A guide to
 | 
			
		||||
  [documenting your integration](integration-docs-guide.html) is on a
 | 
			
		||||
  separate page.
 | 
			
		||||
 | 
			
		||||
A detailed walkthrough of a simple "Hello World" integration can be
 | 
			
		||||
found in the [webhook walkthrough](../tutorials/webhook-walkthrough.html).
 | 
			
		||||
 | 
			
		||||
Contributions to this guide are very welcome, so if you run into any
 | 
			
		||||
issues following these instructions or come up with any tips or tools
 | 
			
		||||
that help writing integration, please email
 | 
			
		||||
zulip-devel@googlegroups.com, open an issue, or submit a pull request
 | 
			
		||||
to share your ideas!
 | 
			
		||||
 | 
			
		||||
## Types of integrations
 | 
			
		||||
 | 
			
		||||
We have several different ways that we integrate with 3rd party
 | 
			
		||||
products, ordered here by which types we prefer to write:
 | 
			
		||||
 | 
			
		||||
1. **[Webhook integrations](#webhook-integrations)** (examples:
 | 
			
		||||
   Freshdesk, GitHub), where the third-party service supports posting
 | 
			
		||||
   content to a particular URI on our site with data about the event.
 | 
			
		||||
   For these, you usually just need to create a new python package in
 | 
			
		||||
   the `zerver/webhooks/` directory.  You can easily find recent
 | 
			
		||||
   commits adding new integrations to crib from via `git log
 | 
			
		||||
   zerver/webhooks/`.
 | 
			
		||||
 | 
			
		||||
2. **[Python script integrations](#python-script-and-plugin-integrations)**
 | 
			
		||||
   (examples: SVN, Git), where we can get the service to call our integration
 | 
			
		||||
   (by shelling out or otherwise), passing in the required data.  Our preferred
 | 
			
		||||
   model for these is to ship these integrations in the
 | 
			
		||||
   [Zulip Python API distribution](https://github.com/zulip/python-zulip-api/tree/master/zulip),
 | 
			
		||||
   within the `integrations` directory there.
 | 
			
		||||
 | 
			
		||||
3. **[Plugin integrations](#python-script-and-plugin-integrations)** (examples:
 | 
			
		||||
   Jenkins, Hubot, Trac) where the user needs to install a plugin into their
 | 
			
		||||
   existing software.  These are often more work, but for some products are the
 | 
			
		||||
   only way to integrate with the product at all.
 | 
			
		||||
 | 
			
		||||
## General advice
 | 
			
		||||
 | 
			
		||||
* Consider using our Zulip markup to make the output from your
 | 
			
		||||
  integration especially attractive or useful (e.g.  emoji, markdown
 | 
			
		||||
  emphasis, @-mentions, or `!avatar(email)`).
 | 
			
		||||
 | 
			
		||||
* Use topics effectively to ensure sequential messages about the same
 | 
			
		||||
  thing are threaded together; this makes for much better consumption
 | 
			
		||||
  by users.  E.g. for a bug tracker integration, put the bug number in
 | 
			
		||||
  the topic for all messages; for an integration like Nagios, put the
 | 
			
		||||
  service in the topic.
 | 
			
		||||
 | 
			
		||||
* Integrations that don't match a team's workflow can often be
 | 
			
		||||
  uselessly spammy.  Give careful thought to providing options for
 | 
			
		||||
  triggering Zulip messages only for certain message types, certain
 | 
			
		||||
  projects, or sending different messages to different streams/topics,
 | 
			
		||||
  to make it easy for teams to configure the integration to support
 | 
			
		||||
  their workflow.
 | 
			
		||||
 | 
			
		||||
* Consistently capitalize the name of the integration in the
 | 
			
		||||
  documentation and the Client name the way the vendor does.  It's OK
 | 
			
		||||
  to use all-lower-case in the implementation.
 | 
			
		||||
 | 
			
		||||
* Sometimes it can be helpful to contact the vendor if it appears they
 | 
			
		||||
  don't have an API or webhook we can use -- sometimes the right API
 | 
			
		||||
  is just not properly documented.
 | 
			
		||||
 | 
			
		||||
* A helpful tool for testing your integration is
 | 
			
		||||
  [UltraHook](http://www.ultrahook.com/), which allows you to receive webhook
 | 
			
		||||
  calls via your local Zulip development environment. This enables you to do end-to-end
 | 
			
		||||
  testing with live data from the service you're integrating and can help you
 | 
			
		||||
  spot why something isn't working or if the service is using custom HTTP
 | 
			
		||||
  headers.
 | 
			
		||||
 | 
			
		||||
## Webhook integrations
 | 
			
		||||
 | 
			
		||||
A webhook allows a third-party service to push data to you when something
 | 
			
		||||
happens. It's different from making a REST API call, where you send a request
 | 
			
		||||
to the service's API and wait for a response. With a webhook, the third-party
 | 
			
		||||
service sends you an HTTP POST when it has something for you. Your webhook
 | 
			
		||||
integration defines the URI the service uses to communicate with Zulip, and
 | 
			
		||||
handles that incoming data.
 | 
			
		||||
 | 
			
		||||
New Zulip webhook integrations can take just a few hours to write,
 | 
			
		||||
including tests and documentation, if you use the right process.
 | 
			
		||||
 | 
			
		||||
**For detailed instructions, check out the ["Hello World" webhook walkthrough](
 | 
			
		||||
../tutorials/webhook-walkthrough.html)**.
 | 
			
		||||
 | 
			
		||||
For a quick guide, read on.
 | 
			
		||||
 | 
			
		||||
* First, use <http://requestb.in/> or a similar site to capture an
 | 
			
		||||
  example webhook payload from the service you're integrating.  You
 | 
			
		||||
  can use these captured payloads to create a set of test fixtures for
 | 
			
		||||
  your integration under `zerver/webhooks/mywebhook/fixtures/`.
 | 
			
		||||
 | 
			
		||||
* Then write a draft webhook handler under `zerver/webhooks/`;
 | 
			
		||||
  there are a lot of examples in that directory.  We recommend
 | 
			
		||||
  templating off a short one (like `stash` or `zendesk`), since
 | 
			
		||||
  the longer ones usually just have more complex parsing which can
 | 
			
		||||
  obscure what's common to all webhook integrations.  In addition to
 | 
			
		||||
  writing the integration itself, you'll need to create `Integration`
 | 
			
		||||
  object and add it to `WEBHOOK_INTEGRATIONS` in
 | 
			
		||||
  `zerver/lib/integrations.py;` search for `webhook` in that
 | 
			
		||||
  file to find the existing ones (and please add yours in the
 | 
			
		||||
  alphabetically correct place).
 | 
			
		||||
 | 
			
		||||
* Then write a test for your fixture in the `tests.py` file in the
 | 
			
		||||
  `zerver/webhooks/mywebhook` directory.  You can now iterate on
 | 
			
		||||
  debugging the tests and webhooks handler until they work, all
 | 
			
		||||
  without ever needing to post directly from the service you're
 | 
			
		||||
  integrating with to your Zulip development machine.  You can run
 | 
			
		||||
  just the tests for one integration like this:
 | 
			
		||||
 | 
			
		||||
  ```
 | 
			
		||||
  test-backend zerver/webhooks/pagerduty/
 | 
			
		||||
  ```
 | 
			
		||||
 | 
			
		||||
  See [this guide](../testing/testing.html) for more details on the Zulip test
 | 
			
		||||
  runner.
 | 
			
		||||
 | 
			
		||||
* Once you've gotten your webhook working and passing a test, capture
 | 
			
		||||
  payloads for the other common types of posts the service's webhook
 | 
			
		||||
  will make, and add tests for them; usually this part of the process
 | 
			
		||||
  is pretty fast.  Webhook integration tests should all use fixtures
 | 
			
		||||
  (as opposed to contacting the service), since otherwise the tests
 | 
			
		||||
  can't run without Internet access and some sort of credentials for
 | 
			
		||||
  the service.
 | 
			
		||||
 | 
			
		||||
* Finally, write documentation for the integration; there's a
 | 
			
		||||
  [detailed guide](integration-docs-guide.html).
 | 
			
		||||
 | 
			
		||||
### Files that need to be created
 | 
			
		||||
 | 
			
		||||
Select a name for your webhook and use it consistently. The examples below are
 | 
			
		||||
for a webhook named 'MyWebHook'.
 | 
			
		||||
 | 
			
		||||
* `static/images/integrations/logos/mywebhook.svg`: An image to represent
 | 
			
		||||
  your integration in the user interface. Generally this should be the logo of the
 | 
			
		||||
  platform/server/product you are integrating. See [Documenting your
 | 
			
		||||
  integration](integration-docs-guide.html) for details.
 | 
			
		||||
* `static/images/integrations/mywebbook/001.svg`: A screen capture of your
 | 
			
		||||
  integration for use in the user interface. You can add as many images as needed
 | 
			
		||||
  to effectively document your webhook integration. See [Documenting your
 | 
			
		||||
  integration](integration-docs-guide.html) for details.
 | 
			
		||||
* `zerver/webhooks/mywebhook/fixtures/messagetype.json`: Sample json payload data
 | 
			
		||||
  used by tests. Add one fixture file per type of message supported by your
 | 
			
		||||
  integration. See [Testing and writing tests](../testing/testing.html) for details.
 | 
			
		||||
* `zerver/webhooks/mywebhook/__init__.py`: Empty file that is obligatory
 | 
			
		||||
   part of every python package.  Remember to `git add` it.
 | 
			
		||||
* `zerver/webhooks/mywebhook/view.py`: Includes the main webhook integration
 | 
			
		||||
  function including any needed helper functions.
 | 
			
		||||
* `zerver/webhooks/mywebhook/tests.py`: Add tests for your
 | 
			
		||||
  webbook. See [Testing and writing tests](../testing/testing.html) for details.
 | 
			
		||||
* `zerver/webhooks/mywebhook/doc.html`: Add end-user documentation. See
 | 
			
		||||
  [Documenting your integration](integration-docs-guide.html) for details.
 | 
			
		||||
 | 
			
		||||
### Files that need to be updated
 | 
			
		||||
 | 
			
		||||
* `zerver/lib/integrations.py`: Add your integration to
 | 
			
		||||
`WEBHOOK_INTEGRATIONS` to register it.  This will automatically
 | 
			
		||||
register a url for the webhook of the form `api/v1/external/mywebhook`
 | 
			
		||||
and associate with the function called `api_mywebhook_webhook` in
 | 
			
		||||
`zerver/webhooks/mywebhook/view.py`.
 | 
			
		||||
 | 
			
		||||
## Python script and plugin integrations
 | 
			
		||||
 | 
			
		||||
For plugin integrations, usually you will need to consult the
 | 
			
		||||
documentation for the third party software in order to learn how to
 | 
			
		||||
write the integration.  But we have a few notes on how to do these:
 | 
			
		||||
 | 
			
		||||
* You should always send messages by POSTing to URLs of the form
 | 
			
		||||
`https://zulip.example.com/v1/messages/`.
 | 
			
		||||
 | 
			
		||||
* We usually build Python script integration with (at least) 2 files:
 | 
			
		||||
`zulip_foo_config.py` containing the configuration for the
 | 
			
		||||
integration including the bots' API keys, plus a script that reads
 | 
			
		||||
from this configuration to actually do the work (that way, it's
 | 
			
		||||
possible to update the script without breaking users' configurations).
 | 
			
		||||
 | 
			
		||||
* Be sure to test your integration carefully and document how to
 | 
			
		||||
  install it (see notes on documentation below).
 | 
			
		||||
 | 
			
		||||
* You should specify a clear HTTP User-Agent for your integration. The
 | 
			
		||||
user agent should at a minimum identify the integration and version
 | 
			
		||||
number, separated by a slash. If possible, you should collect platform
 | 
			
		||||
information and include that in `()`s after the version number. Some
 | 
			
		||||
examples of ideal UAs are:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
ZulipDesktop/0.7.0 (Ubuntu; 14.04)
 | 
			
		||||
ZulipJenkins/0.1.0 (Windows; 7.2)
 | 
			
		||||
ZulipMobile/0.5.4 (Android; 4.2; maguro)
 | 
			
		||||
```
 | 
			
		||||
@@ -1,501 +0,0 @@
 | 
			
		||||
# Webhook walkthrough
 | 
			
		||||
 | 
			
		||||
Below explains each part of a simple webhook integration, called **Hello
 | 
			
		||||
World**. This webhook sends a "hello" message to the `test` stream and includes
 | 
			
		||||
a link to the Wikipedia article of the day, which it formats from json data it
 | 
			
		||||
receives in the http request.
 | 
			
		||||
 | 
			
		||||
Use this walkthrough to learn how to write your first webhook
 | 
			
		||||
integration.
 | 
			
		||||
 | 
			
		||||
## Step 0: Create fixtures
 | 
			
		||||
 | 
			
		||||
The first step in creating a webhook is to examine the data that the
 | 
			
		||||
service you want to integrate will be sending to Zulip.
 | 
			
		||||
 | 
			
		||||
You can use <http://requestb.in> or a similar tool to capture
 | 
			
		||||
webhook payload(s) from the service you are integrating. Examining this
 | 
			
		||||
data allows you to do two things:
 | 
			
		||||
 | 
			
		||||
1. Determine how you will need to structure your webhook code, including what
 | 
			
		||||
   message types your integration should support and how; and,
 | 
			
		||||
2. Create fixtures for your webhook tests.
 | 
			
		||||
 | 
			
		||||
A test fixture is a small file containing test data, one for each test.
 | 
			
		||||
Fixtures enable the testing of webhook integration code without the need to
 | 
			
		||||
actually contact the service being integrated.
 | 
			
		||||
 | 
			
		||||
Because `Hello World` is a very simple webhook that does one thing, it requires
 | 
			
		||||
only one fixture, `zerver/webhooks/helloworld/fixtures/hello.json`:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
{
 | 
			
		||||
  "featured_title":"Marilyn Monroe",
 | 
			
		||||
  "featured_url":"https://en.wikipedia.org/wiki/Marilyn_Monroe",
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
When writing your own webhook integration, you'll want to write a test function
 | 
			
		||||
for each distinct message condition your webhook supports. You'll also need a
 | 
			
		||||
corresponding fixture for each of these tests. Depending on the type of data
 | 
			
		||||
the 3rd party service sends, your fixture may contain JSON, URL encoded text, or
 | 
			
		||||
some other kind of data. See [Step 4: Create tests](#step-4-create-tests) or
 | 
			
		||||
[Testing](../testing/testing.html) for further details.
 | 
			
		||||
 | 
			
		||||
## Step 1: Initialize your webhook python package
 | 
			
		||||
 | 
			
		||||
In the `zerver/webhooks/` directory, create new subdirectory that will
 | 
			
		||||
contain all of corresponding code.  In our example it will be
 | 
			
		||||
`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. `touch
 | 
			
		||||
zerver/webhooks/helloworld/__init__.py`.
 | 
			
		||||
 | 
			
		||||
## Step 2: Create main webhook code
 | 
			
		||||
 | 
			
		||||
The majority of the code for your webhook integration will be in a single
 | 
			
		||||
python file, `zerver/webhooks/mywebhook/view.py`.
 | 
			
		||||
 | 
			
		||||
The Hello World integration is in `zerver/webhooks/helloworld/view.py`:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
from django.utils.translation import ugettext as _
 | 
			
		||||
from zerver.lib.actions import check_send_stream_message
 | 
			
		||||
from zerver.lib.response import json_success, json_error
 | 
			
		||||
from zerver.decorator import REQ, has_request_variables, api_key_only_webhook_view
 | 
			
		||||
from zerver.lib.validator import check_dict, check_string
 | 
			
		||||
 | 
			
		||||
from zerver.models import Client, UserProfile
 | 
			
		||||
 | 
			
		||||
from django.http import HttpRequest, HttpResponse
 | 
			
		||||
from typing import Dict, Any, Iterable, Optional, Text
 | 
			
		||||
 | 
			
		||||
@api_key_only_webhook_view('HelloWorld')
 | 
			
		||||
@has_request_variables
 | 
			
		||||
def api_helloworld_webhook(request: HttpRequest, user_profile: UserProfile,
 | 
			
		||||
                           payload: Dict[str, Iterable[Dict[str, Any]]]=REQ(argument_type='body'),
 | 
			
		||||
                           stream: Text=REQ(default='test'),
 | 
			
		||||
                           topic: Text=REQ(default='Hello World')) -> 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(**payload)
 | 
			
		||||
 | 
			
		||||
    # send the message
 | 
			
		||||
    check_send_stream_message(user_profile, request.client,
 | 
			
		||||
                              stream, topic, body)
 | 
			
		||||
 | 
			
		||||
    # return json result
 | 
			
		||||
    return json_success()
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The above code imports the required functions and defines the main webhook
 | 
			
		||||
function `api_helloworld_webhook`, decorating it with `api_key_only_webhook_view` and
 | 
			
		||||
`has_request_variables`. The `has_request_variables` decorator allows you to
 | 
			
		||||
access request variables with `REQ()`. You can find more about `REQ` and request
 | 
			
		||||
variables in [Writing views](writing-views.html#request-variables).
 | 
			
		||||
 | 
			
		||||
You must pass the name of your webhook to the `api_key_only_webhook_view`
 | 
			
		||||
decorator so your webhook can access the `user_profile` and `request.client`
 | 
			
		||||
(Zulip's analogue of UserAgent) fields from the request. Here we have used
 | 
			
		||||
`HelloWorld`. To be consistent with Zulip code style, use the name of the
 | 
			
		||||
product you are integrating in camel case, spelled as the product spells
 | 
			
		||||
its own name (except always first letter upper-case).
 | 
			
		||||
 | 
			
		||||
The `api_key_only_webhook_view` decorator indicates that the 3rd party service will
 | 
			
		||||
send the authorization as an API key in the query parameters. If your service uses
 | 
			
		||||
HTTP Basic authentication, you would instead use the `authenticated_rest_api_view`
 | 
			
		||||
decorator.
 | 
			
		||||
 | 
			
		||||
You should name your webhook function as such `api_webhookname_webhook` where
 | 
			
		||||
`webhookname` is the name of your webhook and is always lower-case.
 | 
			
		||||
 | 
			
		||||
At minimum, the webhook function must accept `request` (Django
 | 
			
		||||
[HttpRequest](https://docs.djangoproject.com/en/1.8/ref/request-response/#django.http.HttpRequest)
 | 
			
		||||
object), and `user_profile` (Zulip's user object). You may also want to
 | 
			
		||||
define additional parameters using the `REQ` object.
 | 
			
		||||
 | 
			
		||||
In the example above, we have defined `payload` which is populated
 | 
			
		||||
from the body of the http request, `stream` with a default of `test`
 | 
			
		||||
(available by default in the Zulip development environment), and
 | 
			
		||||
`topic` with a default of `Hello World`. If your webhook uses a custom stream,
 | 
			
		||||
it must exist before a message can be created in it. (See
 | 
			
		||||
[Step 4: Create tests](#step-4-create-tests) for how to handle this in tests.)
 | 
			
		||||
 | 
			
		||||
The line that begins `# type` is a mypy type annotation. See [this
 | 
			
		||||
page](../contributing/mypy.html) for details about how to properly annotate your webhook
 | 
			
		||||
functions.
 | 
			
		||||
 | 
			
		||||
In the body of the function we define the body of the message as `Hello! I am
 | 
			
		||||
happy to be here! :smile:`. The `:smile:` indicates an emoji. Then we append a
 | 
			
		||||
link to the Wikipedia article of the day as provided by the json payload.
 | 
			
		||||
 | 
			
		||||
* Sometimes, it might occur that a json payload does not contain all required keys your
 | 
			
		||||
  integration checks for. In such a case, any `KeyError` thrown is handled by the server
 | 
			
		||||
  backend and will create an appropriate response.
 | 
			
		||||
 | 
			
		||||
Then we send a public (stream) message with `check_send_stream_message` which will
 | 
			
		||||
validate the message and then send it.
 | 
			
		||||
 | 
			
		||||
Finally, we return a 200 http status with a JSON format success message via
 | 
			
		||||
`json_success()`.
 | 
			
		||||
 | 
			
		||||
## Step 3: Create an api endpoint for the webhook
 | 
			
		||||
 | 
			
		||||
In order for a webhook to be externally available, it must be mapped to a url.
 | 
			
		||||
This is done in `zerver/lib/integrations.py`.
 | 
			
		||||
 | 
			
		||||
Look for the lines beginning with:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
WEBHOOK_INTEGRATIONS = [
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
And you'll find the entry for Hello World:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
  WebhookIntegration('helloworld', ['misc'], 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 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
 | 
			
		||||
icon. The second positional argument defines a list of categories for the
 | 
			
		||||
integration.
 | 
			
		||||
 | 
			
		||||
At this point, if you're following along and/or writing your own Hello World
 | 
			
		||||
webhook, you have written enough code to test your integration.
 | 
			
		||||
 | 
			
		||||
First, get an API key from the Your bots section of your Zulip user's Settings
 | 
			
		||||
page. If you haven't created a bot already, you can do that there. Then copy
 | 
			
		||||
its API key and replace the placeholder "<api_key>" in the examples with
 | 
			
		||||
your real key. This is how Zulip knows the request is from an authorized user.
 | 
			
		||||
 | 
			
		||||
Now you can test using Zulip itself, or curl on the command line.
 | 
			
		||||
 | 
			
		||||
Using `manage.py` from within the Zulip development environment:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
(zulip-venv)vagrant@vagrant-ubuntu-trusty-64:/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 which 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)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Using curl:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
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 which you should see:
 | 
			
		||||
```
 | 
			
		||||
{"msg":"","result":"success"}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Using either method will create a message in Zulip:
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
## Step 4: Create tests
 | 
			
		||||
 | 
			
		||||
Every webhook integration should have a corresponding test file:
 | 
			
		||||
`zerver/webhooks/mywebhook/tests.py`.
 | 
			
		||||
 | 
			
		||||
The Hello World integration's tests are in `zerver/webhooks/helloworld/tests.py`
 | 
			
		||||
 | 
			
		||||
You should name the class `<WebhookName>HookTests` and have it inherit from
 | 
			
		||||
the base class `WebhookTestCase`. For our HelloWorld webhook, we name the test
 | 
			
		||||
class `HelloWorldHookTests`:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
class HelloWorldHookTests(WebhookTestCase):
 | 
			
		||||
    STREAM_NAME = 'test'
 | 
			
		||||
    URL_TEMPLATE = "/api/v1/external/helloworld?&api_key={api_key}"
 | 
			
		||||
    FIXTURE_DIR_NAME = 'helloworld'
 | 
			
		||||
 | 
			
		||||
    # Note: Include a test function per each distinct message condition your integration supports
 | 
			
		||||
    def test_hello_message(self) -> None:
 | 
			
		||||
        expected_subject = u"Hello World";
 | 
			
		||||
        expected_message = u"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
 | 
			
		||||
        self.send_and_test_stream_message('hello', expected_subject, expected_message,
 | 
			
		||||
                                          content_type="application/x-www-form-urlencoded")
 | 
			
		||||
 | 
			
		||||
    def get_body(self, fixture_name: Text) -> Text:
 | 
			
		||||
        return self.fixture_data("helloworld", fixture_name, file_type="json")
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
In the above example, `STREAM_NAME`, `URL_TEMPLATE`, and `FIXTURE_DIR_NAME` refer
 | 
			
		||||
to class attributes from the base class, `WebhookTestCase`. These are needed by
 | 
			
		||||
the helper function `send_and_test_stream_message` to determine how to execute
 | 
			
		||||
your test. `STREAM_NAME` should be set to your default stream. If it doesn't exist,
 | 
			
		||||
`send_and_test_stream_message` will create it while executing your test.
 | 
			
		||||
 | 
			
		||||
If your test expects a stream name from a test fixture, the value in the fixture
 | 
			
		||||
and the value you set for `STREAM_NAME` must match. The test helpers use `STREAM_NAME`
 | 
			
		||||
to create the destination stream, and then create the message to send using the
 | 
			
		||||
value from the fixture. If these don't match, the test will fail.
 | 
			
		||||
 | 
			
		||||
`URL_TEMPLATE` defines how the test runner will call your webhook, in the same way
 | 
			
		||||
 you would provide a webhook URL to the 3rd party service. `api_key={api_key}` says
 | 
			
		||||
that an API key is expected.
 | 
			
		||||
 | 
			
		||||
In `get_body`, the first argument in the call to `self.fixture_data` specifies the
 | 
			
		||||
prefix of your fixture file names, and `file_type` their type. Common types are
 | 
			
		||||
`json` and `txt`.
 | 
			
		||||
 | 
			
		||||
When writing tests for your webhook, you'll want to include one test function
 | 
			
		||||
(and corresponding fixture) per each distinct message condition that your
 | 
			
		||||
integration supports.
 | 
			
		||||
 | 
			
		||||
If, for example, we added support for sending a goodbye message to our `Hello
 | 
			
		||||
World` webhook, we would add another test function to `HelloWorldHookTests`
 | 
			
		||||
class called something like `test_goodbye_message`:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
    def test_goodbye_message(self) -> None:
 | 
			
		||||
        expected_subject = u"Hello World";
 | 
			
		||||
        expected_message = u"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
 | 
			
		||||
        self.send_and_test_stream_message('goodbye', expected_subject, expected_message,
 | 
			
		||||
                                          content_type="application/x-www-form-urlencoded")
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
As well as a new fixture `goodbye.json` in
 | 
			
		||||
`zerver/webhooks/helloworld/fixtures/`:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
{
 | 
			
		||||
  "featured_title":"Goodbye",
 | 
			
		||||
  "featured_url":"https://en.wikipedia.org/wiki/Goodbye",
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Also consider if your integration should have negative tests, a test where the
 | 
			
		||||
data from the test fixture should result in an error. For details see
 | 
			
		||||
[Negative tests](#negative-tests), below.
 | 
			
		||||
 | 
			
		||||
Once you have written some tests, you can run just these new tests from within
 | 
			
		||||
the Zulip development environment with this command:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
(zulip-venv)vagrant@vagrant-ubuntu-trusty-64:/srv/zulip$
 | 
			
		||||
./tools/test-backend zerver/webhooks/helloworld
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
(Note: You must run the tests from the top level of your development directory.
 | 
			
		||||
The standard location in a Vagrant environment is `/srv/zulip`. If you are not
 | 
			
		||||
using Vagrant, use the directory where you have your development environment.)
 | 
			
		||||
 | 
			
		||||
You will see some script output and if all the tests have passed, you will see:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
Running zerver.webhooks.helloworld.tests.HelloWorldHookTests.test_goodbye_message
 | 
			
		||||
Running zerver.webhooks.helloworld.tests.HelloWorldHookTests.test_hello_message
 | 
			
		||||
DONE!
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Step 5: Create documentation
 | 
			
		||||
 | 
			
		||||
Next, we add end-user documentation for our webhook integration.  You
 | 
			
		||||
can see the existing examples at <https://zulipchat.com/integrations>
 | 
			
		||||
or by accessing `/integrations` in your Zulip development environment.
 | 
			
		||||
 | 
			
		||||
There are two parts to the end-user documentation on this page.
 | 
			
		||||
 | 
			
		||||
The first is the lozenge in the grid of integrations, showing your
 | 
			
		||||
integration logo and name, which links to the full documentation.
 | 
			
		||||
This is generated automatically once you've registered the integration
 | 
			
		||||
in `WEBHOOK_INTEGRATIONS` in `zerver/lib/integrations.py`, and supports
 | 
			
		||||
some customization via options to the `WebhookIntegration` class.
 | 
			
		||||
 | 
			
		||||
Second, you need to write the actual documentation content in
 | 
			
		||||
`zerver/webhooks/mywebhook/doc.md`.
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
Learn how Zulip integrations work with this simple Hello World example!
 | 
			
		||||
 | 
			
		||||
The Hello World webhook will use the `test` stream, which is
 | 
			
		||||
created by default in the Zulip dev environment. If you are running
 | 
			
		||||
Zulip in production, you should make sure that this stream exists.
 | 
			
		||||
 | 
			
		||||
Next, on your {{ ../subsystems/settings.html|safe }}, create a Hello World bot.
 | 
			
		||||
Construct the URL for the Hello World bot using the API key and
 | 
			
		||||
stream name:
 | 
			
		||||
 | 
			
		||||
`{{ api_url }}/v1/external/helloworld?api_key=abcdefgh&stream=test`
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
To trigger a notification using this webhook, use
 | 
			
		||||
`send_webhook_fixture_message` from the Zulip command line:
 | 
			
		||||
 | 
			
		||||
    (zulip-venv)vagrant@vagrant-ubuntu-trusty-64:/srv/zulip$
 | 
			
		||||
    ./manage.py send_webhook_fixture_message \
 | 
			
		||||
    > --fixture=zerver/fixtures/helloworld/hello.json \
 | 
			
		||||
    > '--url=http://localhost:9991/api/v1/external/helloworld?api_key=<api_key>'
 | 
			
		||||
 | 
			
		||||
Or, use curl:
 | 
			
		||||
 | 
			
		||||
    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>
 | 
			
		||||
 | 
			
		||||
{!congrats.md!}
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
`{!congrats.md!}` is an example of a Markdown macro. Zulip has a macro-based
 | 
			
		||||
Markdown/Jinja2 framework that includes macros for common instructions in
 | 
			
		||||
Zulip's webhooks/integrations documentation.
 | 
			
		||||
 | 
			
		||||
See
 | 
			
		||||
[our guide on documenting an integration](integration-docs-guide.html)
 | 
			
		||||
for further details, including how to easily create the message
 | 
			
		||||
screenshot.
 | 
			
		||||
 | 
			
		||||
## Step 5: Preparing a pull request to zulip/zulip
 | 
			
		||||
 | 
			
		||||
When you have finished your webhook integration and are ready for it to be
 | 
			
		||||
available in the Zulip product, follow these steps to prepare your pull
 | 
			
		||||
request:
 | 
			
		||||
 | 
			
		||||
1. Run tests including linters and ensure you have addressed any issues they
 | 
			
		||||
   report. See [Testing](../testing/testing.html) and [Linters](../testing/linters.html) for details.
 | 
			
		||||
2. Read through [Code styles and conventions](../contributing/code-style.html) and take a look
 | 
			
		||||
   through your code to double-check that you've followed Zulip's guidelines.
 | 
			
		||||
3. Take a look at your git history to ensure your commits have been clear and
 | 
			
		||||
   logical (see [Version Control](../contributing/version-control.html) for tips). If not,
 | 
			
		||||
   consider revising them with `git rebase --interactive`. For most webhooks,
 | 
			
		||||
   you'll want to squash your changes into a single commit and include a good,
 | 
			
		||||
   clear commit message.
 | 
			
		||||
4. Push code to your fork.
 | 
			
		||||
5. Submit a pull request to zulip/zulip.
 | 
			
		||||
 | 
			
		||||
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).
 | 
			
		||||
You can also create a [`[WIP]` pull request](../overview/contributing.html#working-on-an-issue)
 | 
			
		||||
while you are still working on your integration. See the
 | 
			
		||||
[Git guide](../contributing/git-guide.html#create-a-pull-request) for more on Zulip's pull
 | 
			
		||||
request process.
 | 
			
		||||
 | 
			
		||||
## 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.
 | 
			
		||||
 | 
			
		||||
### Negative tests
 | 
			
		||||
 | 
			
		||||
A negative test is one that should result in an error, such as incorrect data.
 | 
			
		||||
The helper functions may interpret this as a test failure, when it should instead
 | 
			
		||||
be a successful test of an error condition. To correctly test these cases, you
 | 
			
		||||
must explicitly code your test's execution (using other helpers, as needed)
 | 
			
		||||
rather than call the usual helper function.
 | 
			
		||||
 | 
			
		||||
Here is an example from the WordPress webhook:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
def test_unknown_action_no_data(self) -> None:
 | 
			
		||||
    # Mimic send_and_test_stream_message() to manually execute a negative test.
 | 
			
		||||
    # Otherwise its call to send_json_payload() would assert on the non-success
 | 
			
		||||
    # we are testing. The value of result is the error message the webhook should
 | 
			
		||||
    # return if no params are sent. The fixture for this test is an empty file.
 | 
			
		||||
 | 
			
		||||
    # subscribe to the target stream
 | 
			
		||||
    self.subscribe(self.test_user, self.STREAM_NAME)
 | 
			
		||||
 | 
			
		||||
    # post to the webhook url
 | 
			
		||||
    post_params = {'stream_name': self.STREAM_NAME,
 | 
			
		||||
                   'content_type': 'application/x-www-form-urlencoded'}
 | 
			
		||||
    result = self.client_post(self.url, 'unknown_action', **post_params)
 | 
			
		||||
 | 
			
		||||
    # check that we got the expected error message
 | 
			
		||||
    self.assert_json_error(result, "Unknown WordPress webhook action: WordPress Action")
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
In a normal test, `send_and_test_stream_message` would handle all the setup
 | 
			
		||||
and then check that the webhook's response matches the expected result. If
 | 
			
		||||
the webhook returns an error, the test fails. Instead, explicitly do the
 | 
			
		||||
setup it would have done, and check the result yourself.
 | 
			
		||||
 | 
			
		||||
Here, `subscribe_to_stream` is a test helper that uses `TEST_USER_EMAIL` and
 | 
			
		||||
`STREAM_NAME` (attributes from the base class) to register the user to receive
 | 
			
		||||
messages in the given stream. If the stream doesn't exist, it creates it.
 | 
			
		||||
 | 
			
		||||
`client_post`, another helper, performs the HTTP POST that calls the webhook.
 | 
			
		||||
As long as `self.url` is correct, you don't need to construct the webhook
 | 
			
		||||
URL yourself. (In most cases, it is.)
 | 
			
		||||
 | 
			
		||||
`assert_json_error` then checks if the result matches the expected error.
 | 
			
		||||
If you had used `send_and_test_stream_message`, it would have called
 | 
			
		||||
`send_json_payload`, which checks the result with `assert_json_success`.
 | 
			
		||||
 | 
			
		||||
### Custom query parameters
 | 
			
		||||
 | 
			
		||||
Custom arguments passed in URL query parameters work as expected in the webhook
 | 
			
		||||
code, but require special handling in tests.
 | 
			
		||||
 | 
			
		||||
For example, here is the definition of a webhook function that gets both `stream`
 | 
			
		||||
and `topic` from the query parameters:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
def api_querytest_webhook(request: HttpRequest, user_profile: UserProfile,
 | 
			
		||||
                          payload: str=REQ(argument_type='body'),
 | 
			
		||||
                          stream: str=REQ(default='test'),
 | 
			
		||||
                          topic: str=REQ(default='Default Alert')):
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
In actual use, you might configure the 3rd party service to call your Zulip
 | 
			
		||||
integration with a URL like this:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
http://myhost/api/v1/external/querytest?api_key=abcdefgh&stream=alerts&topic=queries
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
It provides values for `stream` and `topic`, and the webhook can get those
 | 
			
		||||
using `REQ` without any special handling. How does this work in a test?
 | 
			
		||||
 | 
			
		||||
The new attribute `TOPIC` exists only in our class so far. In order to
 | 
			
		||||
construct a URL with a query parameter for `topic`, you can pass the
 | 
			
		||||
attribute `TOPIC` as a keyword argument to `build_webhook_url`, like so:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
class QuerytestHookTests(WebhookTestCase):
 | 
			
		||||
 | 
			
		||||
    STREAM_NAME = 'querytest'
 | 
			
		||||
    TOPIC = "Default Topic"
 | 
			
		||||
    URL_TEMPLATE = "/api/v1/external/querytest?api_key={api_key}&stream={stream}"
 | 
			
		||||
    FIXTURE_DIR_NAME = 'querytest'
 | 
			
		||||
 | 
			
		||||
    def test_querytest_test_one(self) -> None:
 | 
			
		||||
        # construct the URL used for this test
 | 
			
		||||
        self.TOPIC = u"Query Test"
 | 
			
		||||
        self.url = self.build_webhook_url(topic=self.TOPIC)
 | 
			
		||||
 | 
			
		||||
        # define the expected message contents
 | 
			
		||||
        expected_subject = u"Query Test"
 | 
			
		||||
        expected_message = u"This is a test of custom query parameters."
 | 
			
		||||
 | 
			
		||||
        self.send_and_test_stream_message('test_one', expected_subject, expected_message,
 | 
			
		||||
                                          content_type="application/x-www-form-urlencoded")
 | 
			
		||||
 | 
			
		||||
    def get_body(self, fixture_name: Text) -> Text:
 | 
			
		||||
        return self.fixture_data("querytest", fixture_name, file_type="json")
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
You can also override `get_body` if your test data needs to be constructed in
 | 
			
		||||
an unusual way. For more, see the definition for the base class, `WebhookTestCase`
 | 
			
		||||
in `zerver/lib/test_classes.py.`
 | 
			
		||||
		Reference in New Issue
	
	Block a user