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 eight staff categories as ntfy_topics. Keryx QR generation (POST /api/v1/keryx/qr) includes only the categories the admin left checked. Mobile Access checks the seven operational categories by default. test and debug access are unchecked in the collapsed Admin features section and require an explicit warning confirmation.

Keryx renders one category card for every topic in its QR. It keeps transport, topic names, and subscribe credentials out of the ordinary UI. Each category has a default notification cadence and optional per-known-device overrides; suppressed alerts are still stored and update category state.

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 problemsStatus snapshot transitions (see below)Title/body are the health event; default tags warning,triangular_flag_on_postwarning,triangular_flag_on_posthigh

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 moment events from stateful conditions. SOS, watch, and test messages are moments. Sensors, batteries, not-seen, calibration, and health are conditions with explicit active and resolved transitions. Condition state, successful delivery time, and pending digest work persist in SQLite.

HTTP 429 from ntfy starts a global backoff (default 30 s, or Retry-After). Immediate production alerts retain the existing retry queue. Digest work stays in the durable outbox and is retried by the scheduler. Test publishes are not queued and intentionally bypass every cooldown.

IdDefault server policySubject / groupingEffective behavior
sensorsIndividual, immediate first alert, 6 h repeatStable asset; temperature/humidity facets remain detail and do not bypass the per-sensor cooldownRecovery is an explicit quiet state update. A daily quiet snapshot lets Keryx establish current state
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
batteriesBulk every 24 hOne digest on the batteries topic with per-device itemsAdmin can choose a shorter fixed interval or individual per-device delivery
notseenPanic/watch immediate with 6 h critical repeat; sensors bulk every 24 hStable asset and device typeSeeing the device again resolves the condition. Fridge timeout remains max(10 min, 2 × update_interval)
calibrationBulk every 24 hDevice assignment / stable assetHourly checker updates state; admin can choose shorter or individual delivery
testImmediate, exemptUnique test occurrenceEvery Recheck / Test Send; no Keryx cadence control
healthMQTT/KISS/ntfy failures immediate with 6 h critical repeat; fleet exceptions bulk every 24 hInfrastructure key or individual fleet issueRecoveries are explicit. Vendor ops follows the same split

Bulk intervals use fixed wall-clock buckets anchored at 09:00 Europe/Prague by default. The timezone, anchor, modes, and intervals are editable in SafeCall settings. Digests are one message per existing category topic unless the 4 KiB push limit requires numbered parts.

server_ntfy_enabled gates staff production and test publishes. Health site posts also require that flag. Vendor ops copies of health are independent (server_ntfy_ops_enabled + non-empty server_ntfy_ops_topic).

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 also publishes a silent current-state snapshot for each stateful category included in that QR so a newly scanned phone can leave Waiting for status without waiting for the next 09:00 digest. Those snapshots do not mark the scheduled digest as delivered. Dashboard Recheck and Watch/Panic Test Send go to {server_id}-test, not -sos.

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/sdatasensors, adv4sos, adv8batteries. 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 dual-publishes each of these to {server_id}-health and, when ops is on, to the vendor topic.

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

Config that changes cadence

KeyDefaultEffect
server_ntfy_enabledtrueMaster switch for staff topic POSTs (including {server_id}-health)
server_ntfy_sensors_cooldown_seconds21600Per-sensor repeat interval
server_ntfy_button_dedup_seconds40Shared SOS/watch cross-gateway event window
server_ntfy_critical_cooldown_seconds21600Panic/watch not-seen and infrastructure repeat interval
server_ntfy_digest_timezoneEurope/PragueWall-clock timezone for digest boundaries
server_ntfy_digest_time09:00Daily anchor for fixed intervals
server_ntfy_{batteries,sensor_notseen,calibration,fleet_health}_modebulkBulk or individual delivery for the named policy
server_ntfy_{batteries,sensor_notseen,calibration,fleet_health}_interval_seconds86400Digest interval or individual repeat interval
server_ntfy_cooldown_seconds600Deprecated compatibility value for older binaries
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.