> ## 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.

# Webhooks Quickstart

> Receive run_complete events from Cyberdesk without polling

## Overview

Webhooks notify your app when something happens in Cyberdesk. Instead of polling, subscribe to the event types you care about (see the Event Catalog in the Webhooks portal). The most common one is `run_complete`, which we’ll use below as an example.

<Info>
  Typical flow: create a run with the SDK, return immediately to your user, and update your system when you receive the webhook.
</Info>

## 1) Enable Webhooks in the Dashboard

1. Go to the Cyberdesk Dashboard → Webhooks.
2. Click "Activate Webhooks" (creates a Svix Application for your organization).
3. Click "Add Endpoint" and enter your HTTPS URL.
4. Subscribe to the event types you need (for getting started, `run_complete` is typical).
5. Copy the endpoint's signing secret (`whsec_...`).

<Note>
  Each endpoint has its own secret. Keep separate endpoints/secrets for dev and prod.
</Note>

## 2) Implement the Endpoint (verify signatures)

### Install dependencies

<Tabs>
  <Tab title="TypeScript / Node">
    ```bash theme={null}
    npm install cyberdesk svix
    # or
    yarn add cyberdesk svix
    # or
    pnpm add cyberdesk svix
    ```
  </Tab>

  <Tab title="Python">
    ```bash theme={null}
    pip install cyberdesk svix
    # or
    poetry add cyberdesk svix
    # or
    pipenv install cyberdesk svix
    ```
  </Tab>
</Tabs>

<Note>
  We’re working on a managed Webhooks SDK with built‑in verification and typing. If you’d like this prioritized, please let the team know.
</Note>

### Endpoint handlers

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

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

    function assertRunCompletedEvent(x: unknown): asserts x is RunCompletedEvent {
      if (!x || (x as any).event_type !== "run_complete" || !(x as any).run) {
        throw new Error("Invalid run_complete payload");
      }
    }

    app.post("/webhooks/cyberdesk", (req, res) => {
      const secret = process.env.SVIX_WEBHOOK_SECRET!;
      const wh = new Webhook(secret);

      const headers = {
        "svix-id": req.header("svix-id")!,
        "svix-timestamp": req.header("svix-timestamp")!,
        "svix-signature": req.header("svix-signature")!,
      };

      try {
        const payload = wh.verify(req.body, headers);
        assertRunCompletedEvent(payload);
        // Narrowed type: payload is RunCompletedEvent; run is fully typed
        const run: RunResponse = payload.run;
        // idempotency: upsert on headers["svix-id"] or payload.event_id
        // handle success/error/cancelled
        res.status(200).end();
      } catch (err) {
        if (err instanceof WebhookVerificationError) return res.status(400).end();
        res.status(500).end();
      }
    });
    ```

    ```ts theme={null}
    import { createCyberdeskClient } from "cyberdesk";
    import type { RunResponse } from "cyberdesk";

    async function onRunComplete(run: RunResponse) {
      const client = createCyberdeskClient(process.env.CYBERDESK_API_KEY!);

      // Example A: Start a follow-up workflow
      await client.runs.create({
        workflow_id: process.env.NEXT_WORKFLOW_ID!,
        input_values: { summary: run.output_data?.summary }
      });

      // Example B: Persist and enqueue for async processing
      await db.runs.upsert({ id: run.id, status: run.status, output: run.output_data });
      await queue.enqueue("postprocess-run", { runId: run.id });
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    from fastapi import FastAPI, Request, HTTPException
    from svix.webhooks import Webhook, WebhookVerificationError
    from openapi_client.cyberdesk_cloud_client.models.run_completed_event import RunCompletedEvent
    from openapi_client.cyberdesk_cloud_client.models.run_response import RunResponse
    from typing import cast
    import os

    app = FastAPI()

    @app.post("/webhooks/cyberdesk")
    async def cyberdesk_webhook(request: Request):
        secret = os.environ["SVIX_WEBHOOK_SECRET"]
        wh = Webhook(secret)

        payload = await request.body()
        headers = {
            "svix-id": request.headers.get("svix-id"),
            "svix-timestamp": request.headers.get("svix-timestamp"),
            "svix-signature": request.headers.get("svix-signature"),
        }

        try:
            data = wh.verify(payload, headers)
            evt = RunCompletedEvent.from_dict(data)
            # Help some IDEs narrow the type for evt.run
            run: RunResponse = cast(RunResponse, evt.run)
            # idempotency: upsert on headers["svix-id"] or evt.event_id
            return {"ok": True}
        except WebhookVerificationError:
            raise HTTPException(status_code=400, detail="Invalid signature")
    ```

    ```python theme={null}
    from cyberdesk import CyberdeskClient, RunCreate
    from openapi_client.cyberdesk_cloud_client.models.run_response import RunResponse

    async def on_run_complete(run: RunResponse):
        client = CyberdeskClient(os.environ["CYBERDESK_API_KEY"])

        # Handle different statuses
        if run.status == "success":
            # Example A: Start a follow-up workflow
            follow_up = RunCreate(
                workflow_id=os.environ["NEXT_WORKFLOW_ID"],
                input_values={"summary": (run.output_data or {}).get("summary")}
            )
            await client.runs.create(follow_up)

            # Example B: Persist and enqueue
            await db.upsert_run(run.id, run.status, run.output_data)
            await queue.enqueue("postprocess-run", {"run_id": run.id})
        
        elif run.status == "error":
            # Handle errors
            await notify_error(run.id, run.error)
    ```
  </Tab>
</Tabs>

<Note>
  Type safety:

  * TypeScript: use a type guard (like `assertRunCompletedEvent`) to narrow the Svix‑verified payload to `RunCompletedEvent`, then type `run` as `RunResponse`.
  * Python: `RunCompletedEvent.from_dict(data)` returns a typed attrs object; some IDEs may still show `Any | RunResponse` for `evt.run`, so `cast(RunResponse, evt.run)` helps IDEs while remaining safe after validation. Note: The SDK uses attrs classes, not Pydantic, so use `from_dict()` not `model_validate()`.
</Note>

## 3) Event payload

Payloads vary by event type. Refer to the Event Catalog in the portal for up‑to‑date schemas. Example for `run_complete`:

```json theme={null}
{
  "event_id": "uuid",
  "event_type": "run_complete",
  "occurred_at": "2025-08-16T19:19:44Z",
  "run": { /* Run data: id, workflow_id, status, error, output_data, input_values, attachment ids, created_at, ... */ }
}
```

Use `run.status` to branch your logic and `output_data` or attachments to continue your process.

`run_message_history` is omitted from webhook payloads to keep deliveries small and reliable. If you need message history, fetch the run later through the API or SDK.

<Info>
  Sensitive inputs: plaintext secrets are never included in webhook payloads. If a run used sensitive variables, you'll see `run.sensitive_input_aliases`, which maps each sensitive input key (for example, `password`) to the secure secret identifier used during execution. Actual values stay in the secure vault and are deleted after completion.
</Info>

## 4) Test

* In the Webhooks tab, send a test event to your endpoint.
* Use the Logs/Activity views to inspect payloads and delivery attempts, replay failures, and recover from downtime.

## Learn more

<CardGroup cols={2}>
  <Card title="Detailed Webhook Guide" icon="plug" href="/webhooks/detailed-guide">
    Signature details, retries, troubleshooting, and recovery patterns.
  </Card>

  <Card title="Webhook Transformations" icon="shuffle" href="/webhooks/transformations">
    Reshape or reduce webhook payloads before they reach your endpoint.
  </Card>

  <Card title="React to Post-run Checks" icon="check-double" href="/webhooks/detailed-guide#react-to-post-run-check-failures">
    Handle Post-run Check results from `run_complete` events.
  </Card>

  <Card title="Workflow Chains" icon="link" href="/concepts/sessions-and-chains">
    Run multi-step workflows on one reserved session.
  </Card>
</CardGroup>
