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 Authorization or X-Webhook-Secret header to a NinjaOne webhook?
  • How do I handle a CONDITION activity with status TRIGGERED or RESET?
  • 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:

  1. Generate a random secret yourself (openssl rand -hex 32).
  2. Call PUT /v2/webhook with "headers": [{ "name": "X-Webhook-Secret", "value": "<secret>" }].
  3. On your endpoint, compare the X-Webhook-Secret header with the secret in constant time. Return 401 if it's missing or wrong.
  4. 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:

activityTypestatusCodeMeaning
CONDITIONTRIGGEREDA monitoring condition fired (an alert)
CONDITIONRESETThe condition cleared
ACTIONSTARTED, COMPLETED, CANCELLEDA script or action ran on a device
ACTIONSETSTARTED, COMPLETED, CANCELLEDAn automation (set of actions) ran
SYSTEMNODE_CREATED, NODE_UPDATED, NODE_DELETEDDevice 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


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 2, 2026

View on GitHub →