Invite-only beta free, entry by code Get a code

Notifications

The cloud notices on its own when a camera stops checking in, and tells you – by email, in Telegram or with a request to your own URL. Nothing has to be open for this: the cloud reports the outage, not a browser tab. When the camera is back, you hear about that too.

What you get without setting anything up

An email to your account's address, about your own cameras. Nothing to switch on: if a camera drops and you have never opened Notifications, the email still comes.

Cameras someone else shared with you are left out by default – a guest who was shown a backyard did not ask for an alarm about somebody else's router. Turn them on in the panel, all at once or per camera.

When the cloud considers a camera gone

A camera checks in every twenty seconds. A minute and a half of silence and it counts as gone. The cloud then waits a little longer (two minutes by default, adjustable in the panel) before it writes. A camera that rebooted and came back within that time does not bother you at all – neither about the outage nor about the return.

"Back online" goes only where "offline" went, and says how long the camera was away.

If several cameras drop together – behind one router, say – you get one message with a list, not one per camera. And if most cameras of the whole cloud fall silent at once, that is almost certainly trouble on our side rather than yours: we do not broadcast that, we fix it.

Telegram

Panel → Notifications → "Connect Telegram". Open the link and press Start – the chat is linked. From a computer, scan the link with your phone. The link is single-use and lives ten minutes.

To get notifications in a group, press "Add to a group" and pick the group: the bot links it to your account.

Under an offline message there are two buttons: "Open" goes to the camera's page in the panel (you need to sign in – a forwarded message shows the camera to nobody), "Mute for an hour" silences that camera for an hour. /stop in the chat turns notifications there off. Blocked the bot or removed it from the group – the channel switches itself off, and the panel says why.

In the app

The app on your phone gets notifications as push messages – even when it is closed. Push arrives with the next app update; the cloud is ready for it already.

Nothing to set up: after you sign in, the app tells the cloud where to send, and signing out takes it back. Each phone shows up among the panel's channels as "App", and you can switch it off there – the app will not override that. Signed into another account on the phone – the previous account's notifications stop going to it.

Push travels through Firebase (Google) or, on phones without Google services, through UnifiedPush – the ntfy app, for example. Only the camera's name and the time reach the lock screen: no addresses, no viewing links. Cameras that drop together arrive as one push.

Uninstalled the app – the cloud learns it from the push service and stops sending there, and the panel says why. Installed it again and signed in – notifications come back on their own.

Settings

In the panel's Notifications section:

  • Where they go – email, Telegram chats, phones with the app, and webhooks. Any of them can be switched off or tried with "Test"; the account's email can be switched off but not removed.
  • When to tell – how long to wait before reporting an outage; whether to report returns; whether to report cameras shared with you; the language of the messages.
  • Devices – switch notifications off for one camera, for example one that is powered down at night.
  • Recent notifications – what was sent when, and whether it arrived.

Webhook

For your own automation – Home Assistant, n8n, your own bot – the cloud sends the event to your URL. Panel → Notifications → "Webhook": enter an https URL, and the panel shows the signing secret once.

The URL has to be public and https: the cloud does not call local or internal addresses.

The request is a POST with JSON:

{
  "id": "evt_0192f3a1…",
  "type": "node.offline",
  "at": "2026-10-12T15:42:07Z",
  "node": { "id": "node_…", "name": "Porch", "kind": "Camera" },
  "lastSeenAt": "2026-10-12T15:40:31Z",
  "offlineSince": null,
  "count": null,
  "url": "https://cloud-panel.openipc.ru/#device=node_…"
}

Types: node.offline – the camera is gone, node.online – it is back (offlineSince says since when it was away), test – the "Test" button.

Headers:

HeaderWhat it holds
X-Streq-Eventthe event type
X-Streq-Deliverydelivery number – a retry carries the same number
X-Streq-Timestampwhen it was sent, Unix seconds
X-Streq-Signaturesha256= and HMAC-SHA256 of <timestamp>.<body> with the signing secret

Delivery is at least once: if your URL does not answer 2xx, the cloud retries later with the same X-Streq-Delivery. Drop repeats by it. A URL that refuses twenty deliveries in a row switches itself off; turn it back on in the panel once it is fixed.

Checking the signature in Python:

import hashlib, hmac, time

def valid(secret: str, headers, body: bytes) -> bool:
    ts = headers["X-Streq-Timestamp"]
    if abs(time.time() - int(ts)) > 300:
        return False
    mac = hmac.new(secret.encode(), ts.encode() + b"." + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest("sha256=" + mac, headers["X-Streq-Signature"])

In Node.js:

import { createHmac, timingSafeEqual } from 'node:crypto';

export function valid(secret, headers, body /* Buffer */) {
  const ts = headers['x-streq-timestamp'];
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
  const mac = 'sha256=' + createHmac('sha256', secret).update(`${ts}.`).update(body).digest('hex');
  const got = Buffer.from(headers['x-streq-signature'] ?? '');
  return got.length === mac.length && timingSafeEqual(got, Buffer.from(mac));
}

In Go:

func valid(secret string, h http.Header, body []byte) bool {
	ts := h.Get("X-Streq-Timestamp")
	sec, err := strconv.ParseInt(ts, 10, 64)
	if err != nil || math.Abs(float64(time.Now().Unix()-sec)) > 300 {
		return false
	}
	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write([]byte(ts + "."))
	mac.Write(body)
	want := "sha256=" + hex.EncodeToString(mac.Sum(nil))
	return hmac.Equal([]byte(want), []byte(h.Get("X-Streq-Signature")))
}

The signature is over the body exactly as it arrived, byte for byte: do not parse and re-serialise the JSON before checking it.