Skip to main content

Webhooks

Webhooks are the recommended way to react to what happens in a home. Instead of polling the API on a timer, you register an HTTPS endpoint that you control and Minut sends an HTTP POST to it as each event is generated. This fits the way Minut works — devices only talk to the backend periodically (see Architecture), so there is nothing to gain from polling, and a webhook delivers the event as soon as the backend has it.

This page covers registering, verifying and managing user webhooks (the /webhooks endpoints). Every request needs a valid access token — see Authentication for Users.

Register a webhook

POST /webhooks with the URL you want events delivered to and the list of events you care about:

curl --request POST 'https://api.minut.com/v8/webhooks' \
--header 'Authorization: Bearer ACCESS_TOKEN' \
--header 'Content-Type: application/json' \
--data '{
"url": "https://example.com/minut/webhook",
"events": ["disturbance_first_notice", "disturbance_ended"],
"token": "a-secret-you-choose"
}'
  • url — must be a publicly reachable HTTPS endpoint.
  • events — the event types to subscribe to. See the Events Reference for the full list. Pass ["*"] to receive every event type. Note that * must be the only element in the array — combining it with named event types is rejected with 400 wildcard events can only be used with a single event.
  • token (optional) — an arbitrary string you choose. Minut sends it back on every delivery (see Verifying deliveries), so you can confirm a request really came from your webhook.

The response is the created webhook:

{
"hook_id": "6628f1a2b167b70039f80800",
"created_at": "2026-05-29T10:00:00.000Z",
"url": "https://example.com/minut/webhook",
"events": ["disturbance_first_notice", "disturbance_ended"],
"token": true,
"hook_secret": "a3f1c8d2e9b047561234abcd5678ef901234abcd"
}
caution

hook_secret is returned only in this create response — it is never included again when you list webhooks. Store it now; you need it to verify the HMAC signature on incoming deliveries.

List your webhooks

GET /webhooks returns every webhook registered for the authenticated user:

curl --request GET 'https://api.minut.com/v8/webhooks' \
--header 'Authorization: Bearer ACCESS_TOKEN'
{
"hooks": [
{
"hook_id": "6628f1a2b167b70039f80800",
"created_at": "2026-05-29T10:00:00.000Z",
"url": "https://example.com/minut/webhook",
"events": ["disturbance_first_notice", "disturbance_ended"],
"token": true
}
]
}

Delete a webhook

DELETE /webhooks/{hook_id} stops deliveries and removes the webhook:

curl --request DELETE 'https://api.minut.com/v8/webhooks/HOOK_ID' \
--header 'Authorization: Bearer ACCESS_TOKEN'

Send a test event

POST /webhooks/{hook_id}/ping triggers a ping delivery to the webhook's URL, so you can confirm your endpoint is reachable and your verification logic works without waiting for a real event:

curl --request POST 'https://api.minut.com/v8/webhooks/HOOK_ID/ping' \
--header 'Authorization: Bearer ACCESS_TOKEN'
caution

At the moment the ping is always sent to your first registered webhook, regardless of the hook_id in the path. If you have several webhooks, a ping isn't a reliable way to test a specific one — it's most useful when you have a single webhook registered.

What Minut sends to your endpoint

For each event, Minut makes an HTTP POST to your url with these headers:

HeaderValue
Content-Typeapplication/json; charset=utf-8
User-Agentminut-webhook-service
Authorizationthe raw token you set when registering, sent verbatim without a Bearer prefix (absent if you didn't set one)

and a JSON body shaped like this:

{
"hook_id": "6628f1a2b167b70039f80800",
"hook_token": "a-secret-you-choose",
"event": {
"id": "5ffffeecb167b70039f8076f",
"type": "disturbance_first_notice",
"user_id": "5ffff21db167b70039f806f8",
"created_at": "2026-05-29T10:05:00.708Z",
"home_id": "5ffff22ab167b70039f806fe"
},
"hook_digest": "3a7bdb1f...hex"
}

The event object delivered by webhooks is a reduced representation — not the same shape as the events endpoints. id, type, created_at, and user_id are always present; home_id is additionally included for home-scoped events, and sensor-related events also carry device_id, sensor_value, and sensor_threshold. Note in particular that the identifier is id (the events endpoints use event_id), and fields like text_params or actions are not included. See the Events Reference for which fields accompany each event type.

Verifying deliveries

Your endpoint is public, so verify that a delivery actually came from Minut before acting on it. There are two independent checks:

  1. The token. Compare the Authorization header (and/or hook_token in the body) against the token you registered. If it doesn't match, reject the request.
  2. The HMAC signature. hook_digest is an HMAC-SHA256, hex-encoded, computed over the JSON-serialized event object using your hook_secret as the key. Recompute it and compare:
import { createHmac, timingSafeEqual } from 'crypto'

function isFromMinut(body, hookSecret) {
const expected = createHmac('sha256', hookSecret)
.update(JSON.stringify(body.event))
.digest('hex')

const a = Buffer.from(expected)
const b = Buffer.from(body.hook_digest ?? '')
return a.length === b.length && timingSafeEqual(a, b)
}

Responding and delivery semantics

  • Respond quickly with a 2xx status. Do any heavy processing asynchronously — Minut applies a short timeout to the request, and a slow endpoint will be treated as a failed delivery.
  • Webhook delivery is best-effort (at-most-once). A single delivery is attempted per event with no retry queue, so if your endpoint is down, returns a non-2xx, or times out, that event is not redelivered. If you need to be sure you haven't missed anything, periodically reconcile against Fetching Events.
  • When you reconcile, you may re-encounter an event you already received via webhook — deduplicate on the event id so reprocessing is harmless.

Good to know

  • Prefer subscribing to the specific events you need over ["*"]; it keeps the traffic to your endpoint relevant and easier to reason about.
  • Use a fresh, hard-to-guess token per webhook, and keep your hook_secret server-side.
  • For organization-level integrations that manage many homes, see the organization webhook endpoints in the API Reference.