Skip to content

n8n Companion Workflows

motionbug edited this page May 19, 2026 · 1 revision

n8n Companion Workflows

Setup Manager HUD should remain the real-time dashboard. n8n is a good companion for automation: receive the same Setup Manager webhook, store a copy, and notify a team when a finished enrollment has failed actions.

Use one of these workflows:

  • Slack workflow: store every webhook and post failed-action alerts to Slack.
  • Teams workflow: store every webhook and post failed-action alerts to Microsoft Teams.

The two workflows are intentionally separate so a customer can import or build only the notification target they use.

For n8n reference, see the official docs for the Webhook node, Data Table node, HTTP Request node, and workflow import/export.


Before You Start

Create one n8n Data Table named setup_manager_events with these columns:

Column Type Notes
event_id String Stable event identifier generated by n8n
received_at Date/time or String When n8n received the webhook
event String Setup Manager event name
serial_number String Device serial number
model_name String Device model
macos_version String macOS version
started_at String Setup Manager started timestamp
finished_at String Setup Manager finished timestamp, if present
duration_seconds Number Finished enrollment duration, if present
failed_action_count Number Number of failed enrollment actions
failed_action_labels String Comma-separated failed action names
payload_json String Full webhook payload copy

Create a separate token for n8n, for example N8N_WEBHOOK_TOKEN, and save it securely. Do not reuse a live customer token in public examples.

Important

Setup Manager sends the token value as the raw Authorization header. In the n8n Webhook node, use Header Auth with header name Authorization and the exact token value.


Setup Manager Profile

There are two safe patterns:

  • Sidecar: Setup Manager sends events to Setup Manager HUD and n8n if your Setup Manager version and profile structure support multiple destinations.
  • Forwarding layer: Setup Manager sends events to n8n, then n8n stores, alerts, and forwards the unchanged payload to Setup Manager HUD.

Do not accidentally replace Setup Manager HUD with n8n unless that is your intent. Setup Manager HUD should still receive the webhook if you want the dashboard, D1 history, and live view to stay current.

Example n8n webhook target values:

<key>webhooks</key>
<dict>
    <key>started</key>
    <dict>
        <key>url</key>
        <string>https://your-n8n.example.com/webhook/setup-manager-hud-slack</string>
        <key>token</key>
        <string>your-n8n-webhook-token-here</string>
    </dict>
    <key>finished</key>
    <dict>
        <key>url</key>
        <string>https://your-n8n.example.com/webhook/setup-manager-hud-slack</string>
        <key>token</key>
        <string>your-n8n-webhook-token-here</string>
    </dict>
</dict>

For the Teams workflow, use the Teams path instead:

https://your-n8n.example.com/webhook/setup-manager-hud-teams

Shared Normalization Code

Use this Code node after the Webhook node in both workflows.

const input = $input.first().json;
const payload = input.body ?? input;

const required = [
  "name",
  "event",
  "timestamp",
  "started",
  "modelName",
  "modelIdentifier",
  "macOSBuild",
  "macOSVersion",
  "serialNumber",
  "setupManagerVersion",
];

for (const field of required) {
  if (typeof payload[field] !== "string" || payload[field].trim() === "") {
    throw new Error(`Invalid Setup Manager payload: missing ${field}`);
  }
}

if (
  payload.event === "com.jamf.setupmanager.started" &&
  payload.name !== "Started"
) {
  throw new Error('Invalid Setup Manager payload: started event must use name "Started"');
}

if (
  payload.event === "com.jamf.setupmanager.finished" &&
  payload.name !== "Finished"
) {
  throw new Error('Invalid Setup Manager payload: finished event must use name "Finished"');
}

if (payload.event === "com.jamf.setupmanager.finished") {
  if (typeof payload.finished !== "string" || payload.finished.trim() === "") {
    throw new Error("Invalid Setup Manager payload: finished events require finished");
  }
  if (typeof payload.duration !== "number" || payload.duration < 0) {
    throw new Error("Invalid Setup Manager payload: finished events require numeric duration");
  }
}

const failedActions = Array.isArray(payload.enrollmentActions)
  ? payload.enrollmentActions.filter((action) => action?.status === "failed")
  : [];

const failedLabels = failedActions
  .map((action) => action.label)
  .filter(Boolean);

const isFinished = payload.event === "com.jamf.setupmanager.finished";
const shouldAlert = isFinished && failedActions.length > 0;
const eventId = [
  payload.event,
  payload.serialNumber,
  payload.timestamp,
].join(":");

const summary = [
  `Device: ${payload.computerName || payload.serialNumber}`,
  `Serial: ${payload.serialNumber}`,
  `Model: ${payload.modelName}`,
  `macOS: ${payload.macOSVersion} (${payload.macOSBuild})`,
  `Failed actions: ${failedLabels.join(", ") || "None"}`,
].join("\n");

return [
  {
    json: {
      event_id: eventId,
      received_at: new Date().toISOString(),
      event: payload.event,
      name: payload.name,
      serial_number: payload.serialNumber,
      model_name: payload.modelName,
      model_identifier: payload.modelIdentifier,
      macos_version: payload.macOSVersion,
      macos_build: payload.macOSBuild,
      setup_manager_version: payload.setupManagerVersion,
      started_at: payload.started,
      finished_at: payload.finished ?? "",
      duration_seconds: payload.duration ?? null,
      computer_name: payload.computerName ?? "",
      failed_action_count: failedActions.length,
      failed_action_labels: failedLabels.join(", "),
      payload_json: JSON.stringify(payload),
      should_alert: shouldAlert,
      slack_payload: {
        text: shouldAlert
          ? `Setup Manager enrollment failed actions\n${summary}`
          : `Setup Manager event received: ${payload.name} ${payload.serialNumber}`,
      },
      teams_payload: {
        type: "message",
        attachments: [
          {
            contentType: "application/vnd.microsoft.card.adaptive",
            content: {
              $schema: "http://adaptivecards.io/schemas/adaptive-card.json",
              type: "AdaptiveCard",
              version: "1.4",
              body: [
                {
                  type: "TextBlock",
                  text: "Setup Manager failed actions",
                  weight: "Bolder",
                  size: "Medium",
                },
                {
                  type: "TextBlock",
                  text: summary,
                  wrap: true,
                },
              ],
            },
          },
        ],
      },
    },
  },
];

Workflow 1: Slack Failed Actions

Node order:

  1. Webhook

    • HTTP Method: POST
    • Path: setup-manager-hud-slack
    • Authentication: Header Auth
    • Header name: Authorization
    • Header value: your-n8n-webhook-token-here
    • Respond: immediately with 200
  2. Code

    • Paste the shared normalization code above.
  3. Data Table

    • Resource: Row
    • Operation: Insert or Upsert
    • Table: setup_manager_events
    • Map each Data Table column to the matching field from the Code node output.
    • If using Upsert, use event_id as the unique key.
  4. Optional HTTP Request: Forward to Setup Manager HUD

    • Use this only when n8n is acting as the forwarding layer.
    • Method: POST
    • URL: your Setup Manager HUD /webhook URL
    • Header: Authorization: your-setupmanagerhud-webhook-token
    • Send body as JSON:
={{JSON.parse($json.payload_json)}}
  1. If

    • Continue only when should_alert is true.
  2. HTTP Request

    • Method: POST
    • URL: your Slack incoming webhook URL
    • Send body as JSON:
{
  "text": "={{$json.slack_payload.text}}"
}

Note

Slack incoming webhook URLs are secrets. Store them in n8n credentials or variables where possible, and do not paste real URLs into shared workflow exports.


Workflow 2: Teams Failed Actions

Node order:

  1. Webhook

    • HTTP Method: POST
    • Path: setup-manager-hud-teams
    • Authentication: Header Auth
    • Header name: Authorization
    • Header value: your-n8n-webhook-token-here
    • Respond: immediately with 200
  2. Code

    • Paste the shared normalization code above.
  3. Data Table

    • Resource: Row
    • Operation: Insert or Upsert
    • Table: setup_manager_events
    • Map each Data Table column to the matching field from the Code node output.
    • If using Upsert, use event_id as the unique key.
  4. Optional HTTP Request: Forward to Setup Manager HUD

    • Use this only when n8n is acting as the forwarding layer.
    • Method: POST
    • URL: your Setup Manager HUD /webhook URL
    • Header: Authorization: your-setupmanagerhud-webhook-token
    • Send body as JSON:
={{JSON.parse($json.payload_json)}}
  1. If

    • Continue only when should_alert is true.
  2. HTTP Request

    • Method: POST
    • URL: your Microsoft Teams incoming webhook or Teams workflow webhook URL
    • Send body as JSON:
={{$json.teams_payload}}

Microsoft's current Teams guidance uses incoming webhooks through Teams Workflows and Adaptive Card JSON payloads. See Microsoft's Teams incoming webhook guidance.


Testing

Use the started and finished curl examples in Security#verifying-token-setup, but replace the URL with your n8n webhook URL and the token with your n8n webhook token.

The started test should store a row but not notify Slack or Teams.

The finished test should notify only when at least one enrollmentActions item has:

{
  "status": "failed"
}

Example failed action:

{
  "label": "Install Apps",
  "status": "failed"
}

Operating Notes

  • Keep n8n alerting separate from Setup Manager HUD's WEBHOOK_TOKEN.
  • Treat Slack and Teams webhook URLs as secrets.
  • Store every webhook, but alert only on finished events with failed actions.
  • Keep Setup Manager HUD as the dashboard source of truth; use n8n for copies, notifications, and downstream automation.
  • If n8n is down, Setup Manager HUD should still receive its own webhook directly.