Agent skill

JumpCloud Webhooks Skill

Receive and authenticate JumpCloud webhook channel notifications. Use when setting up a JumpCloud webhook endpoint, checking HTTP Basic auth on JumpCloud alert notifications, debugging 401s, or handling alert fields like alert_id, alert_title, priority, status and di_event.

Install this skill

npx skills add hookdeck/webhook-skills --skill jumpcloud-webhooks


JumpCloud (directory and IAM) can send alert notifications to a URL through a webhook channel (Admin Portal > Settings > Notification Channels > Webhooks). Payloads are not signed. The channel can add authentication: None, Basic (username and password), Token, plus up to 25 custom headers. This skill covers Basic: JumpCloud sends Authorization: Basic base64(username:password) and your endpoint compares the credentials with the ones you configured.

When to Use This Skill

  • How do I receive JumpCloud webhooks?
  • How do I authenticate JumpCloud webhook channel requests with Basic auth?
  • Does JumpCloud sign webhook payloads? (No: use the channel's Basic credentials)
  • How do I handle JumpCloud alert notifications (alert_id, priority, status, di_event)?
  • Why does my JumpCloud 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 verifyJumpCloudRequest(headers, user = process.env.JUMPCLOUD_WEBHOOK_USERNAME,
                                pass = process.env.JUMPCLOUD_WEBHOOK_PASSWORD) {
  if (!user || !pass) return false;                           // never accept when unconfigured
  const m = /^basic +([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 < 0) return false;
  const userOk = safeEqual(decoded.slice(0, i), user);
  const passOk = safeEqual(decoded.slice(i + 1), pass);       // evaluate both, no short-circuit
  return userOk && passOk;
}
  • Header: Authorization: Basic base64(username:password); the scheme is case-insensitive.
  • Return 401 for a missing or malformed header or any mismatch, 400 for a body that isn't a JSON object, 200 once handled.
  • Python: encode both sides to bytes and use hmac.compare_digest on them (never pass str, which raises on non-ASCII).
  • Read the body raw (express.raw({ type: '*/*' })) so any content type works, and authenticate before parsing it.
  • Credentials must arrive on the first request. The docs don't say whether JumpCloud sends Basic credentials preemptively; assume it must. A 401 + WWW-Authenticate challenge flow won't work through Hookdeck, which verifies the request as received.

Security limits: a static shared credential authenticates the sender, not the body, and gives no replay protection (no signature, no timestamp). Use HTTPS, and rotate the credential in the channel settings (and your environment variables) if it leaks.

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

Alert Payload

JumpCloud documents the payload fields and one sample body (in its Azure walk-through), but no category values, so treat any sample you see (including the ones in the tests) as illustrative.

FieldDescription
alert_idID of the alert
alert_titleTitle of the alert
categoryCategory of the alert
di_eventDI event associated with the alert, an object carrying event_type
event_descriptionDescription of the event that triggered the alert
priorityCritical, High, Medium or Low
statusOpen, Acknowledged, Resolved or Auto-Resolved
rule_idID of the rule that triggered the alert

Other documented fields: description, in_console_alert, initiated_by, occurred_at, organization_name, previous_alert_status, source_id, source_name, source_type, source_url, update_at.

The event name is di_event.event_type, a Directory Insights type such as user_update (see Create an Insights Rule and Manage Access Requests). Access Request details (workflow, access_request_id, access_request_state) are also nested under di_event. Device monitoring rules aren't on those lists, so fall back to rule_id or category.

Environment Variables

JUMPCLOUD_WEBHOOK_USERNAME=your-channel-username   # Basic Authentication > Username
JUMPCLOUD_WEBHOOK_PASSWORD=your-channel-password   # Basic Authentication > Password

Local Development

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

npx hookdeck-cli listen 3000 jumpcloud --path /webhooks/jumpcloud

Use the printed URL as the webhook channel URL in JumpCloud.

Resources


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 5, 2026

View on GitHub →