Jan 06, 2020

Explore Microsoft Graph Subscriptions - Part I

Hi Microsoft 365 devs !

Today I start a new series of blog posts in which I'll explore a very interesting capability regarding your Microsoft 365 corporate data: Microsoft Graph Subscriptions, or, if you prefer, Microsoft Graph Webhooks. In a few words, it means the ability to notify an external system whenever something happens in your corporate data.

What is a Webhook ?

A Webhook is a callback issued by a system that manages information to an external system. It is commonly done as a HTTP POST message containing information about the notification generally expressed in JSON. While I still haven't found a true technical standard reference, numbers of systems have integrated them with more or less the same approach (GitHub, Facebook, Twitter, ...). However, they all have their own variant.

Microsoft Graph Subscription (aka Webhook)

In the Microsoft Graph official documentation, you will barely spot the word "Webhook". Instead, it will be referred to as "Subscription", it is basically a synonym.

In Microsoft Graph, you are able to manage (create, update and delete) subscriptions to resources. That means you are able to request Microsoft Graph to notify an endpoint of your choice that a particular change has occurred to one of your Microsoft Graph resources.

How does it work ?

Once you have created a subscription to a specific type of change of a particular resource, Microsoft Graph will issue a HTTP POST call with a JSON payload that will basically contain the identifier of the affected resource and the type of change. The recipient of this notification will then be able to trigger any routine or process in reaction to this change.

Limitations and concerns

By the time of this writing, according to the official docs, Microsoft Graph V1 supports only a certain set of resource types:

Moreover, there are some concerns you have to keep in mind when implementing a Microsoft Graph Webhook :

  • Subscription lifetime: In order to keep any system stable, a subscription usually has an expiration date to avoid the processing workload for something that is no longer used. When the expiration date is reached, the subscription is simply removed. The same applies obviously to Microsoft Graph (which is a huge system that needs to remain stable...). The expiration date is quite short (up to maximum 3 days for almost all of the resource types). The subscription can obviously be updated and the expiration date can be changed. It means it is up to the Webhook owner to take care of the subscription not to expire.
  • HTTPS: The HTTP endpoint to which the notification will be sent must use a HTTPS public address which requires a valid public certificate.
  • URL reachable from the Web: Since the URL will be called from the Microsoft Cloud computers, it requires that the URLs are reachable on the Web. If your recipient system is some on-prem web app, make sure you set the proper firewall rules and DNS redirections!
  • Web Requests: Since Webhooks are essentially Web Requests, you have to make sure that your recipient responds in a timely manner (10 seconds).

If a timeout occurs multiple times, the subscription will be more than likely deleted by Microsoft Graph. In order to reduce the response delay, you have to perform the minimal possible task when receiving the notification. your recipient should rather store or queue the notification and reply to the HTTP request. In turn, another asynchronous program will process the queued notification.

A basic Webhook Implementation

Let's see how we can do a very basic implementation of a Microsoft Graph Webhook! In order to have an hands-on experience with this sample, you will need to have the sufficient privileges on Microsoft Graph (The best is to have a dev tenant with global admin rights ! ;) ) as well as an Azure valid subscription because we will use an Azure Function as our notified endpoint. As a sample scenario, we will subscribe to added events to our personal calendar, The Azure Function will simply output the received notification to the log console.

Our notification endpoint (Azure Function)

  1. Open the Azure Portal and click the "Create a resource"


  1. On the resource type selection, search for "Function App" and select it.
  2. On the Basics settings, select your subscription and resource group preferences and set the runtime stack to Node.js


  1. Click then next at all further steps and leave all default settings. At the end, click the Create button. Wait a couple minutes for your new Function App to be provisioned.
  2. When done, click the New function button and select In portal



  1. Click Continue and then Webhook + API Azure-Function-WebhookTemplate.png

  2. Update the configuration in the Integrate section as following (Set the HTTP methods to POST only, set the Authorization level to Anonymous


  1. Copy the URL of the Azure Function, we will need it a bit later


  1. Replace the code of the function by the following and click Save
module.exports = async function (context, req) {
    context.log('Executing Webhook endpoint...');

    // Validate the subscription creation
    if (req.query.validationToken) {
        context.log('Validating new subscription...');
        context.log('Validation token:');
        context.res = {
            headers: {
                'Content-Type': 'text/plain'
            body: req.query.validationToken
    else {
        context.log('Received new notification...');
        context.log('Notification: ');
        context.res = { body: "" };

A bit of explanation about the small piece of code here above: When we will create the new subscription, Microsoft Graph will immediately issue a POST request to the specified URL with a query string parameter named validationToken with a random opaque value. In order to validate the subscription creation, your endpoint must reply within 10 seconds with a plain text response containing the validationToken value as is. you must not assert anything about this value because it is an arbitrary value set by Microsoft Graph and might change. You can try the Run button passing either a validationToken query string parameter or any content in the body to make sure the Azure Function works as expected. If you don't see anything appearing in the log output, refresh the portal page (it happened to me a few times...).

Create the subscription

To create the subscription, we will use the simplest way: The Graph Explorer. Make sure you signed in with your account.

Azure-Function-IntegrateConfig.png Azure-Function-IntegrateConfig.png

In the URL bar of the Graph Explorer UI, enter https://graph.microsoft.com/v1.0/subscriptions/, you can run with GET verb so you can see you have no subscriptions yet

  1. Change the verb to POST and add the following body
  "changeType": "updated",
  "notificationUrl": "https://yourfunc.azurewebsites.net/api/HttpTrigger1",
  "resource": "me/events",

Notice: even if we specifically want to monitor the added events, the changeType is here set to updated, because MS Graph will reject the changeType "added" for the me/events resource, it actually make sense to me; whenever we add an event, we are "updating" the collection of "my events". 2. the notificationUrl value must be set to the URL you copied earlier. The expirationDateTime value must be a date not later then 3 days from now 3. If everything is okay, you should see a response like


On your Azure Function log, you will probably see "Validating new subscription..."

Let's test it !

Open your outlook calendar and add a new event, after a few seconds, you should see in your Azure Function logs something like Azure-Function-NotificationBody


Now, you know the fundamentals of creating subscriptions in Microsoft Graph! What's next ? In Part II of this post series, we will go a bit deeper and see how to develop a Webhook with proper dev tools and will try to address some of the concerns mentioned above, handling the subscription lifetime and queuing the notifications for asynchronous processing. I hope you enjoyed reading this and then I got your interest to come back read part II soon ;)



Other posts