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):
- Per-user watch flag —
users.down_alert_enabled(boolean, default false). Not dialplan-relevant, so toggling it never reloads Asterisk. - Separate recipient list —
sip_alert_subscribers(org_id,country_code,phone,name,enabled). Deliberately NOTticket_alert_subscribers— infra-down alerts and missed-call alerts go to different people. - Admin singleton config —
admin_whatsapp_configrow ofconfig_type='sip_user_down'holds the MSG91integrated_number,namespace, and template name (sip_down_alert). Auth key isMSG91_ADMIN_AUTH_KEYenv (shared with the other alert types; never in DB/UI). - Poller —
api/src/jobs/sipDownAlertScheduler.js, asetIntervalinside the astrapbx process (default 30s). ReusespjsipRegistrationsService.getAllUserRegistrations()andmsg91Service.sendBulkTemplate(). The pure decision logic is split intoapi/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 unknown — never 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:
- Admin WhatsApp config (platform-admin dashboard → WhatsApp → SIP User Down tab): set integrated number, namespace, template
sip_down_alert, languageen, then Save. (The config is per-environment — staging and prod each have their ownadmin_whatsapp_config.) - 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 persistdown_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_downWhatsApp config — a platform admin must set it on prod separately (staging's value never crosses over). config_typemust be in the route allowlist.api/src/routes/admin-whatsapp.jsvalidates?type=againstVALID_CONFIG_TYPES; a new config type that's missing there silently falls back todaily_missed_call(the admin tab then shows/saves the wrong row).sip_user_downis included as of the feature's admin-UI fix.- Baseline resets on restart. The poller's in-memory
prevStatusis 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 + auditapi/src/jobs/sipDownAlertLogic.js—evaluateTransition(pure, unit-tested)api/src/models/SipAlertSubscriber.js+ migration*-create-sip-alert-subscribers.jsapi/src/routes/sip-alerts.js— recipient CRUD (mounted/api/v1/orgs/:orgId/sip-alerts)api/src/services/asterisk/pjsipRegistrationsService.js— thepjsip show contactsparser (shared with the Users-page dots)api/tests/sip-down-alert.test.js— SD1–SD6 (edge, baseline, unknown-skip, throttle, down-only)