Introduction


While controlling devices from a mobile app is useful, there are cases in which you want to control devices without involving users. For example, you may want to turn on or off a device based on an event from another device.

There will be some logic or rules to determine the reaction. MODE lets you implement this logic on a server on the internet and have the server called every time there is an interesting event. These webhooks are provided by Smart Modules you add to your MODE project. (Smart Modules are pluggable features you can add to your project. More on this later.)

Your servers are called for events triggered in any homes in your project. They are intended for you to provide common functionality to your users. You can also use webhooks to aggregate project-wide information to generate statistics or analytics.

Sample Server Quick Start Guide


Before diving into the details of setting up webhooks, let's spin up a sample server on Heroku that will be receiving the webhook calls. Heroku is a cloud service for running your web service. We will be using this example.

Here we assume you have already configured your MODE project to work with devices and apps. If you have not set up a project before, please read Creating a MODE Project first.

To run the sample code on Heroku, create a Heroku account or sign in to Heroku. Then click the following Deploy to Heroku button:

Deploy to Heroku

You will land on Heroku's app deployment screen that looks like the following:

Screenshot - Heroku Deployment

Pick a unique App Name for your Heroku app. Then under the Config Variables section, replace (YOUR_APP_NAME) in the EVENT_URL field with the app name you just chose. If your app name is my-webhooks-handler, EVENT_URL will be https://my-webhooks-handler.herokuapp.com/evt.

Before clicking the Deploy for Free button, open the MODE Developer Console in another browser window and set up a Webhooks Smart Module.

Set up a Webhooks Smart Module on MODE


On the Developer Console, go to the Smart Modules section of your project. Smart Modules are pluggable modules that you can add to your project for extra features.

Click the +NEW button at the top of SMART MODULE LIST. You will see the following screen which lists the types of Smart Modules you can use for your project. More types of Smart Modules will become available over time, but for the purpose of this tutorial, you are using the Webhooks module.

Screenshot - Smart Modules Catalog

Click the ADD button on the Webhooks module. You will see the following form:

Screenshot - Creating Smart Module

Fill in the items to configure your Smart Module. Specify a module ID and enter a short description. In the Event Webhook section, for URL enter the same URL you used for EVENT_URL in the previous step. For Key, simply click the GENERATE button to create one for you.

By specifying Subscribed Events field, you can configure a Smart Module to listen to only a subset of events.

For example, if you are only interested in motion-detected and home-status events, you can specify the Subscribed Events field as follows:

motion-detected
home-status

Or, if you want to subscribe all the Events that happen in your project, enter an asterisk ( "*" ).

Once you are done, click SAVE.

Now you can see your new Smart Module in SMART MODULE LIST. Click the entry and you can see its settings and activity logs.

Launch the Webhooks Handler

Back to the Heroku's app deployment screen. Note the event webhook key you generated earlier on the MODE console. Copy-and-paste that into the EVENT_KEY field under the Config Variables section.

Click the Deploy for Free button. If the deployment is successful, click the View button to open the app you just deployed in your browser. You will see the text No device event.

Test the Webhook

Now go to the MODE console and launch a device simulator. Make sure the device already belongs to a home. Then simply trigger an event on the simulator. Then check the browser window with the Heroku app loaded. You should see an event JSON string like the following:

{
  "homeId": 173,
  "timestamp": "2016-01-04T23:26:09.521Z",
  "eventType": "abc",
  "eventData": {},
  "originDeviceId": 260
}

You can trigger events from the device simulator with different event types and event data. Whenever you trigger an event, you will see a new JSON string with a new timestamp. If you do not see any JSON strings on the browser, make sure the Heroku app's config variables (EVENT_URL and EVENT_KEY) are correctly set.

Reacting to Events and Commands


As you can see in the example above, webhooks can be configured to be called when events are triggered by devices.

An event webhook URL is called whenever an event happens. For example:

https://www.example.com/event_webhook

MODE will make a POST request to the specified URL with information about the event in the request body. By default, the content is in JSON format, so Content-Type will be application/json. Also, the webhook POST request comes with an X-Mode-Signature HTTP header. The webhooks handler should check that the signature and the content match. (We will explain this later.)

The JSON object in the request body of an event webhook call has the following structure:

  • FieldTypeDescriptionRequired?
  • eventTypestringEvent type ID.Yes
  • eventDataobjectEvent data.Yes
  • timestampstringIn RFC 3339 format.Yes
  • homeIdintegerID of home associated with this event.Yes
  • originDeviceIdintegerID of device that sent the event.No (present only if event came from a device)
  • originDeviceClassstringClass ID of device that sent the event.No (present only if event came from a device)
  • originDeviceIpstringIP address of device that sent the event.No (present only if event came from a device)
  • originProjectKeyIDstringID of project API key associated with the event.No (present only if event came from a program using a project API key)
  • originProjectKeyNamestringName of project API key assoicated with the event.No (present only if event came from a program using a project API key)

Here is an example of an event webhook call:

POST /event_webhook HTTP/1.0
Content-Type: application/json
X-Mode-Signature: d3b07384d113edec49eaa6238ad5ff00

{
    "eventType":"abc",
    "eventData":{"key":"value"},
    "homeId":3,
    "timestamp":"2015-06-07T08:37:37.997281148Z",
    "originDeviceId":3,
    "originDeviceClass":"widget",
    "originDeviceIp":"123.123.55.55"
}

Custom Request Header and Body

You can set arbitrary HTTP header and body for the Event Webhook. Click the Advanced Mode button at the top right corner of the Event Webhook setting area, then you can see the Additional input fields for custom header and body.

Advanced Fields

You can embed an event value with a template notation {{ }}. Here's a request body example.

{
  "description": "The event type is {{ eventType }}."
}

When you get an event which has "eventType": "abc", the above JSON will become the following.

{
  "description": "The event type is abc."
}

The template notation is available for header values and the body. It's not available for header keys. Header values are automatically escaped for HTTP compliance after substitution.

Template Syntax

As the most basic rule, put a field name inside double curly braces like {{ eventType }} and {{ homeId }}. See the table in the above section for the available fields. Whitespaces inside curly braces will be ignored. {{ eventType }} and {{eventType}} are the same.

You can access a child node and array element with . and [] notations. For example, if you get the following event,

{
    "eventType":"abc",
    "eventData":{
        "foo": {
            "bar": "BAR"
        },
        "buz":["BUZ-a", "BUZ-b"]
     },
    "homeId":3,
    "timestamp":"2015-06-07T08:37:37.997281148Z",
}

{{ eventData.foo.bar }} returns BAR and {{ eventData.buz[1] }} returns "BUZ-b". When you specify an nonexistent node, it returns an empty string.

And also, when you specify a non-leaf node, it returns an escaped JSON string. For example, {{ eventData.foo }} returns {\"bar\": \"BAR\"}.

A broken notation (e.g. {{ unclosed, {{ wrongDots.. }} or {{ wrongArray[ }}) causes an error and it prevents a webhook request.

Request Body Validation

When the request body is assumed as JSON, Webhooks Smart Module will verify if it's valid JSON format when it tries propagating the Events. This validation will be done after the template substitution process described above. If the validation fails, Webhooks Smart Module will not send the request.

The body format is determined by the Content-Type header. The format will be assumed as JSON when you specify application/json explicitly, or when you don't specify this header by yourself. Since the default Content-Type header value is application/json as the above section mentions.

Command Webhooks

When setting up your Smart Module earlier, you probably noticed that you can also specify a Command Webhook. This webhook is called whenever you make a PUT request to the MODE cloud at the following API endpoint:

https://api.tinkermode.com/homes/HOME_ID/smartModules/MODULE_ID/command

Where HOME_ID specifies the home associated with this request and MODULE_ID is the ID of your webhooks module. The body of the request must be a JSON object that contains an action field and a parameters field (similar to a device command object). See the full API documentation here.

The request body of a command webhook call is a JSON object with the following structure:

  • FieldTypeDescriptionRequired?
  • actionstringIdentifier of the action to be taken.Yes
  • parametersobjectAction-specific parameters.Yes
  • timestampstringIn RFC 3339 format.Yes
  • homeIdintegerID of home associated with this command.Yes
  • originUserIdintegerID of user who sent the command.No (present only if command came from a user)
  • originProjectKeyIDstringID of project API key associated with command.No (present only if command came from a program using a project API key)
  • originProjectKeyNamestringName of project API key associated with command.No (present only if command came from program using a project API key)

Webhook Signature Verification


Anyone can call your webhook URL and post a request. To verify that the request is coming from MODE, your webhooks handler should check the 'X-Mode-Signature' header in the request. The signature is calculated by applying the SHA-256 hash algorithm on the webhook URL and the request body.

What You Need for Generating the Signature

Let's say the URL of the webhook is as follows: https://www.example.com/command_webhook

And the following is the JSON string in the request body:

{
  "homeId": 4567,
  "timestamp": "2015-06-02T23:07:48.513902477Z",
  "eventType": "counter",
  "eventData": {
    "cnt": 120
  },
  "originDeviceId": 1234
}

On the Developer Console, you can find the secret key associated with the webhook. Let's say the key is: 6idnnaidkdkdr5zq1838jGV

Generating and Matching the Signatures

First you have to create an HMAC using the webhook key. After that, concatenate the webhook URL and the body, calculate the SHA-256 digest, and encode the results using HEX encoding.

Here is a sample code for NodeJS, using the crypto module:

crypto = require("crypto");

var webhook_url = 'https://www.example.com/command_webhook';
var webhook_key = '6idnnaidkdkdr5zq1838jGV';
var body = '{
  "homeId":4567,
  "timestamp":
  "2015-06-02T23:07:48.513902477Z",
  "eventType":"counter",
  "eventData":
    {
      "cnt":120
    },
  "originDeviceId":1234
}';

var hmac = crypto.createHmac('sha256', webhook_key);
hmac.update(webhook_url + body);
var generated_signature = hmac.digest('hex');

Your handler needs to compare the generated signature with the string coming from the X-Mode-Signature header string. If it doesn't match the string, the handler should reject the request.

Multiple Smart Modules

You can set up multiple Smart Modules for a single project. Each event is delivered to all the Smart Modules that subscribe to that type of events.

For example, you can have a Smart Module for debugging in addition to a Smart Module that handles the business logic.

Conclusion


Webhooks provide a flexible and powerful way to augment the functionalities of your IoT system. You can host your webhooks handlers on dedicated servers that you operate. But it is also possible to implement them on other cloud services, as seen in our Heroku example above.