Skip to content

SafeCall ntfy / Keryx alert topics ​

Canonical inventory of the ntfy topics SafeCall publishes and Keryx can subscribe to.

Code source of truth: server/src/ntfy_topics.ts. Admin-facing labels: admin.ntfy_topics.{id}_label in the UI catalogs. Publisher: server/src/ntfy.ts.

This page is for implementation and product work on Keryx and SafeCall notification cadence. Ordinary administrators should keep seeing alert categories, not topic IDs.

Naming ​

PiecePatternExample (server_id = TST000)
Topic idstable enumsos
ntfy topic name{server_id}-{topic_id}TST000-sos
Admin / QR labeltranslated catalog stringPanic button alerts

GET /api/v1/features returns all nine staff categories as ntfy_topics. Keryx QR generation (POST /api/v1/keryx/qr) includes only the categories the admin left checked. Mobile Access default-checks SOS, watches, and Daily status. Condition topics (sensors, batteries, notseen, calibration, health) are optional; Overview loads them from the status API. test and debug access are unchecked in the collapsed Admin features section and require an explicit warning confirmation.

Keryx renders Overview cards for subscribed moment categories plus every unmuted condition category. Daily status is a wake, not a card. Each category has a phone-side notification policy and optional per-known-device overrides. Existing phones need a new QR after this cadence change.

Staff categories ​

Idntfy nameAdmin label (EN)Admin label (CS)What it isTypical triggerTitle / bodyTagsPriority
sensors{server_id}-sensorsSensor threshold alertsPrah senzoruTemperature / humidity out of rangeMQTT adv1 or sData when a configured range is exceededTitle = reason (Temperature (x°C) out of range); body adds for device: {name} ({mac or asset_uid})warning,thermometerurgent
sos{server_id}-sosPanic button alertsPoplachové tlačítkoPanic button pressMQTT adv4 on a panic asset (beacon_is_pushed)Title = KISS text or Panic button pushed; body adds devicewarning,rotating_lighturgent
watches{server_id}-watchesWatch button alertsPoplachové tlačítko hodinekWatch button pressMQTT adv4 on a watch assetTitle = KISS text or Watch button pushed; body adds devicewarning,rotating_lighturgent
batteries{server_id}-batteriesLow battery alertsSlabá baterieLow battery on button/sensor adv8MQTT adv8 when vbatt < 2800 mVTitle = Low battery (xV); body adds devicewarning,batteryurgent
notseen{server_id}-notseenDevice not seen alertsZařízení neviděnoPanic, watch, or sensor stopped reportingInactivity timer in cache.tsTitle = Not seen for more than {n} minutes; body adds devicepanic/watch: warning,rotating_light; sensor: warning,thermometerurgent
calibration{server_id}-calibrationCalibration expiryVypršení kalibraceAssigned sensor (or untyped) device past calibration intervalHourly checker in calibration.tsTitle = Calibration expired: {asset or MAC}; body = asset uid, MAC, type, last calibrated, intervalwarning,wrenchdefault
test{server_id}-testNotification pipeline testsTesty kanálu oznámeníProve the ntfy path without KISSDashboard Recheck; Watch/Panic Test Send (is_test MQTT)Recheck: Dashboard ntfy recheck / SafeCall {server_id} notification pipeline test. Test Send: Notification pipeline test / adds device and [adv]Recheck: white_check_mark. Test Send: tags of the fake adv typeRecheck: min. Test Send: urgent
health{server_id}-healthInstallation health alertsStav instalaceInstall / fleet problems (queryable; not a phone wake)Status snapshot transitionsStored on the status API; not posted to this topic——
status{server_id}-statusDaily statusDenní stavOnce-a-day Overview reminderDaily digest bucket (default 09:00 Europe/Prague)Title = {server_id}: N conditions need attention or All clear; body tells the user to open Overview (no device list)warning,clipboard or white_check_markdefault / min

English catalog labels are what the admin UI and QR labels map use. The English fallbacks inside ntfy_topics.ts differ slightly (Sensor alerts, Device not-seen alerts, Install health) and are only used when a translation key is missing.

How often each topic can fire ​

SafeCall separates wakes from queryable state. SOS, watch, and pipeline tests are immediate ntfy wakes. Sensors, batteries, not-seen, calibration, and install health stay on the installation SQLite store, are PUT to keryx-status on the notify host, and contribute at most one {server_id}-status ntfy per day (skipped when the site is already all-clear). Phones GET the snapshot; they are not the database.

HTTP 429 from ntfy starts a global backoff (default 30 s, or Retry-After). Immediate SOS/watch/test alerts retain the existing retry queue. The daily status wake is retried until ntfy accepts it. Test publishes are not queued and intentionally bypass every cooldown.

IdDefault server policySubject / groupingEffective behavior
sensorsDaily status (mute-able)Stable assetEvaluated locally; no per-event ntfy. Snapshot items + daily count
sosImmediateOne configurable 40 s per-device window shared by event logging, KISS, and ntfyMultiple gateways reporting one press produce one event; a later press after the window is a new moment
watchesImmediateSame 40 s button window as SOSSame as SOS
batteriesDaily status (mute-able)Stable assetSame as sensors
notseenDaily status (mute-able)Stable asset and device typeSeeing the device again resolves the condition. Fridge timeout remains max(10 min, 2 × update_interval)
calibrationDaily status (mute-able)Device assignment / stable assetHourly checker updates state; never becomes dozens of ntfy parts
testImmediate, exemptUnique test occurrenceEvery Recheck / Test Send; no Keryx cadence control
healthDaily status (mute-able)Infrastructure key or fleet issueStaff phones query Overview. Vendor ops is a separate topic (server_ntfy_ops_*)
statusOne ntfy per daily bucket when active_count > 0 (or a quiet all-clear after a day that had issues)SiteCounts only. Tap opens Keryx Overview

The daily bucket is anchored at 09:00 Europe/Prague by default. Timezone and time are editable in SafeCall settings. Per-category mute omits that category from the snapshot and the daily count. server_ntfy_enabled is the master switch for SOS/watch/test and the daily wake. Status PUT continues when ntfy is on so opening the app still works.

Vendor ops copies of health are independent (server_ntfy_ops_enabled + non-empty server_ntfy_ops_topic) and do not post to {server_id}-health.

SafeCall alert record ​

The ntfy title and first body section remain readable in the stock ntfy app. SafeCall appends a deterministic SafeCall alert data v1 section containing the category, moment/condition kind, phase, alert/state intent, stable subject, device identity, timestamp, sequence, and optional digest items. Keryx hides that transport block and uses it to update cards and apply per-device policy. Messages without the record remain visible as legacy history but cannot claim authoritative current state.

Minting a Keryx QR PUTs the current condition snapshot to keryx-status so a newly scanned phone can leave Waiting for status without waiting for the next daily wake. It does not POST silent 3 KiB×N ntfy snapshots. Dashboard Recheck and Watch/Panic Test Send go to {server_id}-test, not -sos.

Keryx Active is SOS, watches, and test only. Condition ntfy from an old install is ignored. Daily -status tap opens Overview. FCM/APNs banners for condition topics are suppressed on the phone.

Scanning a new QR wipes local history and category state. Overview stays on Waiting for status until Keryx GETs the snapshot from keryx-status. Existing phones need a new QR for status_url and {server_id}-status. ACL notify hosts must mint a real ntfy subscribe token; tk_local_ QRs cannot poll and need a regenerated QR after token-manager is configured. FCM/APNs banners can still appear in that broken state because topic wake does not use the QR token.

Keryx-first compatibility rollout ​

Ship Keryx's dual parser and local policy support before enabling the new SafeCall envelope and cadence in a deployed installation. During the overlap:

  • New Keryx parses SafeCall alert data v1, updates category cards, and applies local cadence. Legacy bodies stay in history only.
  • Older Keryx and stock ntfy keep showing the human title and summary.
  • Multiplexing reduces Keryx's own SSE count. It does not reduce connections opened by third-party stock ntfy subscribers.

MQTT / source → topic ​

SourceAsset / deviceTopic id
MQTT adv1 (temp/humidity out of range)sensor (or any asset with ranges)sensors
MQTT sData (temp/humidity out of range)fridge/virtual-MAC sensorsensors
MQTT adv4 pushed, type panicpanicsos
MQTT adv4 pushed, type watchwatchwatches
MQTT adv8 vbatt < 2800 mVwhatever asset owns that MACbatteries
Inactivity timeoutpanic, watch, sensornotseen
Hourly calibration jobassigned sensor/untyped devicecalibration
Dashboard Recheck; Watch/Panic Test Sendn/atest
Status MQTT/KISS/ntfy/fleet transitionsinstallhealth

EVENTS_NTFY_ADV_TOPICS maps adv1/sdata → sensors, adv4 → sos, adv8 → batteries. Watch presses and not-seen alerts override that map to watches and notseen.

Low battery is not evaluated on adv1 vbatt, only on adv8.

Health event keys ​

status_report.ts stores each of these as health conditions on keryx-status. Vendor ops still POSTs when ops is on. Staff {server_id}-health is not a phone wake.

cooldown_subjectWhen
mqtt_downMQTT was connected and becomes not connected
kiss_downKISS was up and becomes unreachable (only if KISS is configured)
ntfy_downntfy reachability was ok and becomes not ok (ignores HTTP 429)
ntfy_test_failedRecheck POST to test failed and was not rate-limited
fleet:{stale|never_seen}:{asset_uid}One fleet condition appears or resolves

Ops copy: same body, title prefixed with {server_id}:, tags warning,triangular_flag_on_post, priority high. Ops is not a Keryx category and is omitted from GET /features.

Topics that are not Keryx categories ​

NamePublisherAudienceIn QR / features?
{binary_name}-ops (default safecall-ops)ntfy_publish_ops_healthVendor diagnosticsNo
{server_id}-serviceMonit check script from scripts/cli/monit.tsProcess up/downNo
APNs / FCM for com.sourcectl.keryxapp / com.sourcectl.keryxntfy firebase-key-file → Keryx Firebase → APNs/FCMWake of Keryxn/a (not an alert category); see ntfy-fcm-apns.md and ntfy-deployer.md

Config that changes cadence ​

KeyDefaultEffect
server_ntfy_enabledtrueMaster switch for SOS/watch/test and the daily {server_id}-status wake
server_ntfy_button_dedup_seconds40Shared SOS/watch cross-gateway event window
server_ntfy_digest_timezoneEurope/PragueWall-clock timezone for the daily reminder
server_ntfy_digest_time09:00Daily anchor
server_ntfy_sensors_mutedfalseOmit sensors from the snapshot and daily count
server_ntfy_batteries_mutedfalseOmit batteries
server_ntfy_notseen_mutedfalseOmit not-seen
server_ntfy_calibration_mutedfalseOmit calibration
server_ntfy_health_mutedfalseOmit install health (ops topic is independent)
server_keryx_status_urlemptyOverride; empty derives {ntfy origin}/keryx-status
server_keryx_status_read_secretemptyHMAC for phone GET; empty uses ntfy subscribe auth
server_ntfy_server_urlbrand notify hostClient ntfy base URL
server_ntfy_ops_enabledtrueVendor ops copy of health
server_ntfy_ops_topic{binary_name}-opsEmpty disables ops even if the flag is on

Keryx uses ntfy's comma-separated multi-topic subscription endpoint, so an active app maintains one SSE connection instead of one per selected category. Reducing SafeCall publishes lowers request and cache work, but does not itself reduce connections opened by third-party stock ntfy clients.

Related: Configuration, Keryx app, Provisioning protocol.