# Jamf Pro Webhooks

Jamf Pro (Apple device management) sends a webhook when something happens on the server: a computer
enrolls or checks in, a mobile device enrolls, an inventory update completes, the server starts. A
webhook can send JSON or XML; **this skill assumes JSON**. Webhooks are **not signed** in the way
this skill covers. To authenticate them, choose **Header Authentication** on the Jamf Pro webhook:
you enter a JSON object of header names and values, Jamf Pro sends those headers on every request,
and your endpoint compares one of them with the value you stored.

> Choose Header Authentication on the Jamf Pro webhook and enter the same header name and value here.

Basic authentication, mTLS and the Hash Signature option are not covered. The Hash Signature header
and what it signs aren't publicly documented, so this skill doesn't verify it.

## When to Use This Skill

* How do I receive Jamf Pro webhooks?
* How do I authenticate a Jamf Pro webhook with Header Authentication?
* Does Jamf Pro sign webhook payloads? (Not in a documented way: use a shared header value)
* How do I handle `ComputerAdded`, `ComputerCheckIn` or `MobileDeviceEnrolled` events?
* Why does my Jamf Pro webhook endpoint return 401?

## Verification (core)

```javascript
const crypto = require('crypto');

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

function verifyJamfProRequest(headers, name = process.env.JAMF_PRO_WEBHOOK_HEADER || 'Authorization',
                              secret = process.env.JAMF_PRO_WEBHOOK_SECRET) {
  if (!secret) return false;                    // never accept when unconfigured
  const received = headers[name.toLowerCase()]; // header names are case-insensitive
  return Boolean(received) && safeEqual(received, secret); // compare the WHOLE value
}

```

* The header **name** is whatever you typed as the JSON key in Header Authentication, so read it
  from `JAMF_PRO_WEBHOOK_HEADER` (default `Authorization`). Look it up case-insensitively.
* The expected **value** is the exact, whole header value, including any `Bearer ` prefix you typed
  (e.g. `{"Authorization": "Bearer <token>"}`). Do not strip or parse the prefix.
* Return `401` when the header is missing or doesn't match, `400` for a body that isn't a JSON
  object, `200` once handled.
* Read the body raw (`express.raw({ type: '*/*' })`) so any content type works, and authenticate
  before parsing it.
* Python: `hmac.compare_digest` on bytes (encode both sides; `str` raises on non-ASCII).

**What this does and doesn't give you:** a static shared credential authenticates the sender, not
the body, and gives no replay protection (no signature, no timestamp). Use HTTPS, keep the value
secret, and rotate it in both Jamf Pro and your endpoint if it leaks.

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/jamf-pro-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/jamf-pro-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/jamf-pro-webhooks/examples/fastapi/).

## Event Types

The body is `{ webhook: { id, name, webhookEvent, eventTimestamp }, event: { ... } }`. The event
name is `webhook.webhookEvent`; the shape of `event` depends on it.

| `webhookEvent` | Sent When |
| --- | --- |
| `ComputerAdded` | A computer is enrolled into Jamf Pro |
| `ComputerCheckIn` | A managed computer checks in with the server |
| `ComputerInventoryCompleted` | A computer finishes submitting inventory |
| `ComputerPatchPolicyCompleted` | A computer patch policy completes |
| `ComputerPolicyFinished` | A policy finishes on a computer |
| `ComputerPushCapabilityChanged` | A computer's push capability changes |
| `DeviceAddedToDEP` | A device is added to Automated Device Enrollment |
| `JSSStartup` / `JSSShutdown` | The Jamf Pro server starts / shuts down |
| `MobileDeviceEnrolled` | A mobile device is enrolled |
| `MobileDeviceUnEnrolled` | A mobile device's MDM profile is removed or it is unmanaged |
| `MobileDeviceCheckIn` | A mobile device checks in |
| `MobileDeviceCommandCompleted` | An MDM command completes on a mobile device |
| `MobileDeviceInventoryCompleted` | A mobile device finishes submitting inventory |
| `MobileDevicePushSent` | A push notification is sent to a mobile device |
| `PatchSoftwareTitleUpdated` | A patch software title is updated |
| `PushSent` | A push notification is sent |
| `RestAPIOperation` | An authorized Classic API request is made |
| `SCEPChallenge` | A SCEP challenge is requested |
| `SmartGroupComputerMembershipChange` | A computer smart group's membership changes |
| `SmartGroupMobileDeviceMembershipChange` | A mobile device smart group's membership changes |
| `SmartGroupUserMembershipChange` | A user smart group's membership changes |

## Environment Variables

```bash
JAMF_PRO_WEBHOOK_HEADER=Authorization                    # the JSON key you entered
JAMF_PRO_WEBHOOK_SECRET=Bearer your-long-random-secret   # the exact, whole JSON value

```

## Local Development

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

```bash
npx hookdeck-cli listen 3000 jamf-pro --path /webhooks/jamf-pro

```

Use the printed URL as the webhook URL in Jamf Pro.

## Resources

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/jamf-pro-webhooks/references/overview.md) - What Jamf Pro webhooks are, events and payload shape
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/jamf-pro-webhooks/references/setup.md) - Create the webhook and enable Header Authentication
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/jamf-pro-webhooks/references/verification.md) - Header check details, gotchas, debugging 401s
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/jamf-pro-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests