Use Azure Functions to Create a Microsoft Teams Webhook Connector


Today Microsoft released their new collaboration tool, Microsoft Teams. Microsoft teams is a Slack copy, build on-top of Office 365. It has very good integration with the rest of the Office 365 services, and is looking to be a very promising alternative to Slack for all those companies that have already invested in an Office 365 subscription. Even those who haven’t bought Office 365, might find the price of Microsoft Teams pretty compelling as you can get it with an Office E1 subscription which cost 8$ a month per user, but includes everything from email, teams to SharePoint and Skype.

Microsoft Teams also offers some interesting developer APIs, currently only the Connector API is enabled, but down the road there’s going to be support for custom tabs, and bots.

In this blog post, I’m going to quickly show how we can host a simple contact form in Azure Functions, and have it post the data directly to a Microsoft Team channel, using the connector API.

The connector API in Microsoft Teams, are the exact one used in Office Groups, nothing has changed which makes it super easy to get going with.

Get Webhook from Microsoft Teams

To create a incoming webhook connector for a Microsoft Team channel, your have to do first request a webhook url. I have take a few screenshots of the process.

Select the channel you want to add the connector to

Find the incoming webhook connector type

Give your connector a name and an icon

After clicking next you get the webhook url, which you can use from your Azure function

Create you Azure Function

Now we have a webhook, so all we need is a web page with a contact form and some code that can post to the webhook in the correct format. Azure functions are excellent for that (okay not excellent for hosting the webpage, but it is fun to do everything with functions, so we will use a dynamic azure function regardless of it being kinda slow on the first request).

Create two Azure anonymous accessible functions using the HttpTrigger-CSharp template, i called mine contact and post.

Contact form

The contact function is going to host a web page that looks like this

The function app code looks like
using System.Net;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using System.IO;

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
var response = new HttpResponseMessage(HttpStatusCode.OK);
var stream = new FileStream(@"d:\home\site\wwwroot\contact\index.html", FileMode.Open);
response.Content = new StreamContent(stream);
response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/html");
return response;

As seen from the code, it reads the index.html file from disk and serves it. The file is just uploaded along side the function.json and run.csx in the contact function folder.

The content of index.html is something I put together super quick, so it doesn’t win any awards
<!DOCTYPE html>
<html lang="en">
<meta charset="utf-8" />
<title>SJKP Microsoft Team Connector</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="The home page of my website" />
<script src="//" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
html, body {
height: 100%;
font: 16px/1.5 "Helvetica Neue",Helvetica,Arial,sans-serif;
margin: 0;

.container {
max-width: 950px;
margin: 0 auto;
padding: 0 10px;
display: block;

header {
background-color: #080808;

nav a {
color: #9d9d9d;
text-decoration: none;

nav ul {
list-style: none;
padding: 0.7em 0;
margin: 0;

nav li {
display: inline-block;
margin: 0 1em;

nav li:first-child {
font-size: 1.3em;
margin-left: 0;

main {
padding: 2rem 0;
display: block;

/* This ensures the footer is always located at the bottom of the page */
.wrapper {
min-height: 100%;
/* equal to footer height */
margin-bottom: -50px;

.wrapper:after {
content: "";
display: block;

footer, .wrapper:after {
height: 50px;

footer .container {
border-top: 1px solid #e1e1e1;
padding-top: 10px;

#text {

/* Changes the layout of the header on small screens */
@media only screen and (max-width: 640px) {
header ul {
text-align: center;

header li:first-child {
display: block;


<div class="wrapper">
<header role="banner">
<nav class="container">
<li><a href="/">SJKP Microsoft Team Connector</a></li>
<li><a href="/">Home</a></li>
<li><a href="/about.html">About</a></li>

<main class="container">
<h1>Send a message to your Microsoft Team</h1>
<label for="text">Message:</label>
<textarea id="text"></textarea>
<label for="email">Email:</label><br/>
<input type="email" required id="email" />
<input id="btn" type="button" value="Send" />


<div class="container">
<span>&copy; 2016 – SJKP Microsoft Team Connector</span>
$(document).ready(function () {
var msg = $(‘#text’);
var email = $(‘#email’);

$(‘#btn’).click(function () {
url: ‘/api/post’,
method: ‘POST’,
contentType: ‘application/json’,
data: JSON.stringify({
msg: msg.val(),
email: email.val()
}).success(function () {


Post API

The other function, the post function, is responsible for posting the data from the form to Microsoft Teams.

It looks like
using System.Net;
using System.Net.Http;

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
log.Info($"C# HTTP trigger function processed a request. RequestUri={req.RequestUri}");

// Get request body
dynamic data = await req.Content.ReadAsAsync<object>();

var client = new HttpClient();

var json = "{\"title\": \"Message from ""\", \"text\": \""+data.msg+"\", \"themeColor\": \"EA4300\", \"potentialAction\": [{\"@context\": \"\", \"@type\": \"ViewAction\", \"name\": \"Send email\", \"target\": [\"\"]}]}";
await client.PostAsync("<YOUR-WEBHOOK-URL>", new StringContent(json, System.Text.Encoding.UTF8, "application/json"));

return req.CreateResponse(HttpStatusCode.OK);

As you can see I didn’t do anything to make the code look nice or do fancy things. I just copied the json format for the Connector cards and substituted some values with those posted to the API by the user. The end result is that users can write to me in a Microsoft team channel, and we can discuss how to get back to them, right there, no more emails back and forth.