Skip to content

Webhooks

Suta sends a JSON POST to each webhook when an alert opens or resolves, and when you select Send test. To add a webhook, see Alerts and webhooks.

Field Description
type alert.opened, alert.resolved or test.
text A one-line summary. Slack displays it as the message.
alert.kind offline, memory, storage, temperature or agent_update_failed.
alert.status open or resolved.
alert.details The reading and the limit that triggered the alert.
device The device’s ID and name.
workspace The workspace’s ID and name.
url A link to the device in Suta.
alert.opened
{
"type": "alert.opened",
"text": "[Suta] Alert: Lobby camera storage is 96% full (limit 90%)",
"alert": {
"id": "8c1f…",
"kind": "storage",
"status": "open",
"summary": "Lobby camera storage is 96% full (limit 90%)",
"details": { "percent": 96.0, "threshold": 90.0 },
"opened_at": "2026-09-23T10:00:00Z",
"resolved_at": null
},
"device": { "id": "…", "name": "Lobby camera" },
"workspace": { "id": "…", "name": "Acme Robotics" },
"url": "https://app.suta.dev/devices/…"
}
Header Value
Content-Type application/json
User-Agent Suta-Alerts/1
Suta-Signature t=<unix seconds>,v1=<hex HMAC>
POST /hooks/suta HTTP/1.1
Content-Type: application/json
User-Agent: Suta-Alerts/1
Suta-Signature: t=1790157600,v1=6f1d…

v1 is an HMAC-SHA256 of <t>.<raw body>, keyed with the webhook’s signing secret, as lowercase hex.

To verify a request:

  1. Read the raw request body before parsing it as JSON.
  2. Split Suta-Signature into t and v1.
  3. Compute the HMAC of t, a ., and the raw body.
  4. Compare it with v1 in constant time.
  5. Reject the request if t is more than five minutes old.
import crypto from "node:crypto";
export function verify(secret, header, rawBody) {
const { t, v1 } = Object.fromEntries(
header.split(",").map((part) => part.split("=")),
);
const expected = crypto
.createHmac("sha256", secret)
.update(`${t}.${rawBody}`)
.digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
return (
fresh &&
v1.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected))
);
}
Try it: verify a signature

Everything runs in your browser. Nothing you paste here is sent anywhere.

Paste a secret, header and body to check them.
  • Suta treats any 2xx response as delivered.
  • A different status, a timeout after 10 seconds or a connection error is retried after 1, 2, 4, 8 and 16 minutes, then marked as failed.
  • Delivery is at least once, so the same alert can arrive more than once. Use alert.id and type to ignore duplicates.
  • Suta doesn’t follow redirects, and only sends to public HTTPS addresses.