Agent skill
NinjaOne Webhooks Skill
Receive and authenticate NinjaOne (NinjaRMM) webhooks. Use when setting up a NinjaOne webhook handler, adding an X-Webhook-Secret or Authorization header through PUT /v2/webhook, or handling activity payloads (activityType CONDITION with statusCode TRIGGERED or RESET, ACTION, ACTIONSET). NinjaOne does NOT sign webhooks: there is no signature header and no HMAC, so verify a shared-secret custom header in constant time. Only API-configured webhooks can carry a header; console notification-channel webhooks take a URL only and cannot be authenticated.
Install this skill
npx skills add hookdeck/webhook-skills --skill ninjaone-webhooks
NinjaOne is an RMM (remote monitoring and management) platform. A NinjaOne webhook posts an activity (a monitoring condition firing, a script finishing, a device being created, a patch failing) to your URL as JSON.
When to Use This Skill
- How do I receive NinjaOne webhooks?
- How do I verify a NinjaOne webhook signature? (You can't. NinjaOne doesn't sign webhooks. Authenticate with a custom header instead.)
- How do I add an
AuthorizationorX-Webhook-Secretheader to a NinjaOne webhook? - How do I handle a
CONDITIONactivity with statusTRIGGEREDorRESET? - Why can't I authenticate the webhook I created in the NinjaOne console?
There Is No Signature
The NinjaOne API v2 spec describes no signature header, signing secret, timestamp or HMAC. Don't invent an X-NinjaOne-Signature verifier: no real delivery would pass it.
The only authentication mechanism is the headers array on the webhook configuration (PUT /v2/webhook, described as "Custom HTTP Headers (i.e. Authorization)"), which NinjaOne sends back on every delivery. So you:
- Generate a random secret yourself (
openssl rand -hex 32). - Call
PUT /v2/webhookwith"headers": [{ "name": "X-Webhook-Secret", "value": "<secret>" }]. - On your endpoint, compare the
X-Webhook-Secretheader with the secret in constant time. Return401if it's missing or wrong. - Fail closed: if the secret isn't configured on your server, reject the request.
Console-created webhooks can't be authenticated. Notification-channel webhooks set up in the NinjaOne console take a URL only, with no header field. Use the API configuration, or put the endpoint behind something that can authenticate (for example a Hookdeck source).
This proves the caller knows the shared secret. It doesn't protect the body from tampering, since nothing is signed over it, so the raw body isn't needed for verification.
Verification (core)
const crypto = require('crypto');
// NinjaOne sends the custom header you configured via PUT /v2/webhook.
function verifyNinjaOneSecret(headerValue, secret) {
if (!secret) return false; // fail closed when NINJAONE_WEBHOOK_SECRET is unset
if (typeof headerValue !== 'string') return false;
// Hash both sides so timingSafeEqual always gets equal-length buffers
const a = crypto.createHash('sha256').update(headerValue).digest();
const b = crypto.createHash('sha256').update(secret).digest();
return crypto.timingSafeEqual(a, b);
}
// verifyNinjaOneSecret(req.headers['x-webhook-secret'], process.env.NINJAONE_WEBHOOK_SECRET)
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Events: activityType + statusCode
NinjaOne has no single event-name field. Each delivery is an activity, identified by the pair activityType and statusCode. Both are enums in the API's Activity schema. The spec lists the two enums separately and doesn't say which combinations occur, so the pairings below are the common ones, not an exhaustive matrix:
| activityType | statusCode | Meaning |
|---|---|---|
CONDITION | TRIGGERED | A monitoring condition fired (an alert) |
CONDITION | RESET | The condition cleared |
ACTION | STARTED, COMPLETED, CANCELLED | A script or action ran on a device |
ACTIONSET | STARTED, COMPLETED, CANCELLED | An automation (set of actions) ran |
SYSTEM | NODE_CREATED, NODE_UPDATED, NODE_DELETED | Device lifecycle |
Other activityType values include PATCH_MANAGEMENT, ANTIVIRUS, MONITOR, PSA and MAINTENANCE_MODE. The status code list has hundreds of values, so filter at the source with the activities map in PUT /v2/webhook rather than receiving everything.
Payload fields: id, activityTime, deviceId, severity (NONE, MINOR, MODERATE, MAJOR, CRITICAL), priority, seriesUid, activityType, statusCode, status, subject, message, data. See references/overview.md.
Environment Variables
# The secret YOU generated. NinjaOne must send: X-Webhook-Secret: <this value>
NINJAONE_WEBHOOK_SECRET=generate_with_openssl_rand_hex_32
Local Development
npx hookdeck-cli listen 3000 ninjaone --path /webhooks/ninjaone
No account required. The CLI creates a guest account on first run and gives you a public URL and a web UI for inspecting requests. Use port 8000 for the FastAPI example.
Reference Materials
- references/overview.md - What NinjaOne webhooks are, activity types and status codes, payload fields
- references/setup.md - Configuring the webhook with
PUT /v2/webhook, the secret header, filters - references/verification.md - Header authentication per framework, gotchas, debugging