Working with SharePoint WebHooks with JavaScript using an Azure Function

This blog post covers additional explanation on how to subscribe to SharePoint WebHooks and have it running with only JavaScript in Azure Functions.  The entire code is in one single JavaScript file.

The SharePoint team delivered on the promise to ship SharePoint WebHooks, and made an reference example in C#.  Be sure to watch the PnP webcast as well.

What is Azure Functions?

Azure Functions, in the simplest sense, is a Azure WebJob that lets you run a JavaScript function in a file (it can do C# too), and it will run it for you when you trigger it.  Because it is a Serverless platform - you don't pay for the WebJob unless your function is running.  This makes it really economical (you also get like a million free runs per month...)

Azure Function comes with a super user-friendly UI and you can paste or upload your JavaScript directly in the browser.  You don't need any tools installed.

Running in the browser


Of course, this is server-JavaScript.  So think NodeJS.  We can talk to lots of REST APIs (Graph, SPO) but there will be no browser DOM.

What can you do with this?

You can trigger it on a timer.   Hey that sounds like a SharePoint Timer Job.

You can trigger it on a web request.  This is super useful if you have a client-side UX and you need a button to do some high level elevated permission action.  You call the Azure Function to do it for you, using an App-Only permission elevation.

Why is SharePoint WebHooks important?

A SharePoint WebHook is a REST endpoint that you can attach a remote End Point to.  Right now, there is only a List endpoint.  So any updates to the list items will trigger an event, and SharePoint will call your function.

Hey.  Wait that sounds like a SharePoint Remote Event Receiver.  You are right!


So the idea of our function is this - when triggered:

It will Auth and then talk to SharePoint REST and do one of the following.

  • Check if it has a request parameter "subs" - then it will list the current subscriptions on our target list
  • Check if it has a request parameter "sub" - it will try to attach itself to the target list
    SharePoint will immediately call the function with a validationtoken parameter so…
  • Check if the request has a validationtoken parameter - it will immediately reply with that token as text/plain.
  • Skipping all three conditions, it will run the default action
    The default action is that it will add an list item in a different destination list.  Because the function is running on its own App-Only permission, it can update a list that the original user doesn't have access to.


GET Subscriptions

options = {
    method: 'GET',
    uri: "'subscribe-this')/subscriptions",
    headers: headers
request(options, function (error, res, body) {
    context.res = { body: body || '' };

GET subscriptions.  Array result is [] empty by default.  It has subscriptions after you attach hooks successfully.


POST Subscription (to add itself)

options = {
    method: 'POST',
    uri: "'subscribe-this')/subscriptions",
    body: JSON.stringify({
        "resource": "'subscribe-this')",
        "notificationUrl": "",
        "expirationDateTime": "2017-01-01T16:17:57+00:00",
        "clientState": "jonnofunks"
    headers: headers
request(options, function (error, res, body) {
    context.res = { body: body || '' };

Sending POST subscription without handling validation token.  The request fails.


Handle Validation Token

// if validationtoken is specified in query
// immediately return token as text/plain
context.res = { "content-type": "text/plain", body: req.query.validationtoken };

The function is called twice.  Second time by SharePoint to validate the subscription.

Result Video

The Poked list item was created by "SharePoint App" not "John Liu" the user.


This demo builds on top of the code from Azure Functions, JS and App-Only Updates to SharePoint Online that covers authentication with certificate, and running AppOnly permissions. 

Additionally, I've moved ClientID and Certifate ThumbPrint to Azure Function App Settings.  This means they are no longer part of the code.

App Settings

var clientId = process.env['MyClientId'];
var thumbprint = process.env['MyThumbPrint'];
Function App Settings > Configure App Settings

Function App Settings > Configure App Settings


Source Code

I'm making an effort to put all my demo source code on GitHub going forward.  This is part of the "Upgrade Your JS" demos.

Let me know what you think about SharePoint WebHooks, Azure Functions and JavaScript that will rule everything ;-)

If you spot a bug or want to update the code - send me a Pull Request.


All Demo Downloads will be on Github - blog housekeeping

I've taken a short break from writing blog posts - I haven't been idle, I have been writing something.  Hopefully to be able to share it with everyone soon.  Anticipation is killing me.

As we roll into a new month September!  There are a few planned updates I'm doing for the rest of the year...

  1. Several old blog posts that was in Draft will be merged and published.  These are summary posts from the Office 365 Saturday events I've been going to.
  2. I've been in several SharePoint Sydney user group sessions and that needs summaries too.
  3. SPFx is announced, and now Developer Preview.  Posts there too.
  4. I'm looking around to see what's the best way to record some video sessions as I retire them to the archives.

First big announcement.

All future demo downloads will be on Github!

The download files for my demos on Upskill Your Javascript - from building JS WebParts for SharePoint to Office Add-ins and Azure Functions is up first.

The main driver for this is that the files are updated overtime, and Github really provides a much better place for me to point people to and say the latest files are over here.  Check it out, and if you have Issues - tag them directly on the lines.

So that's the first of the big news.

Seems obvious now...

Seems obvious now...



ngSydney, Office Add-ins and lots of AngularJS

I went to my first ngSydney meetup on Wednesday night, and presented a short session on Office-Addin with AngularJS (I also throw in Graph API) and ran over my allocated time to 40 minutes.

I probably should have asked for 1hr.


Here is the PowerPoint presentation.  Presentation: office-addin yo ngSydney

Also, check out all the examples on


Graph API has Excel endpoints "coming soon".  These REST endpoints will let your app reach inside a workbook/worksheet/range/cell and pull/push stuff in and out like JSON goodness.

Unfortunately, no Word REST API yet.


As I rushed through the demo some things perhaps wasn't as clear as I could explain.

1. There were two libraries/APIs - the Microsoft Graph REST API lets you talk to services across Office and eventually Microsoft space.  You need to register an Azure App so Graph knows what permissions you are after.  Your users need to authenticate and grant permission to your app to use the services that it is asking for.

2. The Office.js within a Office Add-In lets you talk to the current document, or mail within the Office App.  You need to register/deploy the manifest.xml file with your Office 365, or through network share or group policy for Desktop Apps.  The manifest describes what sort of permissions you are seeking on the current document, usually there is only Read or ReadWrite.

You can use Office.js and Graph API together as it was in my demo, or separately.

3. The 'app' I was running in the iframe for both Office 365 or Word 2013 is running out of https://localhost/ - I didn't stop to show this.

4. Authentication piece of the puzzle moves forward as well - next on the line is Progressive Permissions where an app can request more permissions on the spot and the user will be asked to allow additional permissions (write) on the go.

5. Office Addin is available in: Office 2013/2016, Office for Mac, Office for iOS.  Not yet available on Office for Android.  It also runs in Office Online (SharePoint Online, OneDrive, and within both Exchange Online (business) and (consumer).

Had a few awesome quotes:

Racing to the Races - Putting our Office App out there

As I'm posting this blog entry, our (SharePoint Gurus) first Office App (Add-In) would be available on the store.

I might let you in on a secret - it has in fact been in the store in the last few days, but as it is our company's first Add-In, we had some hiccups and had to push out subsequent updates.  We are pleased with this version and we'll run with it to the actual Melbourne Cup race, which actually isn't all that far away.  It would be on November 3, 2015, and the horses list would be available on October 31 - a Saturday, yes that means our Add-In would prompt you to automatically update data on Monday morning.

Feedback through the week from our clients has been very supportive.  This could turn out great (or a great learning experience).  But either way, we have fun and we hope our clients and friends have fun with our App too.

The Team

We are all consultants and this Add-In is something we wanted to build for a long time, but never could tear ourselves away from our great clients to just stop and write this Add-In.

  • We learn AngularJS along the way
  • We became pros at JavaScript Promises... chaining promises, grouping promises, catching error promises and retrying them.
  • Everyone in the company got involved.  We are not a large company, but this one Add-In has 100% contribution from the entire team.
  • We had different people deploying to their own developer sites, both On-Premises and Office 365. 
  • We use TFS but had an open checkout policy (you have to merge any changes).  This turned out not as disastrous as we think, it gave us freedom to work on the project when we can, without having to wait for a certain colleague to check in first.
  • We started the journey a long time ago with Wiki pages, Task lists and Yammer discussion group.  We are now on Office 365 OneNote (available anywhere, offline, and synchronized) and Office 365 Groups for conversation.  We use the Outlook Groups app when we are on the run.  If Office 365 Planner had been available, I'm sure we would be all over it too.  We had a white-board with moving tasks and Post-It notes.

The Stack

The Add-In is a SharePoint-Hosted App. 

Sweepstake Horse says SAAI.  Also, horse is sorry he didn't say on-premise s

Sweepstake Horse says SAAI.  Also, horse is sorry he didn't say on-premises

The Learning (so far)

  • v2 will be provider hosted.  The complexity would lie in provisioning, and also not all our consultants are fluent with ASPNET MVC or C#
  • The benefits are to do with ease of updating the various components, and hiding core logic.
  • We may tackle NodeJS instead


You should download our Add-In and give it a whirl.  Come next Monday, hopefully we hear good things from you.

We already have people asking to do a Rugby World Cup one next year, which would have been fun, this weekend is finals between Australia vs. New Zealand.

SPD2013 Workflow - how to check user is member of group


I want to describe a method that I use to check if a user is a member of a group.



  • Call a REST webservice
    • Reference MSDN for the correct API
    • Build a RequestURL and a basic RequestHeader
    • Figure out what the results mean
  • Wrap it up in a Workflow Custom Activity



MSDN ( - this needs to be a SharePoint Developer's home page) documents a few REST end points that I use for this.

Says you can get to a sharepoint group via:

  • http://<site url>/_api/web/sitegroups(<group id>)
  • http://<site url>/_api/web/sitegroups(<group name>)

The group also has a Users property that points to a Users Collection.

This expands our example to:

  • http://<site url>/_api/web/sitegroups(<group id>)/users

For example:


The Users Collection does not have a method for testing if a user exists.  So I've taken the shortcut and basically brute force the service and just try to retrieve a user.  If you try to request a user that doesn't exist in the collection, it will just error, and I just catch that error.


SharePoint Designer workflow




Build Request Header


Both Accept and Content-Type needs to be "application/json;odata=verbose"


Build Request URL


Concatenate Current Site URL (which ends without a trailing /) and the earlier API.

Note my group name is 'john Members'


Call Web Service



Catch and process the result value.



The ResponseCode could be either OK or InternalServerError

Get a property from the returned Response variable "d/Title" would correspond to the Display Name of the user returned.  If the ResponseCode was Error, then there would be no value in the Response object.



Sandbox Custom Workflow Activity


In Visual Studio, these activities can be bundled into one single Activity that can be reused in SharePoint Designer.  I'll update this in a future blog post on Visual Studio.



Thoughts on checking nested group or AD group memberships

  • There are no way to check member with nested groups.  One possibility is to not think of it as membership, but think of it as whether the person has a certain permission.

    Does the current user have permission to do Contribute on the current Site. 
  • A more complicated thinking could be to create a list, kick out everyone except the group you are interested in, and check if the current user has permission to that list.



This Example in JavaScript


The more I work with SharePoint 2013 Workflows the closer parallels I see relating to a traditional programming language.  Here's the same function call in Javascript.

var promise = $.ajax({
        type: "GET",
        url: _spPageContextInfo.siteServerRelativeUrl + "/_api/web/sitegroups/getbyname('john Owners')/users/getbyid(" + _spPageContextInfo.userId + ")",
        headers: {
                "accept": "application/json;odata=verbose"
        contentType: "application/json;odata=verbose",
        dataType: "json",
        cache: false,
        processData: true

        function (data, status) {

                if (status == "success" && data && data.d) {
                        var title = d.Title;

                else {
                    // success, but no records - this can't really happen.
        function () {
                // not successful - usually not a member of that group