> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cyberdesk.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Downed Machines Webhook

> Alert your systems when important Cyberdesk machines appear unreachable

## Overview

The `downed_machines` webhook tells your system when one or more important machines appear to need attention. Most teams route this event directly into Slack so operators see machine issues where they already work.

Cyberdesk only monitors machines that you explicitly opt in. On a desktop's detail page, enable **Downed machine alerts** to include that machine in the monitor.

<Note>
  Machines are not monitored by default. This avoids alerts for desktops that are intentionally offline or only used occasionally.
</Note>

## Send Machine Alerts To Slack

The fastest way to use machine alerts is with Cyberdesk's existing [Slack integration](/webhooks/slack-integration). You do not need to host your own webhook receiver unless you want custom routing logic outside Slack.

<Steps>
  <Step title="Create a machine-alert Slack endpoint">
    Go to Dashboard -> Webhooks -> Endpoints, click **+ Add Endpoint**, then open the destination dropdown in the top right. Choose **Send downed machine alerts to Slack**.
  </Step>

  <Step title="Connect your workspace and channel">
    Click **Connect to Slack**. Slack will ask you to choose the workspace and channel that should receive machine alerts. Approve the connection, and you'll return to Cyberdesk.
  </Step>

  <Step title="Review the selected event">
    The connector subscribes to the `downed_machines` event and includes a default Slack transformation that formats machine alerts into a structured Slack message.
  </Step>

  <Step title="Create the endpoint">
    Click **Create**. That's it: newly downed opted-in machines will now post to your selected Slack channel.
  </Step>
</Steps>

<Tip>
  You can use the default Slack message as-is. If your team wants different wording, routing context, or Slack Block Kit layout, edit the endpoint's transformation code before creating it or any time later.
</Tip>

<Accordion title="Default Slack transformation">
  The machine-alert Slack connector includes a default Svix transformation similar to this. You only need to edit it if you want to customize the final Slack message:

  ```javascript theme={null}
  function handler(webhook) {
    if (webhook.eventType !== "downed_machines") {
      return webhook;
    }

    const payload = webhook.payload || {};
    const machines = Array.isArray(payload.machines) ? payload.machines : [];

    if (machines.length === 0) {
      webhook.cancel = true;
      return webhook;
    }

    const maxMachinesToShow = 8;
    const visibleMachines = machines.slice(0, maxMachinesToShow);
    const hiddenCount = Math.max(0, machines.length - visibleMachines.length);
    const occurredAt = payload.occurred_at
      ? new Date(payload.occurred_at).toLocaleString()
      : "unknown time";

    function escapeMrkdwn(value) {
      return String(value ?? "")
        .replace(/&/g, "&amp;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;");
    }

    function truncate(value, maxLength) {
      const text = escapeMrkdwn(value || "Machine appears unreachable");
      return text.length > maxLength ? `${text.slice(0, maxLength - 1)}...` : text;
    }

    const machineBlocks = visibleMachines.map((machine, index) => {
      const name = escapeMrkdwn(machine.name || machine.machine_id || "Unnamed machine");
      const id = escapeMrkdwn(machine.machine_id || "unknown");
      const status = escapeMrkdwn(machine.status || "unknown");
      const reason = truncate(machine.reason, 180);

      return {
        type: "section",
        text: {
          type: "mrkdwn",
          text:
            `*:desktop_computer: ${index + 1}. ${name}*\n` +
            `*Status:* \`${status}\`\n` +
            `*Machine ID:* \`${id}\`\n` +
            `*Reason:* ${reason}`
        }
      };
    });

    const overflowBlocks = hiddenCount > 0
      ? [
          {
            type: "context",
            elements: [
              {
                type: "mrkdwn",
                text: `+ ${hiddenCount} additional downed machine${hiddenCount === 1 ? "" : "s"} not shown. Open Cyberdesk for the full list.`
              }
            ]
          }
        ]
      : [];

    webhook.payload = {
      blocks: [
        {
          type: "header",
          text: {
            type: "plain_text",
            text: "Cyberdesk Machine Alert",
            emoji: true
          }
        },
        {
          type: "section",
          text: {
            type: "mrkdwn",
            text:
              `*:rotating_light: ${machines.length} monitored machine${machines.length === 1 ? "" : "s"} currently need attention.*\n` +
              "This alert fired because at least one opted-in machine newly entered the downed state."
          }
        },
        {
          type: "context",
          elements: [
            {
              type: "mrkdwn",
              text: `Event: \`${payload.event_type || webhook.eventType}\` | Org: \`${payload.organization_id || "unknown"}\` | Detected: ${occurredAt}`
            }
          ]
        },
        { type: "divider" },
        ...machineBlocks,
        ...overflowBlocks,
        { type: "divider" },
        {
          type: "actions",
          elements: [
            {
              type: "button",
              text: {
                type: "plain_text",
                text: "Open Desktops Dashboard"
              },
              url: "https://cyberdesk.io/dashboard/desktops"
            }
          ]
        }
      ]
    };

    return webhook;
  }
  ```
</Accordion>

<Tip>
  For screenshots and more detail on the Slack setup flow, see the full [Slack integration guide](/webhooks/slack-integration). For more on editing and testing transform code, see [Webhook Transformations](/webhooks/transformations).
</Tip>

## When It Fires

Cyberdesk checks opted-in machines on a recurring schedule. A machine appears down when either:

* Its Cyberdesk status is not `connected`.
* Its status is `connected`, but Cyberdesk cannot complete a lightweight display dimensions probe after retries.

The webhook fires only when a **new** opted-in machine enters the down set. The payload still includes the full array of machines that currently appear down for the organization, so your alert can include context about any machines that were already down.

Cyberdesk does not send a separate recovery webhook when a machine comes back online.

## Enable Monitoring For A Machine

1. Go to Dashboard -> Desktops.
2. Enable alerts from the **Alerts** checkbox in the desktops table, or open a desktop and enable **Downed machine alerts** in the Machine Status card.
3. In Dashboard -> Webhooks, make sure your Slack endpoint or webhook endpoint subscribes to `downed_machines`.

## Payload

```json theme={null}
{
  "event_id": "550e8400-e29b-41d4-a716-446655440000",
  "event_type": "downed_machines",
  "occurred_at": "2026-07-08T18:30:00Z",
  "organization_id": "org_123",
  "machines": [
    {
      "machine_id": "9f2c0c07-f694-4c8f-b4d5-5a6f936a9a75",
      "name": "checkout-worker-1",
      "organization_id": "org_123",
      "status": "connected",
      "last_seen": "2026-07-08T18:21:00Z",
      "reason": "Machine dimensions probe timeout"
    }
  ]
}
```

## Verify And Handle The Event

<Tabs>
  <Tab title="TypeScript / Node">
    ```ts theme={null}
    import express from "express";
    import { Webhook } from "svix";
    import type { DownedMachinesEvent } from "cyberdesk";

    const app = express();
    app.use(express.raw({ type: "application/json" }));

    function assertDownedMachinesEvent(x: unknown): asserts x is DownedMachinesEvent {
      if (!x || (x as any).event_type !== "downed_machines" || !Array.isArray((x as any).machines)) {
        throw new Error("Invalid downed_machines payload");
      }
    }

    app.post("/webhooks/cyberdesk", (req, res) => {
      const wh = new Webhook(process.env.SVIX_WEBHOOK_SECRET!);
      const payload = wh.verify(req.body, {
        "svix-id": req.header("svix-id")!,
        "svix-timestamp": req.header("svix-timestamp")!,
        "svix-signature": req.header("svix-signature")!,
      });

      assertDownedMachinesEvent(payload);

      for (const machine of payload.machines) {
        console.log(`${machine.name ?? machine.machine_id}: ${machine.reason}`);
      }

      res.status(204).send();
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import os
    from flask import Flask, request
    from svix.webhooks import Webhook

    app = Flask(__name__)


    @app.post("/webhooks/cyberdesk")
    def cyberdesk_webhook():
        wh = Webhook(os.environ["SVIX_WEBHOOK_SECRET"])
        payload = wh.verify(
            request.get_data(),
            {
                "svix-id": request.headers["svix-id"],
                "svix-timestamp": request.headers["svix-timestamp"],
                "svix-signature": request.headers["svix-signature"],
            },
        )

        if payload.get("event_type") != "downed_machines":
            return "", 204

        for machine in payload["machines"]:
            print(f"{machine.get('name') or machine['machine_id']}: {machine['reason']}")

        return "", 204
    ```
  </Tab>
</Tabs>

## Recommended Alerting Pattern

Use `event_id` for idempotency, then fan out to your alerting system. Because Cyberdesk only sends this event when a newly downed machine is found, your receiver usually does not need to dedupe repeated deliveries beyond Svix retry handling.

<Info>
  Svix may retry deliveries when your endpoint fails or times out. Always verify signatures and treat `event_id` or the `svix-id` header as idempotency keys.
</Info>
