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
401for a missing or malformed header or any mismatch,400for a body that isn't a JSON object,200once handled. - Python: encode both sides to bytes and use
hmac.compare_digeston them (never passstr, 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-Authenticatechallenge 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.
| Field | Description |
|---|---|
alert_id | ID of the alert |
alert_title | Title of the alert |
category | Category of the alert |
di_event | DI event associated with the alert, an object carrying event_type |
event_description | Description of the event that triggered the alert |
priority | Critical, High, Medium or Low |
status | Open, Acknowledged, Resolved or Auto-Resolved |
rule_id | ID 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
- references/overview.md - What JumpCloud webhook channels are, payload fields
- references/setup.md - Create the channel, set Basic auth, attach it to a rule
- references/verification.md - Basic auth check details, gotchas, debugging 401s
- examples/ - Runnable Express, Next.js and FastAPI handlers with tests