Agent skill

Workspace ONE Webhooks Skill

Receive and authenticate Workspace ONE UEM (Omnissa, formerly VMware AirWatch) event notification webhooks. Use when setting up a Workspace ONE webhook endpoint, verifying the HTTP Basic credentials, debugging 401s, or handling device events like Device Enrollment, Device Compliance Status Change, Device Wipe and Device Delete.

Install this skill

npx skills add hookdeck/webhook-skills --skill workspace-one-webhooks


Workspace ONE UEM (Omnissa, formerly VMware) can call your endpoint when something happens to a managed device: it enrolls, changes compliance status, is wiped, is deleted. These are the Event Notifications in System Settings. Each rule has a Target URL, a User Name, a Password and a Format (JSON or XML). Notifications are not signed: Workspace ONE authenticates itself with those credentials using HTTP Basic auth, and your endpoint checks them.

When to Use This Skill

  • How do I receive Workspace ONE UEM event notifications?
  • How do I verify the Basic auth credentials on a Workspace ONE webhook?
  • Does Workspace ONE sign webhook payloads? (No: HTTP Basic auth with a shared user name and password)
  • How do I handle Device Enrollment, Device Compliance Status Change or Device Wipe events?
  • Why does my Workspace ONE webhook endpoint return 401?

Verification (core)

const crypto = require('crypto');

// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
const safeEqual = (a, b) => crypto.timingSafeEqual(
  crypto.createHash('sha256').update(String(a)).digest(),
  crypto.createHash('sha256').update(String(b)).digest());

function verifyWorkspaceOneRequest(headers, user, pass) {
  if (!user || !pass) return false;               // never accept when unconfigured
  const m = /^basic\s+([A-Za-z0-9+/]+={0,2})$/i.exec((headers.authorization || '').trim());
  if (!m) return false;
  const decoded = Buffer.from(m[1], 'base64').toString('utf8');
  const i = decoded.indexOf(':');                 // split on the FIRST colon
  if (i === -1) return false;
  const userOk = safeEqual(decoded.slice(0, i), user);   // evaluate both,
  const passOk = safeEqual(decoded.slice(i + 1), pass);  // no short-circuit
  return userOk && passOk;
}
  • Header: Authorization: Basic base64(username:password), the User Name and Password you entered on the Event Notifications rule. Read them from WORKSPACE_ONE_WEBHOOK_USERNAME and WORKSPACE_ONE_WEBHOOK_PASSWORD.
  • The scheme is case-insensitive. Split the decoded value on the first colon only, since passwords can contain :.
  • Compare username and password each in constant time (Node: crypto.timingSafeEqual on equal-length buffers or SHA-256 digests; Python: hmac.compare_digest on bytes, never str, which raises on non-ASCII).
  • Missing or malformed header, or a mismatch: 401. Stored: 200.
  • The Omnissa page doesn't show the header on a request, or say whether Basic credentials are sent on the first request or only after a 401 challenge. Log the headers of a test notification to see what arrives.
  • Read the body raw (express.raw({ type: '*/*' })) so any content type works, and authenticate before reading it.

A static shared credential authenticates the sender, not the body, and gives no replay protection (no signature, no timestamp). Anyone who captures a request can resend it. Use HTTPS only, make handlers idempotent, and rotate the credential if it leaks.

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Event Types

The Omnissa page lists these notification events by their UI labels (Groups & Settings > All Settings > System > Advanced > API > Event Notifications):

EventSent When
Check-in/Check-outA multi-user device is checked in or out
Device Attribute ChangeA selected device attribute changes (asset number, friendly name, MCC, ownership, organization group ID, operating system, phone number, user email address)
Device Compliance Status ChangeA device's compliance status changes
Device Compromised Status ChangeA device's compromised status changes: an Android device is rooted or an iOS device is jailbroken
Device DeleteA device is deleted from Workspace ONE UEM
Device EnrollmentA device enrolls into Workspace ONE Intelligent Hub or Workspace ONE UEM
Device Unenrolled Enterprise WipeA device is enterprise wiped
Device WipeA device is wiped

The page documents no payload and no wire-level event strings, so these labels are not values to switch on. The examples store the raw body and don't read any field. Capture real deliveries for each event you select (for example with the Hookdeck CLI), then write your parser from what your tenant sends. A rule per event type, each with its own Target URL path, tells your handler which event it selected without reading the body.

Environment Variables

WORKSPACE_ONE_WEBHOOK_USERNAME=your-notification-user
WORKSPACE_ONE_WEBHOOK_PASSWORD=your-notification-password

Both are the credentials you set on the Event Notifications rule. Rotate them in the console and in your environment together.

Local Development

Workspace ONE can't reach localhost. Tunnel with the Hookdeck CLI (no account required):

npx hookdeck-cli listen 3000 workspace-one --path /webhooks/workspace-one

Use the printed URL as the Target URL, with the same User Name and Password configured on the Hookdeck source.

Resources


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 5, 2026

View on GitHub →