diff --git a/templates/zerver/api/outgoing-webhooks.md b/templates/zerver/api/outgoing-webhooks.md
index 87e0e0d8ed..a35ed40600 100644
--- a/templates/zerver/api/outgoing-webhooks.md
+++ b/templates/zerver/api/outgoing-webhooks.md
@@ -116,27 +116,31 @@ Here's how we fill in the fields that a Slack-format webhook expects:
team_id |
- String ID of the Zulip organization |
+ ID of the Zulip organization prefixed by "T". |
team_domain |
- Domain of the Zulip organization |
+ Hostname of the Zulip organization |
channel_id |
- Stream 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 |
+ ID of the user who sent the message prefixed by "U" |
user_name |
@@ -161,13 +165,14 @@ The above data is posted as list of tuples (not JSON), here's an example:
```
[('token', 'v9fpCdldZIej2bco3uoUvGp06PowKFOf'),
- ('team_id', 'zulip'),
- ('team_domain', 'zulip.com'),
- ('channel_id', '123'),
+ ('team_id', 'T1512'),
+ ('team_domain', 'zulip.example.com'),
+ ('channel_id', 'C123'),
('channel_name', 'integrations'),
+ ('thread_ts', 1532078950),
('timestamp', 1532078950),
- ('user_id', 21),
- ('user_name', 'Sample User'),
+ ('user_id', 'U21'),
+ ('user_name', 'Full Name'),
('text', '@**test**'),
('trigger_word', 'mention'),
('service_id', 27)]
diff --git a/zerver/lib/outgoing_webhook.py b/zerver/lib/outgoing_webhook.py
index a1ea236989..c553f367cd 100644
--- a/zerver/lib/outgoing_webhook.py
+++ b/zerver/lib/outgoing_webhook.py
@@ -20,9 +20,9 @@ from zerver.lib.url_encoding import near_message_url
from zerver.models import (
GENERIC_INTERFACE,
SLACK_INTERFACE,
+ Realm,
Service,
UserProfile,
- email_to_domain,
get_client,
get_user_profile_by_id,
)
@@ -40,7 +40,9 @@ class OutgoingWebhookServiceInterface(metaclass=abc.ABCMeta):
)
@abc.abstractmethod
- def make_request(self, base_url: str, event: Dict[str, Any]) -> Optional[Response]:
+ def make_request(
+ self, base_url: str, event: Dict[str, Any], realm: Realm
+ ) -> Optional[Response]:
raise NotImplementedError
@abc.abstractmethod
@@ -49,7 +51,9 @@ class OutgoingWebhookServiceInterface(metaclass=abc.ABCMeta):
class GenericOutgoingWebhookService(OutgoingWebhookServiceInterface):
- def make_request(self, base_url: str, event: Dict[str, Any]) -> Optional[Response]:
+ def make_request(
+ self, base_url: str, event: Dict[str, Any], realm: Realm
+ ) -> Optional[Response]:
"""
We send a simple version of the message to outgoing
webhooks, since most of them really only need
@@ -98,20 +102,38 @@ class GenericOutgoingWebhookService(OutgoingWebhookServiceInterface):
class SlackOutgoingWebhookService(OutgoingWebhookServiceInterface):
- def make_request(self, base_url: str, event: Dict[str, Any]) -> Optional[Response]:
+ def make_request(
+ self, base_url: str, event: Dict[str, Any], realm: Realm
+ ) -> Optional[Response]:
if event["message"]["type"] == "private":
failure_message = "Slack outgoing webhooks don't support private messages."
fail_with_message(event, failure_message)
return None
+ # https://api.slack.com/legacy/custom-integrations/outgoing-webhooks#legacy-info__post-data
+ # documents the Slack outgoing webhook format:
+ #
+ # token=XXXXXXXXXXXXXXXXXX
+ # team_id=T0001
+ # team_domain=example
+ # channel_id=C2147483705
+ # channel_name=test
+ # thread_ts=1504640714.003543
+ # timestamp=1504640775.000005
+ # user_id=U2147483697
+ # user_name=Steve
+ # text=googlebot: What is the air-speed velocity of an unladen swallow?
+ # trigger_word=googlebot:
+
request_data = [
("token", self.token),
- ("team_id", event["message"]["sender_realm_str"]),
- ("team_domain", email_to_domain(event["message"]["sender_email"])),
- ("channel_id", event["message"]["stream_id"]),
+ ("team_id", f"T{realm.id}"),
+ ("team_domain", realm.host),
+ ("channel_id", f"C{event['message']['stream_id']}"),
("channel_name", event["message"]["display_recipient"]),
+ ("thread_ts", event["message"]["timestamp"]),
("timestamp", event["message"]["timestamp"]),
- ("user_id", event["message"]["sender_id"]),
+ ("user_id", f"U{event['message']['sender_id']}"),
("user_name", event["message"]["sender_full_name"]),
("text", event["command"]),
("trigger_word", event["trigger"]),
@@ -326,11 +348,12 @@ def do_rest_call(
"""Returns response of call if no exception occurs."""
try:
start_time = perf_counter()
+ bot_profile = service_handler.user_profile
response = service_handler.make_request(
base_url,
event,
+ bot_profile.realm,
)
- bot_profile = service_handler.user_profile
logging.info(
"Outgoing webhook request from %s@%s took %f seconds",
bot_profile.id,
diff --git a/zerver/tests/test_outgoing_webhook_interfaces.py b/zerver/tests/test_outgoing_webhook_interfaces.py
index e0ef231af6..64e371971b 100644
--- a/zerver/tests/test_outgoing_webhook_interfaces.py
+++ b/zerver/tests/test_outgoing_webhook_interfaces.py
@@ -107,6 +107,7 @@ class TestGenericOutgoingWebhookService(ZulipTestCase):
self.handler.make_request(
test_url,
event,
+ othello.realm,
)
session.post.assert_called_once()
self.assertEqual(session.post.call_args[0], (test_url,))
@@ -150,6 +151,7 @@ class TestGenericOutgoingWebhookService(ZulipTestCase):
class TestSlackOutgoingWebhookService(ZulipTestCase):
def setUp(self) -> None:
super().setUp()
+ self.bot_user = get_user("outgoing-webhook@zulip.com", get_realm("zulip"))
self.stream_message_event = {
"command": "@**test**",
"user_profile_id": 12,
@@ -187,7 +189,9 @@ class TestSlackOutgoingWebhookService(ZulipTestCase):
}
service_class = get_service_interface_class(SLACK_INTERFACE)
- self.handler = service_class(token="abcdef", user_profile=None, service_name="test-service")
+ self.handler = service_class(
+ token="abcdef", user_profile=self.bot_user, service_name="test-service"
+ )
def test_make_request_stream_message(self) -> None:
test_url = "https://example.com/example"
@@ -195,22 +199,24 @@ class TestSlackOutgoingWebhookService(ZulipTestCase):
self.handler.make_request(
test_url,
self.stream_message_event,
+ self.bot_user.realm,
)
session.post.assert_called_once()
self.assertEqual(session.post.call_args[0], (test_url,))
request_data = session.post.call_args[1]["data"]
self.assertEqual(request_data[0][1], "abcdef") # token
- self.assertEqual(request_data[1][1], "zulip") # team_id
- self.assertEqual(request_data[2][1], "zulip.com") # team_domain
- self.assertEqual(request_data[3][1], "123") # channel_id
+ self.assertEqual(request_data[1][1], "T2") # team_id
+ self.assertEqual(request_data[2][1], "zulip.testserver") # team_domain
+ self.assertEqual(request_data[3][1], "C123") # channel_id
self.assertEqual(request_data[4][1], "integrations") # channel_name
- self.assertEqual(request_data[5][1], 123456) # timestamp
- self.assertEqual(request_data[6][1], 21) # user_id
- self.assertEqual(request_data[7][1], "Sample User") # user_name
- self.assertEqual(request_data[8][1], "@**test**") # text
- self.assertEqual(request_data[9][1], "mention") # trigger_word
- self.assertEqual(request_data[10][1], 12) # user_profile_id
+ self.assertEqual(request_data[5][1], 123456) # thread_id
+ self.assertEqual(request_data[6][1], 123456) # timestamp
+ self.assertEqual(request_data[7][1], "U21") # user_id
+ self.assertEqual(request_data[8][1], "Sample User") # user_name
+ self.assertEqual(request_data[9][1], "@**test**") # text
+ self.assertEqual(request_data[10][1], "mention") # trigger_word
+ self.assertEqual(request_data[11][1], 12) # user_profile_id
@mock.patch("zerver.lib.outgoing_webhook.fail_with_message")
def test_make_request_private_message(self, mock_fail_with_message: mock.Mock) -> None:
@@ -219,6 +225,7 @@ class TestSlackOutgoingWebhookService(ZulipTestCase):
response = self.handler.make_request(
test_url,
self.private_message_event,
+ self.bot_user.realm,
)
session.post.assert_not_called()
self.assertIsNone(response)