Skip to content

SIP User Down — WhatsApp Alert

When a watched SIP endpoint drops from registered (🟢) to down (🟠 unreachable / 🔴 unregistered), the org's SIP-alert recipients get a WhatsApp within ~30–60s — so a dead reception phone or a disconnected branch is noticed in minutes, not when a patient complains. Detection is server-side and continuous; no one needs the editor open.

See also: Subscribe to Tickets (Daily Missed-Call Alerts) — the other Astradial-MSG91 WhatsApp alert. This one is the infra-down sibling and uses a separate recipient list.

Why this exists

Hospitals and hotels run extensions on cheap ATAs and softphones that silently drop registration (power blip, NAT timeout, Wi-Fi flap). Today nobody notices until a caller can't get through. The Users page already shows a live 🟢/🟠/🔴 dot per extension — this turns that signal into a push so an operator acts on it.

What it does

  • Operators flag which extensions to watch (a per-user watch flag).
  • A server-side poller checks PJSIP registration every 30s using the same data the Users page dots use (pjsip show contacts), so dots and alerts can never disagree.
  • The moment a watched extension goes 🟢 reachable → 🟠/🔴 down, every enabled SIP-alert recipient for that org gets a WhatsApp (template sip_down_alert).

🔴 Server Down Alert — Bharathi Hospital Extension 1001 (Reception) just went Unregistered at 28 Jun, 21:15. Please check the device / network connection.

Down-only — there is no "back online" message (a deliberate v1 choice to keep volume low).

Architecture

Four moving parts (mirrors the daily ticket alert, but event-driven, not cron):

  1. Per-user watch flagusers.down_alert_enabled (boolean, default false). Not dialplan-relevant, so toggling it never reloads Asterisk.
  2. Separate recipient listsip_alert_subscribers (org_id, country_code, phone, name, enabled). Deliberately NOT ticket_alert_subscribers — infra-down alerts and missed-call alerts go to different people.
  3. Admin singleton configadmin_whatsapp_config row of config_type='sip_user_down' holds the MSG91 integrated_number, namespace, and template name (sip_down_alert). Auth key is MSG91_ADMIN_AUTH_KEY env (shared with the other alert types; never in DB/UI).
  4. Pollerapi/src/jobs/sipDownAlertScheduler.js, a setInterval inside the astrapbx process (default 30s). Reuses pjsipRegistrationsService.getAllUserRegistrations() and msg91Service.sendBulkTemplate(). The pure decision logic is split into api/src/jobs/sipDownAlertLogic.js (evaluateTransition) so it's unit-tested in isolation.

Detection rules (the edge logic)

All enforced by evaluateTransition + api/tests/sip-down-alert.test.js (SD1–SD6):

Rule Behaviour
Edge-triggered Fires only on reachable → {nonqual, unreachable, unregistered}. Staying down does not re-alert.
unknown is a monitoring gap When Asterisk/CLI is unreachable, every endpoint reads unknownnever alerts, and the last known status is preserved (so an AMI/CLI hiccup can't spam "everyone is down").
Baseline on first sight The first observation of a user only records its status (never alerts). So a process restart cannot alert for endpoints that were already down.
Throttle Per-user min gap (SIP_DOWN_ALERT_MIN_GAP_MS, default 10 min) guards against a flapping endpoint.
Refuse + log If MSG91_ADMIN_AUTH_KEY or the sip_user_down config is missing, each tick refuses to send. Run-level reasons (no org context) are console.warn-throttled rather than written to audit_log (that table's org_id is NOT NULL); org-scoped send/skip outcomes ARE persisted to audit_log (resource='sip_down_alert').

Status → dot colour on the Users page: reachable=🟢, nonqual/unreachable=🟠, unregistered=🔴, unknown=⚪.

The WhatsApp template

The configured template (sip_down_alert) uses named MSG91 parameters (like the instant-missed-call template, not the positional daily one):

Component Value the poller fills
header_var_1 org name
body_var_1 "<ext> (<name>)" e.g. 1001 (Reception)
body_var_2 status label (Unregistered / Unreachable (qualify failed) / Unreachable (qualify pending))
body_var_3 time (IST)
button_1 URL button → the org's Users page

Enabling it (per environment, by Astradial ops)

The feature deploys inert — it refuses to send until configured, and nothing alerts until an org opts in. To turn it on:

  1. Admin WhatsApp config (platform-admin dashboard → WhatsAppSIP User Down tab): set integrated number, namespace, template sip_down_alert, language en, then Save. (The config is per-environment — staging and prod each have their own admin_whatsapp_config.)
  2. Per org: an operator ticks the bell checkbox next to an extension's Registered IP (or the "…" → Down alert menu item) to watch it, and adds recipients via the SIP Alerts sheet on the Users page.

The global config alone never alerts anyone

Watch flags default off and recipient lists start empty. Setting the sip_user_down config only enables the capability — an org gets alerts only once it has ≥1 watched extension and ≥1 enabled recipient.

Approve the MSG91 template first

sip_down_alert must be in Approved status in MSG91 before sends succeed — an unapproved template is rejected at send time. Verify with the admin panel's Send Test Message (which fires the sip_down_alert named-param sample).

UI surfaces

  • Users page (/dashboard/<orgId>/users): a bell checkbox left of the Registered IP dot + a "Down alert" item in the per-row "…" menu (both persist down_alert_enabled); a "SIP Alerts" header sheet to CRUD the recipient list. All Owner/Admin-gated.
  • Admin dashboard WhatsApp panel: a "SIP User Down" tab alongside Daily/Instant/CRM.

Tunables (env)

Var Default Meaning
SIP_DOWN_ALERT_POLL_MS 30000 poll interval
SIP_DOWN_ALERT_MIN_GAP_MS 600000 per-user re-alert throttle
MSG91_ADMIN_AUTH_KEY shared MSG91 auth key (also used by ticket alerts)

Gotchas

  • Config is per-environment. Promoting the code to prod runs the migrations but does not copy the sip_user_down WhatsApp config — a platform admin must set it on prod separately (staging's value never crosses over).
  • config_type must be in the route allowlist. api/src/routes/admin-whatsapp.js validates ?type= against VALID_CONFIG_TYPES; a new config type that's missing there silently falls back to daily_missed_call (the admin tab then shows/saves the wrong row). sip_user_down is included as of the feature's admin-UI fix.
  • Baseline resets on restart. The poller's in-memory prevStatus is lost on an astrapbx restart; the first post-restart tick re-baselines (no alert). A genuine down that happened during a restart won't alert until it recovers and drops again — acceptable; persisting it is a possible later hardening.

Where to read more

  • api/src/jobs/sipDownAlertScheduler.js — poll loop, per-user evaluation, send + audit
  • api/src/jobs/sipDownAlertLogic.jsevaluateTransition (pure, unit-tested)
  • api/src/models/SipAlertSubscriber.js + migration *-create-sip-alert-subscribers.js
  • api/src/routes/sip-alerts.js — recipient CRUD (mounted /api/v1/orgs/:orgId/sip-alerts)
  • api/src/services/asterisk/pjsipRegistrationsService.js — the pjsip show contacts parser (shared with the Users-page dots)
  • api/tests/sip-down-alert.test.js — SD1–SD6 (edge, baseline, unknown-skip, throttle, down-only)