Outgoing webhooks

Outgoing webhooks allow you to build or set up Zulip integrations which are notified when certain types of messages are sent in Zulip. When one of those events is triggered, we'll send a HTTP POST payload to the webhook's configured URL. Webhooks can be used to power a wide range of Zulip integrations. For example, the Zulip Botserver is built on top of this API.

Zulip supports outgoing webhooks both in a clean native Zulip format, as well as a format that's compatible with Slack's outgoing webhook API, which can help with porting an existing Slack integration to work with Zulip.

To register an outgoing webhook:

  • Log in to the Zulip server.
  • Navigate to Personal settings () -> Bots -> Add a new bot. Select Outgoing webhook for bot type, the URL you'd like Zulip to post to as the Endpoint URL, the format you want, and click on Create bot. to submit the form/
  • Your new bot user will appear in the Active bots panel, which you can use to edit the bot's settings.

Triggering

There are currently two ways to trigger an outgoing webhook:

  1. @-mention the bot user in a stream. If the bot replies, its reply will be sent to that stream and topic.
  2. Send a private message with the bot as one of the recipients. If the bot replies, its reply will be sent to that thread.

Timeouts

The remote server must respond to a POST request in a timely manner. The default timeout for outgoing webhooks is 10 seconds, though this can be configured by the administrator of the Zulip server by setting OUTGOING_WEBHOOKS_TIMEOUT_SECONDS in the server's settings.

Outgoing webhook format

This is an example of the JSON payload that the Zulip server will POST to your server:

{
    "bot_email": "outgoing-bot@localhost",
    "bot_full_name": "Outgoing webhook test",
    "data": "@**Outgoing webhook test** Zulip is the world\u2019s most productive group chat!",
    "message": {
        "avatar_url": "https://secure.gravatar.com/avatar/1f4f1575bf002ae562fea8fc4b861b09?d=identicon&version=1",
        "client": "website",
        "content": "@**Outgoing webhook test** Zulip is the world\u2019s most productive group chat!",
        "display_recipient": "Verona",
        "id": 112,
        "is_me_message": false,
        "reactions": [],
        "recipient_id": 20,
        "rendered_content": "<p><span class=\"user-mention\" data-user-id=\"25\">@Outgoing webhook test</span> Zulip is the world\u2019s most productive group chat!</p>",
        "sender_email": "iago@zulip.com",
        "sender_full_name": "Iago",
        "sender_id": 5,
        "sender_realm_str": "zulip",
        "stream_id": 5,
        "subject": "Verona2",
        "submessages": [],
        "timestamp": 1527876931,
        "topic_links": [],
        "type": "stream"
    },
    "token": "xvOzfurIutdRRVLzpXrIIHXJvNfaJLJ0",
    "trigger": "mention"
}

Fields documentation

Return values

  • bot_email: string

    Email of the bot user.

  • bot_full_name: string

    The full name of the bot user.

  • data: string

    The message content, in raw Markdown format (not rendered to HTML).

  • trigger: string

    What aspect of the message triggered the outgoing webhook notification. Possible values include private_message and mention.

  • token: string

    A string of alphanumeric characters that can be used to authenticate the webhook request (each bot user uses a fixed token). You can get the token used by a given outgoing webhook bot in the zuliprc file downloaded when creating the bot.

  • message: object

    A dictionary containing details on the message that triggered the outgoing webhook, in the format used by GET /messages.

    • avatar_url: string | null

      The URL of the message sender's avatar. Can be null only if the current user has access to the sender's real email address and client_gravatar was true.

      If null, then the sender has not uploaded an avatar in Zulip, and the client can compute the gravatar URL by hashing the sender's email address, which corresponds in this case to their real email address.

      Changes: Before Zulip 7.0 (feature level 163), access to a user's real email address was a realm-level setting. As of this feature level, email_address_visibility is a user setting.

    • client: string

      A Zulip "client" string, describing what Zulip client sent the message.

    • content: string

      The content/body of the message.

    • content_type: string

      The HTTP content_type for the message content. This will be text/html or text/x-markdown, depending on whether apply_markdown was set.

    • display_recipient: string | (object)[]

      Data on the recipient of the message; either the name of a stream or a dictionary containing basic data on the users who received the message.

    • edit_history: (object)[]

      An array of objects, with each object documenting the changes in a previous edit made to the the message, ordered chronologically from most recent to least recent edit.

      Not present if the message has never been edited or if the realm has disabled viewing of message edit history.

      Every object will contain user_id and timestamp.

      The other fields are optional, and will be present or not depending on whether the stream, topic, and/or message content were modified in the edit event. For example, if only the topic was edited, only prev_topic and topic will be present in addition to user_id and timestamp.

      • prev_content: string

        Only present if message's content was edited.

        The content of the message immediately prior to this edit event.

      • prev_rendered_content: string

        Only present if message's content was edited.

        The rendered HTML representation of prev_content.

      • prev_rendered_content_version: integer

        Only present if message's content was edited.

        The Markdown processor version number for the message immediately prior to this edit event.

      • prev_stream: integer

        Only present if message's stream was edited.

        The stream ID of the message immediately prior to this edit event.

        Changes: New in Zulip 3.0 (feature level 1).

      • prev_topic: string

        Only present if message's topic was edited.

        The topic of the message immediately prior to this edit event.

        Changes: New in Zulip 5.0 (feature level 118). Previously, this field was called prev_subject; clients are recommended to rename prev_subject to prev_topic if present for compatibility with older Zulip servers.

      • stream: integer

        Only present if message's stream was edited.

        The ID of the stream containing the message immediately after this edit event.

        Changes: New in Zulip 5.0 (feature level 118).

      • timestamp: integer

        The UNIX timestamp for the edit.

      • topic: string

        Only present if message's topic was edited.

        The topic of the message immediately after this edit event.

        Changes: New in Zulip 5.0 (feature level 118).

      • user_id: integer | null

        The ID of the user that made the edit.

        Will be null only for edit history events predating March 2017.

        Clients can display edit history events where this is null as modified by either the sender (for content edits) or an unknown user (for topic edits).

    • id: integer

      The unique message ID. Messages should always be displayed sorted by ID.

    • is_me_message: boolean

      Whether the message is a /me status message

    • last_edit_timestamp: integer

      The UNIX timestamp for when the message was last edited, in UTC seconds.

      Not present if the message has never been edited.

    • reactions: (object)[]

      Data on any reactions to the message.

      • emoji_name: string

        Name of the emoji.

      • emoji_code: string

        A unique identifier, defining the specific emoji codepoint requested, within the namespace of the reaction_type.

      • reaction_type: string

        A string indicating the type of emoji. Each emoji reaction_type has an independent namespace for values of emoji_code.

        Must be one of the following values:

        • unicode_emoji : In this namespace, emoji_code will be a dash-separated hex encoding of the sequence of Unicode codepoints that define this emoji in the Unicode specification.

        • realm_emoji : In this namespace, emoji_code will be the ID of the uploaded custom emoji.

        • zulip_extra_emoji : These are special emoji included with Zulip. In this namespace, emoji_code will be the name of the emoji (e.g. "zulip").

      • user_id: integer

        The ID of the user who added the reaction.

        Changes: New in Zulip 3.0 (feature level 2). The user object is deprecated and will be removed in the future.

      • user: object

        Dictionary with data on the user who added the reaction, including the user ID as the id field. Note that reactions data received from the events API has a slightly different user dictionary format, with the user ID field called user_id instead.

        Changes: Deprecated and to be removed in a future release once core clients have migrated to use the adjacent user_id field, which was introduced in Zulip 3.0 (feature level 2). Clients supporting older Zulip server versions should use the user ID mentioned in the description above as they would the user_id field.

        • id: integer

          ID of the user.

        • email: string

          Zulip API email of the user.

        • full_name: string

          Full name of the user.

        • is_mirror_dummy: boolean

          Whether the user is a mirror dummy.

    • recipient_id: integer

      A unique ID for the set of users receiving the message (either a stream or group of users). Useful primarily for hashing.

    • sender_email: string

      The Zulip API email address of the message's sender.

    • sender_full_name: string

      The full name of the message's sender.

    • sender_id: integer

      The user ID of the message's sender.

    • sender_realm_str: string

      A string identifier for the realm the sender is in. Unique only within the context of a given Zulip server.

      E.g. on example.zulip.com, this will be example.

    • stream_id: integer

      Only present for stream messages; the ID of the stream.

    • subject: string

      The topic of the message. Currently always "" for private messages, though this could change if Zulip adds support for topics in private message conversations.

      The field name is a legacy holdover from when topics were called "subjects" and will eventually change.

    • submessages: (string)[]

      Data used for certain experimental Zulip integrations.

    • timestamp: integer

      The UNIX timestamp for when the message was sent, in UTC seconds.

    • topic_links: (object)[]

      Data on any links to be included in the topic line (these are generated by custom linkification filters that match content in the message's topic.)

      Changes: This field contained a list of urls before Zulip 4.0 (feature level 46).

      New in Zulip 3.0 (feature level 1). Previously, this field was called subject_links; clients are recommended to rename subject_links to topic_links if present for compatibility with older Zulip servers.

      • text: string

        The original link text present in the topic.

      • url: string

        The expanded target url which the link points to.

    • type: string

      The type of the message: stream or private.

    • rendered_content: string

      The content/body of the message rendered in HTML.

Replying with a message

Many bots implemented using this outgoing webhook API will want to send a reply message into Zulip. Zulip's outgoing webhook API provides a convenient way to do that by simply returning an appropriate HTTP response to the Zulip server.

A correctly implemented bot will return a JSON object containing one of two possible formats, described below.

Example response payloads

If the bot code wants to opt out of responding, it can explicitly encode a JSON dictionary that contains response_not_required set to True, so that no response message is sent to the user. (This is helpful to distinguish deliberate non-responses from bugs.)

Here's an example of the JSON your server should respond with if you would not like to send a response message:

{
    "response_not_required": true
}

Here's an example of the JSON your server should respond with if you would like to send a response message:

{
    "content": "Hey, we just received **something** from Zulip!"
}

The content field should contain Zulip-format Markdown.

Note that an outgoing webhook bot can use the Zulip REST API with its API key in case your bot needs to do something else, like add an emoji reaction or upload a file.

Slack-format webhook format

This interface translates Zulip's outgoing webhook's request into the format that Slack's outgoing webhook interface sends. As a result, one should be able to use this to interact with third-party integrations designed to work with Slack's outgoing webhook interface. Here's how we fill in the fields that a Slack-format webhook expects:

Name Description
token A string of alphanumeric characters you can use to authenticate the webhook request (each bot user uses a fixed token)
team_id ID of the Zulip organization prefixed by "T".
team_domain Hostname of the Zulip organization
channel_id Stream ID prefixed by "C"
channel_name Stream name
thread_ts Timestamp for when message was sent
timestamp Timestamp for when message was sent
user_id ID of the user who sent the message prefixed by "U"
user_name Full name of sender
text The content of the message (in Markdown)
trigger_word Trigger method
service_id ID of the bot user

The above data is posted as list of tuples (not JSON), here's an example:

[('token', 'v9fpCdldZIej2bco3uoUvGp06PowKFOf'),
 ('team_id', 'T1512'),
 ('team_domain', 'zulip.example.com'),
 ('channel_id', 'C123'),
 ('channel_name', 'integrations'),
 ('thread_ts', 1532078950),
 ('timestamp', 1532078950),
 ('user_id', 'U21'),
 ('user_name', 'Full Name'),
 ('text', '@**test**'),
 ('trigger_word', 'mention'),
 ('service_id', 27)]
  • For successful request, if data is returned, it returns that data, else it returns a blank response.
  • For failed request, it returns the reason of failure, as returned by the server, or the exception message.