# 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.

![](https://998911913-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MPk9cn4V7WnnKt7fbry%2Fuploads%2Fgit-blob-eb7f06e5f1c977be768b730924bfc10565f0a2cd%2Fwebhooks%20\(2\)%20\(1\).png?alt=media)

#### 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:

```http
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`.

```javascript
{
  "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.

{% tabs %}
{% tab title="JavaScript" %}
{% code title="/app.js" %}

```javascript
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;
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
{% code title="/app.mts" %}

```mts
import Homey from "homey";

type WebhookMessageArgs = {
  body: unknown;
  headers: Record<string, string>;
  query: Record<string, string>;
};

export default class App extends Homey.App {
  async onInit(): Promise<void> {
    const id: string = Homey.env.WEBHOOK_ID; // "56db7fb12dcf75604ea7977d"
    const secret: string = 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: WebhookMessageArgs) => {
      this.log("Got a webhook message!");
      this.log("headers:", args.headers);
      this.log("query:", args.query);
      this.log("body:", args.body);
    });
  }
}

```

{% endcode %}
{% endtab %}

{% tab title="Python" %}
{% code title="/app.py" %}

```python
import homey
from homey import app
from homey.cloud_webhook import WebhookMessage


class App(app.App):
    async def on_init(self) -> None:
        id = homey.env["WEBHOOK_ID"]  # "56db7fb12dcf75604ea7977d"
        secret = homey.env["WEBHOOK_SECRET"]  # "2uhf83h83h4gg34..."
        data = {
            # Provide unique properties for this Homey here
            "deviceId": "aaabbbccc",
        }

        my_webhook = await self.homey.cloud.create_webhook(id, secret, data)

        def on_message(message: WebhookMessage) -> None:
            self.log("Got a webhook message!")
            self.log("headers:", message.get("headers"))
            self.log("query:", message.get("query"))
            self.log("body:", message.get("body"))

        my_webhook.on_message(on_message)


homey_export = App

```

{% endcode %}
{% endtab %}
{% endtabs %}

## 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.

{% tabs %}
{% tab title="JavaScript" %}
{% code title="/app.js" %}

```javascript
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;
```

{% endcode %}

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.
{% endtab %}

{% tab title="TypeScript" %}
{% code title="/app.mts" %}

```mts
import Homey from "homey";
import fetch from "node-fetch";

export default class App extends Homey.App {
  async onInit(): Promise<void> {
    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 }),
    });
  }
}

```

{% endcode %}

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.
{% endtab %}

{% tab title="Python" %}
{% code title="/app.py" %}

```python
import requests
from homey import app
from homey.homey import Homey


class App(app.App):
    async def on_init(self) -> None:
        homey_id = await self.homey.cloud.get_homey_id()
        webhook_url = f"https://webhooks.athom.com/webhook/${Homey.env.WEBHOOK_ID}?homey={homey_id}"

        requests.post(
            webhook_url,
            json={"url": webhook_url},
        )


homey_export = App

```

{% endcode %}

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 `self.homey.cloud.create_webhook()` function.
{% endtab %}
{% endtabs %}

### 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.

{% hint style="info" %}
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`).
{% endhint %}

{% tabs %}
{% tab title="JavaScript" %}
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.
{% endtab %}

{% tab title="TypeScript" %}
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.
{% endtab %}

{% tab title="Python" %}
Your *key path* has to match an incoming webhook against the `data` object provided in `self.homey.cloud.create_webhook(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.
{% endtab %}
{% endtabs %}

#### 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:

{% tabs %}
{% tab title="JavaScript" %}
{% code title="/app.js" %}

```javascript
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;

```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
{% code title="/app.mts" %}

```mts
import Homey from "homey";

type WebhookMessageArgs = {
  body: unknown;
  headers: Record<string, string>;
  query: Record<string, string>;
};

export default class App extends Homey.App {
  async onInit(): Promise<void> {
    const id: string = Homey.env.WEBHOOK_ID; // "56db7fb12dcf75604ea7977d"
    const secret: string = 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: WebhookMessageArgs) => {
      this.log("Got a webhook message!");
      this.log("headers:", args.headers);
      this.log("query:", args.query);
      this.log("body:", args.body);
    });
  }
}

```

{% endcode %}
{% endtab %}

{% tab title="Python" %}
{% code title="/app.py" %}

```python
import homey
from homey import app
from homey.cloud_webhook import WebhookMessage


class App(app.App):
    async def on_init(self) -> None:
        id = homey.env["WEBHOOK_ID"]  # "56db7fb12dcf75604ea7977d"
        secret = homey.env["WEBHOOK_SECRET"]  # "2uhf83h83h4gg34..."
        data = {
            # Provide unique properties for this Homey here
            "$keys": ["aaa", "bbb"],
        }

        my_webhook = await self.homey.cloud.create_webhook(id, secret, data)

        def on_message(message: WebhookMessage) -> None:
            self.log("Got a webhook message!")
            self.log("headers:", message.get("headers"))
            self.log("query:", message.get("query"))
            self.log("body:", message.get("body"))

        my_webhook.on_message(on_message)


homey_export = App

```

{% endcode %}
{% endtab %}
{% endtabs %}

The webhook HTTP request might look like this:

```http
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

{% tabs %}
{% tab title="JavaScript" %}
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.
{% endtab %}

{% tab title="TypeScript" %}
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.
{% endtab %}

{% tab title="Python" %}
Using the same `data` object provided in `self.homey.cloud.create_webhook(id, secret, data)` as example 1, you can also define your *key path* in the body of your webhook HTTP request.
{% endtab %}
{% endtabs %}

The webhook HTTP request might look like this:

```http
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)

{% hint style="danger" %}
Cloud Functions are now considered legacy and have been made "read-only", consider changing to Query Parameter or Key Path based webhooks.
{% endhint %}

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:

```javascript
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.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://apps.developer.homey.app/cloud/webhooks.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
