{"id":3992,"date":"2018-12-14T11:19:47","date_gmt":"2018-12-14T16:19:47","guid":{"rendered":"http:\/\/lisa.rushworth.us\/?p=3992"},"modified":"2018-12-14T11:26:42","modified_gmt":"2018-12-14T16:26:42","slug":"did-you-know-you-can-post-data-to-teams-channels-from-your-own-code","status":"publish","type":"post","link":"https:\/\/www.rushworth.us\/lisa\/?p=3992","title":{"rendered":"Did you know \u2026 you can post data to Teams channels from your own code?"},"content":{"rendered":"\n<p>This post contains more niche \u201cfor developers\u201d information than most of my Teams info series \ud83d\ude0a <\/p>\n\n\n\n<p>You\u2019ve seen third-party services with connectors in Teams \u2013 both services with connectors published to Teams and services that can use the \u201cIncoming Webhook\u201d connector to post data. <em>You<\/em>can use the \u201cIncoming Webhook\u201d connector within your code too. If you want to allow others to post information into <em>their\u00a0<\/em>Teams spaces, you\u2019ll need a configuration option that allows end-users to supply the webhook URL. If you want to post information into <em>your<\/em> Teams space, then you can include it directly in your code.\u00a0If you are doing the former, provide something like the \u201cCreating a Teams Webhook URL\u201d instructions below to your end users. In this example, I am doing the later. The example script is <a href=\"https:\/\/github.com\/ljr55555\/msTeamsUserDetailReport\" target=\"_blank\" rel=\"noreferrer noopener\" aria-label=\"You\u2019ve seen third-party services with connectors in Teams \u2013 both services with connectors published to Teams and services that can use the \u201cIncoming Webhook\u201d connector to post data. Youcan use the \u201cIncoming Webhook\u201d connector within your code too. If you want to allow others to post information into their\u00a0Teams spaces, you\u2019ll need a configuration option that allows end-users to supply the webhook URL. If you want to post information into your Teams space, then you can include it directly in your code.\u00a0If you are doing the former, provide something like the \u201cCreating a Teams Webhook URL\u201d instructions below to your end users. In this example, I am doing the later. The example script is Python code that gathers statistics and posts a summary to my Teams space.  (opens in a new tab)\">Python code that gathers statistics and posts a summary to my Teams space<\/a>. <\/p>\n\n\n\n<p><strong><em>Creating a Teams Webhook URL:<\/em><\/strong><\/p>\n\n\n\n<p>Create a URL for your incoming webhook \u2013 from the not-quite-a hamburger menu next to the channel into which you want to publish your data, select \u201cConnectors\u201d, find \u201cIncoming Webhook\u201d, and click \u201cAdd\u201d (if you have already configured a webhook in your Teams space, you will see \u201cConfigure\u201dinstead, and will not have to \u2018install\u2019 the webhook)<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1019\" height=\"680\" src=\"http:\/\/lisa.rushworth.us\/wp-content\/uploads\/2018\/12\/Teams-IncomingWebhookFromYourCode01.png\" alt=\"\" class=\"wp-image-3994\" srcset=\"https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2018\/12\/Teams-IncomingWebhookFromYourCode01.png 1019w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2018\/12\/Teams-IncomingWebhookFromYourCode01-300x200.png 300w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2018\/12\/Teams-IncomingWebhookFromYourCode01-768x513.png 768w\" sizes=\"auto, (max-width: 1019px) 100vw, 1019px\" \/><\/figure>\n\n\n\n<p>Read the privacy policy and terms of use, and if they are acceptable click \u201cInstall\u201d<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"766\" height=\"759\" src=\"http:\/\/lisa.rushworth.us\/wp-content\/uploads\/2018\/12\/Teams-IncomingWebhookFromYourCode02.png\" alt=\"\" class=\"wp-image-3995\" srcset=\"https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2018\/12\/Teams-IncomingWebhookFromYourCode02.png 766w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2018\/12\/Teams-IncomingWebhookFromYourCode02-150x150.png 150w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2018\/12\/Teams-IncomingWebhookFromYourCode02-300x297.png 300w\" sizes=\"auto, (max-width: 766px) 100vw, 766px\" \/><\/figure>\n\n\n\n<p>Provide a name for your webhook \u2013 this name will be displayed on all posts made via this webhook. Scroll down.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1023\" height=\"695\" src=\"http:\/\/lisa.rushworth.us\/wp-content\/uploads\/2018\/12\/Teams-IncomingWebhookFromYourCode03.png\" alt=\"\" class=\"wp-image-3996\" srcset=\"https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2018\/12\/Teams-IncomingWebhookFromYourCode03.png 1023w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2018\/12\/Teams-IncomingWebhookFromYourCode03-300x204.png 300w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2018\/12\/Teams-IncomingWebhookFromYourCode03-768x522.png 768w\" sizes=\"auto, (max-width: 1023px) 100vw, 1023px\" \/><\/figure>\n\n\n\n<p>You can customize the logo associated with your webhook posts \u2013 nice if your application has a well-known logo. Click create to generate the webhook URL. <\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1019\" height=\"697\" src=\"http:\/\/lisa.rushworth.us\/wp-content\/uploads\/2018\/12\/Teams-IncomingWebhookFromYourCode04.png\" alt=\"\" class=\"wp-image-3997\" srcset=\"https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2018\/12\/Teams-IncomingWebhookFromYourCode04.png 1019w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2018\/12\/Teams-IncomingWebhookFromYourCode04-300x205.png 300w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2018\/12\/Teams-IncomingWebhookFromYourCode04-768x525.png 768w\" sizes=\"auto, (max-width: 1019px) 100vw, 1019px\" \/><\/figure>\n\n\n\n<p>Copy the URL somewhere, then click \u2018Done\u2019.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"696\" src=\"http:\/\/lisa.rushworth.us\/wp-content\/uploads\/2018\/12\/Teams-IncomingWebhookFromYourCode05-1024x696.png\" alt=\"\" class=\"wp-image-3998\" srcset=\"https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2018\/12\/Teams-IncomingWebhookFromYourCode05-1024x696.png 1024w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2018\/12\/Teams-IncomingWebhookFromYourCode05-300x204.png 300w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2018\/12\/Teams-IncomingWebhookFromYourCode05-768x522.png 768w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2018\/12\/Teams-IncomingWebhookFromYourCode05.png 1031w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p><strong><em>Using Teams Webhooks Within Code:<\/em><\/strong><\/p>\n\n\n\n<p>OK, now you\u2019ve got something like this in a text document (no, I didn\u2019t post a real webhook to the Internet \u2013 the long pseudo-random alphanumeric string is hex, and there aren\u2019t a whole lot of m\u2019s and q\u2019s in hex!):<\/p>\n\n\n<p>https:\/\/outlook.office.com\/webhook\/bge9922d-44fa-4c60-bp59-e935554ff4cd@2567m4c1-b0ep-40f5-aee3-58d7c5f3e2b2\/IncomingWebhook\/644915ga291e4ee499f2479a32qde691\/9e0c4d6c-65d9-4f94-b0d5-4fbbb0238358<\/p>\n\n\n<p>What do you do with it? You make a POST call to that URL and supply JSON-formatted card content in the data. Microsoft provides a complete <a href=\"https:\/\/docs.microsoft.com\/en-us\/microsoftteams\/platform\/concepts\/cards\/cards-reference\">card reference<\/a>, but you\u2019ll need to use the <a href=\"https:\/\/docs.microsoft.com\/en-us\/microsoftteams\/platform\/concepts\/cards\/cards-reference#office-365-connector-card\">O365Connector Card<\/a> with the incoming webhook connector. <\/p>\n\n\n\n<p>The card requires \u201csummary\u201d or \u201ctext\u201d be included \u2013 you\u2019ll get a bad data HTTP response if you fail to set one of these values. Card text can be formatted in markdown or HTML \u2013 if you want to use HTML, you need to set markdown to false.<\/p>\n\n\n\n<p>You\u2019ll need a function that POSTs data to a URL:<\/p>\n\n\n<pre>################################################################################\n# This function POSTs to a URL\n# Requirements: requests\n# Input: strURL -- URL to which data is posted\n#        strBody -- content to be sent as data\n#        strContentType -- Content-Type definition\n# Output: BOOL -- TRUE on 200 HTTP code, FALSE on other HTTP response\n################################################################################\ndef postDataToURL(strURL, strBody, strContentType):\n    if strURL is None:\n        print(\"POST failed -- no URL provided\")\n        return False\n    print(\"Sending POST request to strURL=%s\" % strURL)\n    print(\"Body: %s\" % strBody)\n    try:\n        dictHeaders = {'Content-Type': strContentType}\n        res = requests.post(strURL, headers=dictHeaders,data=strBody)\n        print(res.text)\n        if 200 &lt;= res.status_code &lt; 300:\n            print(\"Receiver responded with HTTP status=%d\" % res.status_code)\n            return True\n        else:\n            print(\"POST failed -- receiver responded with HTTP status=%d\" % res.status_code)\n            return False\n    except ValueError as e:\n        print(\"POST failed -- Invalid URL: %s\" % e)\n    return False\n<\/pre>\n\n\n<p>And you\u2019ll need something to create a card and call the HTTP POST function. Here, I define a function that takes statistics on Windstream\u2019s Microsoft Teams usage, formats a card with the values, and POSTs it to my webhook URL. <\/p>\n\n\n<pre>################################################################################\n# This function posts usage stats to Teams via webhook\n# Requirements: json\n# Input: strURL -- webhook url\n#        iPrivateMessages, iTeamMessages, iCalls, iMeetings -- integer usage stats\n#        strReportDate - datetime date for stats\n# Output: BOOL -- TRUE on 200 HTTP code, FALSE on other HTTP response\n################################################################################\ndef postStatsToTeams(strURL,iPrivateMessages,iTeamMessages,iCalls,iMeetings,strReportDate):\n    try:\n        strCardContent = '{\"title\": \"Teams Usage Statistics\",\"sections\": [{\"activityTitle\": \"Usage report for ' + yesterday.strftime('%Y-%m-%d') + '\"}, {\"title\": \"Details\",\"facts\": [{\"name\": \"Private messages\",\"value\": \"' + str(iPrivateMessages) + '\"}, {\"name\": \"Team messages\",\"value\": \"' + str(iTeamMessages) + '\"}, {\"name\": \"Calls \",\"value\": \"' + str(iCalls) + '\"}, {\"name\": \"Meetings\",\"value\": \"' + str(iMeetings) + '\"}]}],\"summary\": \"Teams Usage Statistics\",\"potentialAction\": [{\"name\": \"View web report\",\"target\": [\"https:\/\/csgdirsvcs.windstream.com:1977\/o365Stats\/msTeams.php\"],\"@context\": \"http:\/\/schema.org\",\"@type\": \"ViewAction\"}]}'\n        jsonPostData = json.loads(strCardContent)\n\n        if postDataToURL(strURL, json.dumps(jsonPostData),'application\/json'):\n            print(\"POST successful\")\n            return True\n        else:\n            print(\"POST failed\")\n            return False\n    except Exception as e:\n        print(\"ERROR Unexpected error: %s\" % e)\n    return False\n<\/pre>\n\n\n<p>Run the program, and you\u2019ll see information in Teams:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"396\" src=\"http:\/\/lisa.rushworth.us\/wp-content\/uploads\/2018\/12\/Teams-IncomingWebhookFromYourCode06-1024x396.png\" alt=\"\" class=\"wp-image-3999\" srcset=\"https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2018\/12\/Teams-IncomingWebhookFromYourCode06-1024x396.png 1024w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2018\/12\/Teams-IncomingWebhookFromYourCode06-300x116.png 300w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2018\/12\/Teams-IncomingWebhookFromYourCode06-768x297.png 768w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2018\/12\/Teams-IncomingWebhookFromYourCode06.png 1264w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>A personal recommendation based on my experience with third-party code that uses generic incoming webhooks &#8212; have a mechanism to see more than a generic error when the POST fails. It takes a lot of effort to pull apart what is actually being sent, turn it into a curl command to reproduce the event, and read the <em>actual<\/em> error.Providing a debug facility that includes both the POST body and actual response from the HTTP call saves you a <em>lot<\/em> of time should your posts fail.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This post contains more niche \u201cfor developers\u201d information than most of my Teams info series \ud83d\ude0a You\u2019ve seen third-party services with connectors in Teams \u2013 both services with connectors published to Teams and services that can use the \u201cIncoming Webhook\u201d connector to post data. Youcan use the \u201cIncoming Webhook\u201d connector within your code too. If &hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[677],"tags":[675,666,664,665,718],"class_list":["post-3992","post","type-post","status-publish","format-standard","hentry","category-office-365","tag-did-you-know","tag-microsoft-teams","tag-python","tag-teams","tag-webhook"],"_links":{"self":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts\/3992","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=3992"}],"version-history":[{"count":3,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts\/3992\/revisions"}],"predecessor-version":[{"id":4003,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts\/3992\/revisions\/4003"}],"wp:attachment":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=3992"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=3992"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=3992"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}