-
Notifications
You must be signed in to change notification settings - Fork 3
Security
Warning
Webhook tokens are required for production deployments. Without a token, anyone who discovers your Worker URL can POST fake enrollment events to your dashboard.
You must configure both sides for webhook authentication to work:
-
Worker Side: Set
WEBHOOK_TOKENas a Cloudflare Worker secret -
Setup Manager Side: Add
tokenkey to your webhook plist
If these values don't match, webhooks return 401 Unauthorized.
First, generate a secure random token:
openssl rand -hex 24Save this value — you'll need it for both the Worker and Setup Manager configuration.
If the Deploy to Cloudflare setup screen asks for WEBHOOK_TOKEN, paste your generated token there during setup. Do not leave it blank. Save the value before continuing because Cloudflare secrets are hidden after they are saved.
If you skipped it during Deploy Button setup, add it manually:
- Go to Workers & Pages in the Cloudflare dashboard
- Click on your Worker (e.g.,
setupmanagerhud) - Go to Settings → Variables and Secrets
- Click Add under the Secrets section
- Name:
WEBHOOK_TOKEN - Type: Secret (not Text — secrets are encrypted and hidden after save)
- Value: paste your generated token
- Click Save and Deploy
If you cloned the repo locally:
npx wrangler secret put WEBHOOK_TOKEN
# Paste your token when promptedThe secret is stored securely by Cloudflare and never exposed in logs or code.
Add the token key to each webhook in your Setup Manager configuration plist:
<key>webhooks</key>
<dict>
<key>started</key>
<dict>
<key>url</key>
<string>https://your-worker.workers.dev/webhook</string>
<key>token</key>
<string>paste-your-secret-here</string>
</dict>
<key>finished</key>
<dict>
<key>url</key>
<string>https://your-worker.workers.dev/webhook</string>
<key>token</key>
<string>paste-your-secret-here</string>
</dict>
</dict>For a fuller Setup Manager profile example, including debug-mode webhooks, see Setup Manager Configuration.
Important
The token value must be identical to what you set in WEBHOOK_TOKEN. Setup Manager sends this as a raw Authorization: <token> request header. The Worker also accepts Authorization: Bearer <token> for curl and other tools.
A blank WEBHOOK_TOKEN is not valid. If you do not know the saved value, generate a new token, update the Worker secret, and update Setup Manager with the same new value.
Test your configuration with curl after WEBHOOK_TOKEN is set on the Worker. Replace https://your-worker.workers.dev and your-token-here with your Worker URL and the exact token value you saved.
Send a started webhook:
curl -X POST https://your-worker.workers.dev/webhook \
-H "Authorization: Bearer your-token-here" \
-H "Content-Type: application/json" \
-d '{"name":"Started","event":"com.jamf.setupmanager.started","timestamp":"2025-01-01T00:00:00Z","started":"2025-01-01T00:00:00Z","modelName":"Test Mac","modelIdentifier":"Mac15,3","macOSBuild":"24A335","macOSVersion":"15.0","serialNumber":"TEST001","setupManagerVersion":"2.0.0"}'Send a finished webhook:
curl -X POST https://your-worker.workers.dev/webhook \
-H "Authorization: Bearer your-token-here" \
-H "Content-Type: application/json" \
-d '{"name":"Finished","event":"com.jamf.setupmanager.finished","timestamp":"2025-01-01T00:10:00Z","started":"2025-01-01T00:00:00Z","finished":"2025-01-01T00:10:00Z","duration":600,"modelName":"Test Mac","modelIdentifier":"Mac15,3","macOSBuild":"24A335","macOSVersion":"15.0","serialNumber":"TEST001","setupManagerVersion":"2.0.0","computerName":"TEST-MAC-001","enrollmentActions":[{"label":"Install Apps","status":"finished"}]}'- 200 OK = token matches and the webhook payload was accepted
-
400 Bad Request = token matched, but the JSON payload is not valid; check that
name,event, timestamps, and required fields match the examples -
401 Unauthorized = token is missing or does not match
WEBHOOK_TOKEN
Note: Setup Manager also supports Basic Auth (
username+passwordkeys instead oftoken), but this Worker validates thetokenkey through theAuthorizationheader. Use thetokenkey for compatibility.
Cloudflare Access is optional. Enable it when you want only authorized users to view enrollment data in the dashboard. It sits at Cloudflare's edge - before dashboard requests reach your Worker.
User -> Cloudflare Access (optional login gate) -> Dashboard (Worker)
Device -> POST /webhook (bypasses Access) -> Worker -> D1
Important
Choose your login method first. One-time PIN is the fastest setup: Cloudflare emails a code to users allowed by your Access policy. You can also connect an existing identity provider such as GitHub, Google Workspace, Microsoft Entra ID, Okta, JumpCloud, SAML, or OIDC. The steps below use One-time PIN because it requires the least setup, but Setup Manager HUD works with any Cloudflare Access identity provider.
- Enable Zero Trust: Go to dash.cloudflare.com, choose a team name, select Free plan
-
Choose a login method: Go to Zero Trust -> Integrations -> Identity providers
- For the quickest setup, choose Add new identity provider -> One-time PIN.
- For existing organization login, add your provider, such as GitHub, Google, Microsoft, Okta, SAML, or OIDC.
- If your provider already appears under Your identity providers, you can use the existing provider.
-
Create application: Go to Zero Trust -> Access -> Applications -> Add an application -> Self-hosted
- Name:
Setup Manager HUD - Domain:
your-worker.workers.dev
- Name:
- Create Allow policy: Include your email address or domain
-
Create Bypass policy for webhook:
- Policy name:
Bypass webhook - Action:
Bypass - Include:
Everyone - Path:
/webhook - Position this ABOVE the Allow policy
- Policy name:
If you enable Cloudflare Access, it protects the dashboard at the edge. JWT validation adds a second optional check inside the Worker: dashboard, API, and WebSocket requests must include a valid Cloudflare Access token before the Worker serves data. If the token is missing or invalid, the Worker returns 403 and keeps the normal security headers, including CSP and HSTS.
This is useful for production because it makes the Worker reject dashboard traffic that did not come through your Access application. It does not apply to /webhook; that route must bypass Access so Setup Manager devices can post events, and it is protected separately by WEBHOOK_TOKEN.
After creating the Access application:
- Open Cloudflare Zero Trust
- Go to Access -> Applications
- Open your Setup Manager HUD application
- Copy the Application Audience (AUD) Tag for
CF_ACCESS_AUD - Find your team domain in Settings -> Custom Pages or your Zero Trust URL, for example
your-team.cloudflareaccess.com
Add these values to wrangler.toml:
[vars]
CF_ACCESS_AUD = "paste-your-audience-tag-here"
CF_ACCESS_TEAM_DOMAIN = "your-team.cloudflareaccess.com"If you used the Deploy to Cloudflare button, Cloudflare created a copy of this project in your GitHub account and deployed from that repository. wrangler.toml is in that GitHub repository.
You have two common ways to update it:
Option A: edit locally and deploy with Wrangler
git clone https://github.com/YOUR-USERNAME/setupmanagerhud.git
cd setupmanagerhud
npm install
npx wrangler login
# edit wrangler.toml and add CF_ACCESS_AUD / CF_ACCESS_TEAM_DOMAIN
npm run deployIf you want your GitHub repo to keep this configuration too, commit and push the wrangler.toml change:
git add wrangler.toml
git commit -m "Enable Cloudflare Access JWT validation"
git pushOption B: edit in GitHub and deploy with GitHub Actions
- Open your copied
setupmanagerhudrepository in GitHub - Edit
wrangler.tomland add the[vars]values - Commit the change
- Make sure GitHub Actions are enabled for the repository
- Confirm the repository secrets exist:
CLOUDFLARE_API_TOKENCLOUDFLARE_ACCOUNT_ID
- Go to Actions -> Deploy to Cloudflare Workers -> Run workflow
Tip
Deploying locally with npm run deploy updates Cloudflare immediately, but it does not update GitHub unless you also commit and push. Editing in GitHub keeps the repository as the source of truth, but the Worker is updated only after the deploy workflow runs.
When optional Cloudflare Access is enabled:
| Route | Authentication | Who |
|---|---|---|
/ (dashboard) |
Cloudflare Access | Authorized users only |
/ws (WebSocket) |
Cloudflare Access | Authorized users only |
/api/* |
Cloudflare Access | Authorized users only |
/webhook |
Bypassed by Access, protected by WEBHOOK_TOKEN
|
Setup Manager devices only |
Without Cloudflare Access, the dashboard routes are public to anyone who knows the Worker URL. /webhook still requires WEBHOOK_TOKEN either way.
Add a Cloudflare WAF rate limiting rule to prevent webhook flooding:
- Cloudflare dashboard -> Security -> WAF -> Rate limiting rules
- Create rule:
- Match: URI Path equals
/webhook - Rate: 30 requests per minute per IP (adjust for fleet size)
- Action: Block for 1 minute
- Match: URI Path equals
| Fleet Size | Concurrent Enrollments | Suggested Rate |
|---|---|---|
| Small (1-10 devices) | 1-10 | 30 req/min |
| Medium (10-50 devices) | 10-50 | 120 req/min |
| Large (50+ from one IP) | 50+ | 300 req/min |
Tip: Each device sends exactly 2 webhooks per enrollment (started + finished). If devices share a NAT gateway, use a higher limit.