Webhooks

Subscribe to incoming events with webhooks.

A webhook is an API concept that allows manufacturer's Web APIs to send realtime updates using regular HTTP requests.

Because Homey connects to the internet through a router, your app is not publicly accessible from the internet. We provide a webhook-forwarding service to route all incoming webhooks to the right Homey.

Example

Let's say there is a Web API that sends a webhook when the user has turned on a light bulb. The webhook HTTP request might look like this:

POST /webhook/56db7fb12dcf75604ea7977d HTTP/1.1
Host: webhooks.athom.com
Content-Type: application/json; charset=utf-8

{
  "device_id": "aaabbbccc",
  "turned_on": true
}

1. Creating a Webhook

First, you need a unique Webhook URL to provide to the manufacturer's Web API. Sometimes this can be configured in a manufacturer-owned developer portal, other times this URL can be registered by making an API call.

Go to https://tools.developer.homey.app/webhooks and select New Webhook. Copy the ID & Secret to your app's /env.json file as WEBHOOK_ID and WEBHOOK_SECRET.

{
  "WEBHOOK_ID": "56db7fb12dcf75604ea7977d",
  "WEBHOOK_SECRET": "2uhf83h83h4gg34..."
}

2. Registering your Webhook

Secondly, in your app you you need to register a webhook listener that subscribes to this webhook.

/app.js
const Homey = require('homey');

class App extends Homey.App {
  async onInit() {
    const id = Homey.env.WEBHOOK_ID; // "56db7fb12dcf75604ea7977d"
    const secret = Homey.env.WEBHOOK_SECRET; // "2uhf83h83h4gg34..."
    const data = {
      // Provide unique properties for this Homey here
      deviceId: 'aaabbbccc',
    };

    const myWebhook = await this.homey.cloud.createWebhook(id, secret, data);

    myWebhook.on('message', args => {
      this.log('Got a webhook message!');
      this.log('headers:', args.headers);
      this.log('query:', args.query);
      this.log('body:', args.body);
    });
  }
}

module.exports = App;

3. Set-up your webhook URL

But how does the webhook service know that only this Homey may receive the webhook? There are three options, this is depending on how you register webhooks with the manufacturer's Web API.

Option 1 — Dynamic webhooks using Query Parameters

If the manufacturer's Web API allows you to dynamically register a webhook, for example by posting your webhook URL to an endpoint (POST https://myapi.com/webhook), you can attach Homey ID as query parameter homey to the webhook.

/app.js
const Homey = require('homey');
const fetch = require('node-fetch');

class App extends Homey.App {
  async onInit() {
    const homeyId = await this.homey.cloud.getHomeyId();
    const webhookUrl = `https://webhooks.athom.com/webhook/${Homey.env.WEBHOOK_ID}?homey=${homeyId}`;
    
    await fetch('https://myapi.com/webhook', {
      method: 'POST',
      headers: {'Content-Type': 'application/json'}
      body: JSON.stringify({ url: webhookUrl }),
    });
  }
}

module.exports = App;

Homey's webhook forwarding service understands that webhooks with a homey query parameter should be forwarded to that Homey.

When using query parameters, the data property is not required when registering your webhook using the this.homey.cloud.createWebhook() function.

Option 2 — Webhooks using Key Path properties

If the manufacturer's Web API requires you to specify the webhook URL beforehand, for example in their developer portal, then you can use the key path option when creating your webhook.

The key path describes which property from the webhook request contains the value that uniquely identifies a Homey. For example: headers['X-Device-Id'], note that these properties are case-sensitive. You can use body, headers and query in your Webhook key path filter.

A key path is an ECMAScript expression consisting only of identifiers (myVal), member accesses (foo.bar) and key lookup with literal values (arr[0] obj['str-value'].bar.baz).

Your key path has to match an incoming webhook against the data object provided in this.homey.cloud.createWebhook(id, secret, data). For example, an object with { $key: "aaabbbccc" } has to match the value aaabbbccc in the data object.

It is also possible to define the keypath as an array, for example: { $keys: ["aaa", "bbb"] } then the value in the key path either has to match value aaa or bbb.

The key path will be checked for each Homey that is registered to this webhook. All Homeys with matching data will receive the webhook in the webhook.on('message', ...) listener.

Example 1 — Headers

Let's use headers['X-Device-Id'] from the example above, and define $key as an array in the data object, as follows:

/app.js
const Homey = require('homey');

class App extends Homey.App {
  async onInit() {
    const id = Homey.env.WEBHOOK_ID; // "56db7fb12dcf75604ea7977d"
    const secret = Homey.env.WEBHOOK_SECRET; // "2uhf83h83h4gg34..."
    const data = {
      // Provide unique properties for this Homey here
      $keys: ["aaa", "bbb"],
    };

    const myWebhook = await this.homey.cloud.createWebhook(id, secret, data);

    myWebhook.on('message', args => {
      this.log('Got a webhook message!');
      this.log('headers:', args.headers);
      this.log('query:', args.query);
      this.log('body:', args.body);
    });
  }
}

module.exports = App;

The webhook HTTP request might look like this:

POST /webhook/56db7fb12dcf75604ea7977d HTTP/1.1
X-Device-Id: aaa
Host: webhooks.athom.com
Content-Type: application/json; charset=utf-8

{
  "turned_on": true
}

Homey's webhook service will match your webhook based on headers['X-Device-Id'] with value aaa (or bbb) and forward this to that Homey. If the webhook service received value ccc it will not be forwarded.

Example 2 — Body

Using the same data object provided in this.homey.cloud.createWebhook(id, secret, data) as example 1, you can also define your key path in the body of your webhook HTTP request.

The webhook HTTP request might look like this:

POST /webhook/56db7fb12dcf75604ea7977d HTTP/1.1
X-Device-Id: aaa
Host: webhooks.athom.com
Content-Type: application/json; charset=utf-8

{
  "X-Device-Id": "bbb",
  "turned_on": true
}

Option 3 — Static webhook URLs using the Cloud Function (legacy)

Cloud Functions are now considered legacy and have been made "read-only", consider changing to Query Parameter or Key Path based webhooks.

If you had previously defined a custom cloud function on https://tools.developer.homey.app/webhooks, it is possible to view this function in the developer portal.

Your function has to match an incoming webhook against the data object provided in this.homey.cloud.createWebhook(id, secret, data). For example an object with{ deviceId: "aaabbbccc" }.

The webhook sends the Device's ID in the webhook's body as device_id. Our cloud function therefore becomes:

return homey_data.deviceId === webhook_data.device_id;

This function will execute for each Homey that is registered to this webhook. All Homeys that returned true will receive the webhook in the webhook.on('message', ...) listener.

Last updated