Webhooks.
Ultra and Beast plans can register one HTTPS endpoint per device in Settings → Webhooks. Registration takes a URL and a bearer token of your choosing; every alert that device would be pushed is then POSTed to your endpoint - authenticated, retried, and logged.
The delivery
Each alert is an HTTP POST with a JSON body and these headers:
POST https://your-endpoint.example/notifi Content-Type: application/json Authorization: Bearer <your token> (the token you set at registration) X-NotiFi-Timestamp: 1783608725 (unix seconds, when this attempt was signed) X-NotiFi-Signature: 3f1a… (hex HMAC-SHA256, optional to verify) X-NotiFi-Delivery: whd_8f3k2m1x9c4v7b5n (unique per delivery - dedupe on this) X-NotiFi-Event: in_stock (in_stock | price_drop | product_added | test)
Your endpoint has 5 seconds to respond with any 2xx status. Respond first, then do slow work asynchronously.
Payload
{
"event": "in_stock",
"delivery_id": "whd_8f3k2m1x9c4v7b5n",
"occurred_at": "2026-07-08T14:32:05.000Z",
"product": {
"slug": "unifi-express-7",
"title": "UniFi Express 7",
"url": "https://store.ui.com/us/en/products/unifi-express-7?variant=ux7-us",
"image_url": "https://cdn.ui.com/…/ux7.png"
},
"variant": { "sku": "UX7-US", "name": "UniFi Express 7" },
"price": { "amount": 19900, "currency": "USD" }
}- price.amount is in minor units (cents for USD). price is null when the storefront does not surface one.
- price_drop events add a previous_price object in the same shape.
- product.url is the live store.ui.com page with the variant preselected - ready to open or forward.
Authenticating deliveries
Every delivery carries the bearer token you chose at registration in the Authorization header. Check it with a constant-time comparison and reject anything else - that is the whole contract for a typical receiver:
import { timingSafeEqual } from "node:crypto";
function authorized(req: { headers: Headers }): boolean {
const header = req.headers.get("Authorization") ?? "";
const expected = `Bearer ${process.env.NOTIFI_BEARER_TOKEN}`;
const a = Buffer.from(header);
const b = Buffer.from(expected);
return a.length === b.length && timingSafeEqual(a, b);
}
app.post("/notifi", express.json(), (req, res) => {
if (!authorized(req)) return res.status(401).end();
if (req.body.event === "in_stock") {
// e.g. flash the office lights, ping a Discord channel, open a browser…
}
res.status(200).end(); // respond fast; do slow work async
});Replace the token any time by saving the webhook again with a new one in Settings → Webhooks.
Verifying signatures (optional)
Deliveries are additionally signed: the X-NotiFi-Signature header is HMAC-SHA256 over `${timestamp}.${body}`, hex-encoded, using the signing secret shown in the app's webhook settings (viewable and rotatable there any time). If you want integrity and replay protection on top of the bearer check, verify against the raw request body - the bytes as sent, not a re-serialization:
import { createHmac, timingSafeEqual } from "node:crypto";
function verifySignature(req: { headers: Headers; rawBody: string }, secret: string): boolean {
const timestamp = req.headers.get("X-NotiFi-Timestamp");
const signature = req.headers.get("X-NotiFi-Signature");
if (!timestamp || !signature) return false;
// Reject stale timestamps to block replays (5 minutes is plenty).
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const expected = createHmac("sha256", secret)
.update(`${timestamp}.${req.rawBody}`)
.digest("hex");
const a = Buffer.from(signature);
const b = Buffer.from(expected);
return a.length === b.length && timingSafeEqual(a, b);
}Retries and failure handling
- A non-2xx response or timeout retries up to 5 attempts with backoff: 5 min, 15 min, 45 min, then 2 h.
- Deliveries are signed per attempt - the timestamp header is fresh on each retry, the body is byte-identical.
- After 10 consecutive exhausted deliveries the webhook is disabled automatically; re-enable it from Settings once your endpoint is healthy.
- The app shows a delivery log with status codes and errors for every attempt, plus a test button that sends a test event on demand.
Things people point it at
Home Assistant automations (flash the lights on a restock), a Discord or Slack relay for the homelab server, an n8n or Node-RED flow, or a tiny script that opens the product page on your desktop the moment the POST lands.
Webhooks are included with Ultra and NotiFi Beast. One endpoint per device, managed entirely in the app - no dashboard, no API keys to babysit.