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.
There are currently two ways to trigger an outgoing webhook:
- @-mention the bot user in a stream. If the bot replies, its
reply will be sent to that stream and topic.
- Send a private message with the bot as one of the recipients.
If the bot replies, its reply will be sent to that thread.
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
Outgoing webhook format
This is an example of the JSON payload that the Zulip server will
to your server:
"bot_full_name": "Outgoing webhook test",
"data": "@**Outgoing webhook test** Zulip is the world\u2019s most productive group chat!",
"content": "@**Outgoing webhook test** Zulip is the world\u2019s most productive group chat!",
"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>",
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
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
zuliprc file downloaded when creating the bot.
message: object A dict containing details on the message that triggered the
outgoing webhook, in the format used by
avatar_url: string The URL of the user's avatar. Can be null only if client_gravatar was passed,
which means that the user has not uploaded an avatar in Zulip, and the
client should compute the gravatar URL by hashing the
user's email address itself for this user.
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
text/x-markdown, depending on
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.
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
reactions: (object) Data on any reactions to the message.
emoji_code: string A unique identifier, defining the specific emoji codepoint requested,
within the namespace of the
For example, for
unicode_emoji, this will be an encoding of the
emoji_name: string Name of the emoji.
reaction_type: string One of the following values:
unicode_emoji: Unicode emoji (
emoji_code will be its Unicode
realm_emoji: Custom emoji.
emoji_code will be its ID).
zulip_extra_emoji: Special emoji included with Zulip. Exists to
user_id: integer The ID of the user who added the reaction.
Changes: New in Zulip 3.0 (feature level 2). The
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: In the events
confusing had the user ID in a field called
instead. We recommend ignoring fields other than the user
ID. Deprecated and to be removed in a future release
once core clients have migrated to use the
id: integer ID of the user.
email: string 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
sender_email: string The Zulip display 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.
example.zulip.com, this will be
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
The field name is a legacy holdover from when topics were
called "subjects" and will eventually change.
topic_links: (object) Data on any links to be included in the
line (these are generated by custom linkification
filters that match content in the
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
if present for compatibility with older Zulip servers.
submessages: (string) Data used for certain experimental Zulip integrations.
timestamp: integer The UNIX timestamp for when the message was sent,
in UTC seconds.
type: string The type of the message:
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
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:
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!"
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:
|A string of alphanumeric characters you can use to
authenticate the webhook request (each bot user uses a fixed token)
|String ID of the Zulip organization
|Domain of the Zulip organization
|Timestamp for when message was sent
|ID of the user who sent the message
|Full name of sender
|The content of the message (in Markdown)
|ID of the bot user
The above data is posted as list of tuples (not JSON), here's an example:
('user_name', 'Sample User'),
- 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.