---
title: "Send events to your own endpoint"
description: "Post IoTFlows events to an HTTPS URL you control. Add a hosted endpoint under Organization Settings with its URL, description and the signing secret you choose, subscribe it to the events your system acts on, and send each event's real test payload to your URL before you write the handler. The selectable event list is served by IoTFlows and read off the screen, so this page shows how to read it rather than reproducing it."
category: "Send alerts elsewhere"
source_url: "https://www.iotflows.com/docs/alerts/webhooks/"
---
# Send events to your own endpoint

Post IoTFlows events to a URL you control, and know what arrives before you write the handler.

A *hosted endpoint* is an HTTPS URL of your own that IoTFlows posts events to, as an HTTP POST with a JSON body. Endpoints belong to the organization rather than to one machine, so you add one once and choose which events it receives. They are configured self-serve in Organization Settings and are unrelated to REST API access, which IoTFlows grants by hand.

**Prerequisites.** Organization Owner or Organization Administrator, see [Roles and permissions](/docs/admin/roles-reference/). An HTTPS URL that accepts a POST and returns a response, reachable from the public internet.

You do not need a hosted endpoint to reach Slack, Microsoft Teams or Discord. Each has a purpose-built integration that needs no code, see [Send alerts to Slack, Teams, or Discord](/docs/alerts/chat-integrations/). Build an endpoint when the event has to reach something that is not a chat channel: a CMMS, an ERP, a dashboard of your own, or a queue.

## What a hosted endpoint is
An endpoint is three things: a URL, a *signing secret* you choose, and a set of *subscriptions*, where a subscription is one event type switched on for that one endpoint. Nothing is delivered until at least one subscription is on.

Endpoints and machine event rules are separate systems. A rule on a machine's **Event Notifications** grid reaches people through email, push, SMS, the log, a work order and chat integrations, see [Overview: alerts and integrations](/docs/alerts/overview/#channels). A hosted endpoint subscribes to organization-level events and carries no per-machine switch, so there is nothing to turn on per asset.

You can add more than one endpoint, each with its own secret and its own subscriptions. Split them when two systems want different events, for example a maintenance system that wants faults and a reporting service that wants job completions.

## Add an endpoint
1. Click the gear icon at the top right of the header. It opens Organization Settings at `/settings/organization?select=settings`.
2. Click **Webhooks** in the sub-navigation, at `/settings/organization?select=webhooks`. The page is headed **Hosted endpoints** and lists every endpoint in the organization by URL.
3. Click **Add endpoint**. The **Listen to IoTFlows Events** dialog opens.
4. Fill in the three fields below.
5. Click **Verify**. IoTFlows saves the endpoint and confirms with **Endpoint created**, then replaces the fields with **Select events to listen to** and a switch per event.
6. Switch on the events this endpoint should receive, then click **Close**. The endpoint was saved at step 5 and each switch saves as you flip it, so there is nothing left to submit.

The switch list inside the dialog carries event names only. The descriptions and the **Test** buttons are on the endpoint's row afterwards, see [Choose which events to subscribe to](#subscribe).

![The Webhooks tab of Organization Settings, headed Hosted endpoints with an Add endpoint button at the top right. A table under a URL column lists two endpoints, each row ending in a pencil icon and a trash icon](/images/alerts/alr-hook-01.webp)

*Hosted endpoints. Configured in Organization Settings by an admin.*

### Endpoint fields
| Field | Required | Notes |
|---|---|---|
| `Endpoint URL (HTTP POST)` | Yes | The full HTTPS URL IoTFlows posts to, for example `https://ops.example.com/hooks/iotflows`. The dialog does not check the URL before submitting it, so confirm a new endpoint with a test payload rather than assuming a typo would have been caught |
| `Endpoint Description` | No | Free text, shown on the expanded row. Name the system that consumes it, for example `Maintenance CMMS, production` |
| `Endpoint Secret` | No, but treat it as required | The value IoTFlows sends back in the `iotflows-secret` header of every delivery. See [The signing secret](#secret) |

**Verify is the save button.** It creates the endpoint and confirms with **Endpoint created**. It does not show you what your handler will receive or whether your handler handled it. For that, send a test payload, see [Send a test payload](#test).

![The Listen to IoTFlows Events dialog. Three stacked text fields are labeled Endpoint URL (HTTP POST), Endpoint Description and Endpoint Secret, with numbered callouts 1, 2 and 3 against them. A Close button sits at the bottom left and a Verify button at the bottom right](/images/alerts/alr-hook-02.webp)

*The Listen to IoTFlows Events dialog, with the endpoint URL, description and secret fields.*

## The signing secret
The *signing secret* is a string you choose when you create the endpoint. IoTFlows sends it back in the `iotflows-secret` header on every delivery, including test payloads, so your handler can reject a POST that did not come from IoTFlows.

Set one. The field accepts an empty value, and an endpoint with no secret has no way to tell an IoTFlows delivery from anything else that finds the URL. Use a long random string, for example the output of `openssl rand -hex 32`, and store it where your handler reads its own configuration.

Your handler compares the header with its stored copy and drops the request when they differ:

```js
// Express handler for an IoTFlows hosted endpoint.
const express = require('express')
const app = express()

app.post('/hooks/iotflows', express.json(), (req, res) => {
  if (req.get('iotflows-secret') !== process.env.IOTFLOWS_WEBHOOK_SECRET) {
    return res.status(401).send('bad secret')
  }
  console.log('event received', req.body)
  res.status(200).send('ok')
})

app.listen(3000)
```

A delivery that passes the check prints the event and returns `200`:

```text
event received { data: { ... } }
```

To read the secret back later, expand the endpoint's row on the **Hosted endpoints** page. **Secret** is masked; click **Reveal** to show it and **Hide** to mask it again. The field is read-only there, so change a secret from the edit dialog, see [Edit or remove an endpoint](#edit).

Because the secret can be read back, treat it as shared with everyone who can open Organization Settings. Give each endpoint its own secret rather than reusing a key your systems already rely on elsewhere.

![The Secret row of an expanded endpoint. The value is masked as dots in a read-only field, with a blue Reveal link directly beneath it](/images/alerts/alr-hook-03.webp)

*The signing secret, masked by default.*

## Choose which events to subscribe to
Every endpoint on **Hosted endpoints** opens expanded. Under **Events**, each event IoTFlows can send is listed with its description and a **Subscribe** switch. Click the URL, or the chevron beside it, to collapse a row you are not working on.

Flip a switch and the change saves immediately. IoTFlows confirms with **Subscribed to** or **Unsubscribed from**, followed by the event's name. There is no **Save** button, and closing the page mid-list leaves the switches you already flipped in place.

**This page does not reproduce the event list.** The list is served by IoTFlows and can change without a release, and it cannot be exported, so any table printed here would be a copy that silently goes stale. Read it off the screen instead: the name and the one-line description in the **Events** column are the whole of what each event is.

Subscribe to the smallest set of events your system acts on. Every subscription is a delivery your endpoint has to accept and answer, and an endpoint that times out on events it ignores looks like an outage to whoever is watching it. If you cannot name what your handler does with an event, leave it off.

![The Events panel of an expanded endpoint. A header row reads Events on the left and Subscribe on the right. Each row below shows an event name with its description underneath, then a Test button and a toggle switch. Several toggles are on](/images/alerts/alr-hook-04.webp)

*The subscribable events. This list is served by IoTFlows and read off the screen; it cannot be exported.*

## Send a test payload
Every event row carries a **Test** button, next to its switch. Click it to see the exact JSON IoTFlows sends for that event type, then click **Test** in the dialog to post that JSON to your endpoint.

Use it before you write the handler. The dialog shows the real shape of the body, so you can build against the field names rather than guessing them, and it states the header your handler must check: the secret arrives as `iotflows-secret`.

A test tells you two things and no more: that your URL answered, and what the body looks like. IoTFlows confirms a delivery with **Message was sent successfully!**, and reports a URL that did not answer as **No response from server**. Whether your handler did the right thing with the body is a question for your own logs.

The **Test** button works whether or not the event is subscribed, so you can check an endpoint end to end before you switch anything on.

![A dialog headed Test, the event's name, and Event, above a line reading This is the data that you will be receiving with the defined secret as iotflows-secret header. Below it, a dark code block shows the formatted JSON body for that event type, with Cancel and Test buttons underneath](/images/alerts/alr-hook-05.webp)

*A test payload for one event type, so you can build against the real shape.*

## Edit or remove an endpoint
Each row on **Hosted endpoints** ends in two controls.

- **The pencil** opens the same three fields, filled in. Change the URL, the description or the secret, then click **Reverify**. IoTFlows confirms with **Endpoint updated**. The new secret is used on the next delivery, so change your handler's copy in the same window or you will start rejecting valid deliveries.
- **The trash** opens a **Delete Endpoint** confirmation naming the URL. Click **Delete** and the endpoint goes, along with its subscriptions. IoTFlows confirms with **Webhook has been removed**.

Neither dialog touches subscriptions. **Reverify** saves the three fields and nothing else, and the edit dialog does not show the event switches at all.

Move a URL rather than replacing an endpoint. Editing keeps the subscriptions you have already chosen; deleting and re-adding means switching every event back on by hand.

## Verify deliveries
IoTFlows shows you what it sent, not what your server did with it, so confirm deliveries at your end.

1. Send a test payload for one subscribed event, see [Send a test payload](#test).
2. Look for the request in your own server log, and check that the `iotflows-secret` header matched.
3. If **No response from server** appears, the URL did not answer. Check that it is reachable from the public internet, serves a valid TLS certificate, and returns a response rather than holding the connection open.
4. If your handler ran but did nothing, compare the body you received with the test payload for that event type. They are the same shape.

An endpoint that stops receiving after a deploy is usually a transport problem rather than a subscription one. The subscriptions survive a deploy; an expired certificate, a moved path and a firewall rule do not, and all three read the same way from the IoTFlows side, which is silence. Re-run the test before you touch any switches.

If an alert that should have reached your endpoint never fired in the first place, the problem is on the rule and not on the endpoint, see [Troubleshoot alerts you did not receive](/docs/alerts/troubleshoot-alerts/).

## See also
- [Send alerts to Slack, Teams, or Discord](/docs/alerts/chat-integrations/)
- [Event types and channels](/docs/alerts/event-types-reference/)
- [Troubleshoot alerts you did not receive](/docs/alerts/troubleshoot-alerts/)
- [Overview: alerts and integrations](/docs/alerts/overview/)
- [Roles and permissions](/docs/admin/roles-reference/)
