Node/Express service that:
- Receives Appspace outbound webhooks, enriches with Workspace ONE MDM
data (matched by serial), and posts Adaptive Card alerts to Webex.
- Runs a Webex bot in WebSocket mode with two commands:
* `offline [filter]` - lists currently offline / lost / failed
Appspace devices, enriched with per-device MDM facts + console links.
* `restart-offline [filter]` - sends WS1 SoftReset (reboot) to every
currently-offline device that has a WS1 record. Capped at 50 per
invocation with bounded concurrency to protect the WS1 API.
Notes on hardening already applied:
- In-flight promise coalescing in mdm.js and index.js so burst webhook
traffic can't stampede the WS1 token / device-cache refresh or the
Appspace token refresh.
- Structured logger that serializes Error instances (message, stack,
code, axios response.status/data) instead of stringifying to "{}".
- Webex 7439-char message-limit handling: `offline` builds its body
incrementally against a character budget and reports accurate
"N more not shown" truncation.
- Uses string phrases for `framework.hears(...)` so the framework's
`(^| )phrase($| )` wrapper handles group-space @mentions correctly,
and a shared `extractFilterArg()` helper so filter parsing works
identically in DMs and mentioned messages.
Config, Docker, smoke-test profile, and healthcheck included.
Secrets are managed via `.env` (gitignored); see `.env.example`.
Co-authored-by: Cursor <cursoragent@cursor.com>
|
||
|---|---|---|
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| docker-compose.yml | ||
| Dockerfile | ||
| index.js | ||
| mdm.js | ||
| package-lock.json | ||
| package.json | ||
| query-offline.js | ||
| README.md | ||
Appspace Webex Alerts
Lightweight Node/Express service that bridges Appspace device health events to Cisco Webex via Adaptive Cards, with optional enrichment from Workspace ONE MDM.
It also runs an interactive Webex bot (WebSocket mode) for on-demand queries.
What it does
- Listens for Appspace outbound webhooks (
DEVICE.HEALTHSTATUS.*andDEVICE.UNREGISTERED). - Ignores PWA devices.
- Enriches alerts with current MDM data (model, OS version, compliance, last sample/seen timestamps in EDT).
- Posts formatted Adaptive Cards to a configured Webex room (with direct links to both consoles).
- Provides a Webex bot with commands:
offline [optional filter]— snapshot of currently offline/lost/failed devices (client-side filter on name or type).help
Requirements
- Node 20+
- Appspace instance with outbound webhook + Application refresh token
- Webex bot token + room ID
- (Optional but recommended) Workspace ONE MDM OAuth client for enrichment
Quick Start (Docker - recommended)
-
Copy env:
cp .env.example .env # or .env.dev for the dev profile -
Fill in the required values (see
.env.examplefor descriptions). -
Run:
# Production profile npm run docker:prod # Development (with live reload + volume mount) npm run docker:dev # Smoke test (builds image + verifies /health responds "healthy" inside container) npm run docker:smoke
Port mapping notes: The container always listens internally on port 3000 (hardened default). Host port 1889 is used for both dev (docker compose --profile dev up -d app-dev) and prod (docker compose up -d). The PORT env inside the container is forced to 3000 via compose. You cannot run both profiles at the same time due to the shared host port.
Direct:
npm install
npm start
Health check: GET /health
Environment Variables
See .env.example for the full documented list.
Key ones:
WEBEX_BOT_TOKEN,WEBEX_ROOM_IDAPPSPACE_INSTANCE_URL,APPSPACE_SUBJECT_ID,APPSPACE_REFRESH_TOKEN,APPSPACE_API_BASE_URLWS1_*(for MDM enrichment)WEBHOOK_SECRET(recommended for the Appspace webhook)DEBUG,DEBUG_WEBHOOK(see below)
Bot Commands
In the Webex space where the bot is added:
offline— current problematic devicesoffline tablet— filter to devices whose name or type contains "tablet"help
The bot runs in WebSocket mode (no public webhook required).
Debugging
DEBUG=true— verbose logging for MDM lookups, ignored events, command handling, etc. (very useful in dev, noisy in prod).DEBUG_WEBHOOK=true— log the full incoming Appspace webhook payload (contains device details; do not leave on in production).LOG_FORMAT=json(orNODE_ENV=production) — output structured JSON logs (ideal for Docker/K8s log collectors).
You can also set NODE_ENV=development for similar verbose behavior.
Production & Docker Notes
- Graceful shutdown: The service handles
SIGTERM(used bydocker stop, Kubernetes, etc.) andSIGINT. It will:- Stop the Webex WebSocket framework (important to avoid "excessive device registrations").
- Close the HTTP server.
- Exit cleanly. A hard timeout forces exit after ~8s.
- Healthcheck:
/healthreturns 200 with basic status. Used by Docker and orchestrators. - Logging: Logs go to stdout/stderr (12-factor / Docker friendly). Use
LOG_FORMAT=jsonorNODE_ENV=productionfor structured JSON. UseDEBUG=truein non-prod for detail. Pipe to a collector (Loki, CloudWatch, etc.) as needed. - Secrets: Never bake secrets into the image. Use:
env_filefor compose (dev/staging only)- Docker secrets, Kubernetes Secrets, or a secrets manager (Vault, AWS Secrets Manager) for production.
- Ports: Container always listens on 3000 internally. Map host ports as needed (see docker-compose.yml).
- Non-root: Production image runs as the
nodeuser. - Resources: In production, set CPU/memory limits in your orchestrator. The bot command does a full device list scan (limit 500) — monitor for large fleets.
Architecture Notes
- Appspace token uses refresh token + cooldown + safety buffer.
- MDM uses 24h serial→ID cache + fresh detail lookup by ID on every alert (for up-to-date compliance/last-seen).
- WebSocket mode for the bot avoids restart rate limits.
- All enrichment is best-effort; alerts are never blocked by MDM or token issues.
License / Support
Internal tool. Tweak as needed.
TODO / Future
- Server-side filtering for the devices list when the Appspace API supports it reliably.
- Metrics / full structured JSON logging.
- Support for more Appspace event types.
- Multi-stage Dockerfile for even smaller prod images.