Build Your Own Jamf Pro Integrations: Part I An Intro to Webhooks and Flask

Welcome back to the BYO Jamf Pro Integrations tutorial! In Part I we will be giving an introduction to both the webhooks feature of Jamf Pro and the Flask microframework that you installed into your virtual environment in the introduction post.

Webhooks in Jamf Pro

Webhooks are a framework introduced in Jamf Pro v9.93. A webhook itself is an HTTP callback: an HTTP POST that occurs when something happens.

A webhook itself is an HTTP request made by a server to a destination, with a payload, in response to an event. Jamf Pro’s webhooks are built directly on top of a pre-existing Java API called the Events API.

The Events API allowed for Java plugins as .jar files to be installed on the Jamf Pro server. While not an option for Jamf Cloud customers, self-hosted users can take advantage of this. Learn more here:

While not 100%, the available events for Jamf Pro Webhooks closely matches the list for the Events API.

  • ComputerAdded
  • ComputerCheckIn
  • ComputerInventoryCompleted
  • ComputerPolicyFinished
  • ComputerPushCapabilityChanged
  • JSSShutdown
  • JSSStartup
  • MobileDeviceCheckIn
  • MobileDeviceCommandCompleted
  • MobileDeviceEnrolled
  • MobileDevicePushSent
  • MobileDeviceUnEnrolled
  • PatchSoftwareTitleUpdated
  • PushSent
  • RestAPIOperation
  • SCEPChallenge
  • SmartGroupComputerMembershipChange
  • SmartGroupMobileDeviceMembershipChange

To setup a webhook for one of these events, log into your Jamf Pro Server and navigate to Settings -> Global Management -> Webhooks. Click the + New button and you will be taken to a screen to set and select the following:

  • Name
    A description.
  • URL
    The address that you want Jamf Pro to send the event data to.
  • Content Type
    Choose whether that data in in XML or JSON format.
  • Event
    The event from the list above that you want to send on.

When you setup your webhook, Jamf Pro will send an HTTP POST with a payload of the content type you selected containing contextual data on the event. These payloads are broken into two parts: the webhook and eventObject/event keys.

Here is an example in XML:


Here is that same example as JSON:

    "webhook": {
        "id": 1,
        "name": "",
        "webhookEvent": "JSSShutdown"
    "event": {
        "institution": "",
        "hostAddress": "",
        "webApplicationPath": "",
        "isClusterMaster": false,
        "jssUrl": ""

The webhook key is about the Jamf Pro Webhook itself. The database ID, name you set and the type of event are contained here. This is data that, later in the tutorials, can be used to identify which events you are receiving from Jamf Pro.

The eventObject (XML) or event (JSON) key contains the contextual data of what triggered the event, or contextual data about the event depending upon which event was triggered. Many of the events all send the exact same data under this key with the difference will be what is contained under the webhook key. This is, for examples, true for Computer* and MobileDevice* events.

You can dive into full examples of every webhook event in XML and JSON formats at the Unofficial JSS API Docs site:


There needs to be something on the receiving end, the destination URL of the Jamf Pro webhook, to receive the payload and process it / take action on. This is where web development enters the picture. The type of integration that works with webhooks is a web app with endpoints that can accept POST requests.

There are many web technologies out there for all kinds of programming languages. The tutorial series will focus on Python (very popular in the Mac admin community for scripting alongside Ruby) and a microframework called Flask.

The “micro” portion means that Flask does not contains many elements of larger frameworks like a pre-defined database interface. Instead, Flask relies on extensions that plug into the framework and extend the functionality of your code. Flask also does not dictate design choices. Flask apps can be hundreds of files in size, structured in nearly any way, or just one single file. The size and complexity of the project is determined by the scope of your work.

Here is the absolute smallest Flask app that you could write (and you can use this as the boilerplate code to start any of your projects from):

import flask

app = flask.Flask(__name__)

def root():
    return "Hello Penn State MacAdmins!"

if __name__ == '__main__':

Seven lines.

You should have Flask installed and available for your project in the virtual environment created in the first post. At the top of our file we are importing the package.

Then we create an app object that is an instance of the flask.Flask() class:

app = flask.Flask(__name__)

This object will represent the web app throughout our code. To add endpoints, or routes, to the web app we will use the route() decorator.

Decorators are a special kind of Python syntax that “wrap” a function around another function.

In this case, the route() decorator will register an endpoint based upon the path we give as it’s first argument and then execute the wrapped function below it whenever that endpoint is requested! You’ll be able to see clearly this in a moment.

def root():
    return "Hello Penn State MacAdmins!"

The“/” path means the root of the web server. Once this app is running you will be able to reach it in your web browser by navigating to http://localhost:5000. The “/” path resolves to that address.

Flask has a built in development server you can start by calling the run() method on the app object. When you call a Python file as a script from the command line the __name__ dunder becomes set to a value of __main__.

If you are a little confused by the word “dunder” at this point don’t worry, you can continue on without understanding some of these concepts, but you may want to brush up on your Python with some online resources.

By checking if the __name__ dunder is __main__ you can control what your Python scripts do based on whether they have been called from the command line or, later on, imported into other Python code. When imported, the __name__ dunder takes on the name of the file!

So, the last two lines of this single file Flask app mean it will only run the app using the development server if it has been called as a script from the command line:

if __name__ == '__main__':
(byojamf) ~$ python /path/to/
 * Running on (Press CTRL+C to quit)

Your web app is now available when you navigate to http://localhost:5000 in your web browser. Give it a try and you should see the text message in the code! See how when you request the “/” or root of the web app the root() function is executing?

Decorators in action.

Next up…

Accepting Webhooks and Testing with Jook

I’ll see you in Part II tomorrow!





Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s