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

# Events WebSocket

> Subscribe to desktop events - window focus, clipboard, files, processes, idle state.

Subscribe to real-time desktop events over WebSocket. Monitor window focus changes, clipboard updates, file modifications, process lifecycle, screen resolution changes, and idle/active state - all pushed to your client as they happen.

## Connection URL

```
wss://www.orgo.ai/desktops/{computer_id}/ws/events?token={password}
```

### Authentication

The events WebSocket requires the computer's password as the `token` query parameter. Retrieve it from the [Get VNC Password](/api-reference/computers/vnc-password) endpoint before connecting.

Connections without a valid token are rejected with close code `4401`.

### Query Parameters

<ParamField query="token" type="string" required>
  Computer password. Retrieve via the [Get VNC Password](/api-reference/computers/vnc-password) endpoint.
</ParamField>

## Event Catalog

You can also fetch the event catalog as JSON without a WebSocket connection:

```bash theme={null}
curl https://www.orgo.ai/api/computers/{id}/events
```

### Available Event Types

| Type                 | Description                                                | Detection           |
| -------------------- | ---------------------------------------------------------- | ------------------- |
| `window_focus`       | Active window changed                                      | Polled every 100 ms |
| `window_open`        | New window opened                                          | Polled every 100 ms |
| `window_close`       | Window closed                                              | Polled every 100 ms |
| `clipboard`          | Clipboard content changed                                  | Polled every 250 ms |
| `file_change`        | File created, modified, or deleted on Desktop or Downloads | inotify (instant)   |
| `screen_change`      | Display resolution changed                                 | Polled every 1 s    |
| `audio_stream_start` | Audio playback started                                     | Polled every 500 ms |
| `audio_stream_stop`  | Audio playback stopped                                     | Polled every 500 ms |
| `process_start`      | New process started                                        | Polled every 2 s    |
| `process_stop`       | Process exited                                             | Polled every 2 s    |
| `idle`               | No user input for 30 seconds                               | Polled every 1 s    |
| `active`             | User input resumed after idle                              | Polled every 1 s    |

## Subscription Model

New connections receive **no events** until they send a `subscribe` message. This lets you opt in to only the event types you care about, reducing noise and bandwidth.

Subscriptions are:

* **Per-connection** - each WebSocket connection has its own subscription set
* **Additive** - subscribe to more types at any time
* **Selective** - unsubscribe from specific types without disconnecting

## Message Protocol

### Client → Server Messages

<AccordionGroup>
  <Accordion title="subscribe" icon="bell">
    Start receiving specific event types. You can call this multiple times to add more types.

    ```json theme={null}
    {
      "type": "subscribe",
      "event_types": ["window_focus", "clipboard", "file_change"]
    }
    ```

    <ParamField body="type" type="string" required>
      Must be `"subscribe"`.
    </ParamField>

    <ParamField body="event_types" type="string[]" required>
      Array of event type names to subscribe to. See the [event catalog](#available-event-types) for valid types.
    </ParamField>
  </Accordion>

  <Accordion title="unsubscribe" icon="bell-slash">
    Stop receiving specific event types.

    ```json theme={null}
    {
      "type": "unsubscribe",
      "event_types": ["clipboard"]
    }
    ```

    <ParamField body="type" type="string" required>
      Must be `"unsubscribe"`.
    </ParamField>

    <ParamField body="event_types" type="string[]" required>
      Array of event type names to unsubscribe from.
    </ParamField>
  </Accordion>

  <Accordion title="ping" icon="heart-pulse">
    Send a heartbeat ping to keep the connection alive.

    ```json theme={null}
    {
      "type": "ping"
    }
    ```
  </Accordion>
</AccordionGroup>

### Server → Client Messages

<AccordionGroup>
  <Accordion title="event" icon="bolt">
    A desktop event matching one of your subscribed types.

    ```json theme={null}
    {
      "type": "event",
      "event": {
        "type": "window_focus",
        "timestamp": "2026-03-03T12:00:00.123Z",
        "data": {
          "window_id": "0x3200004",
          "title": "Google Chrome"
        }
      }
    }
    ```

    <ResponseField name="type" type="string">
      Always `"event"`.
    </ResponseField>

    <ResponseField name="event" type="object">
      The event payload.
    </ResponseField>

    <ResponseField name="event.type" type="string">
      Event type name (e.g., `"window_focus"`, `"clipboard"`).
    </ResponseField>

    <ResponseField name="event.timestamp" type="string">
      ISO 8601 timestamp of when the event occurred.
    </ResponseField>

    <ResponseField name="event.data" type="object">
      Event-specific data. See [Event Data Schemas](#event-data-schemas) below.
    </ResponseField>
  </Accordion>

  <Accordion title="subscribed" icon="check">
    Confirmation that your subscription was updated.

    ```json theme={null}
    {
      "type": "subscribed",
      "message": "ok"
    }
    ```
  </Accordion>

  <Accordion title="unsubscribed" icon="check">
    Confirmation that event types were removed from your subscription.

    ```json theme={null}
    {
      "type": "unsubscribed",
      "message": "ok"
    }
    ```
  </Accordion>

  <Accordion title="pong" icon="heart-pulse">
    Response to a ping message.

    ```json theme={null}
    {
      "type": "pong"
    }
    ```
  </Accordion>

  <Accordion title="error" icon="circle-exclamation">
    Error message from the server.

    ```json theme={null}
    {
      "type": "error",
      "message": "Unknown event type: invalid_type"
    }
    ```

    <ResponseField name="type" type="string">
      Always `"error"`.
    </ResponseField>

    <ResponseField name="message" type="string">
      Human-readable error description.
    </ResponseField>
  </Accordion>
</AccordionGroup>

## Event Data Schemas

Each event type includes a `data` object with type-specific fields:

### Window events

```json theme={null}
// window_focus
{ "window_id": "0x3200004", "title": "Google Chrome" }

// window_open
{ "window_id": "0x3200008", "title": "Terminal" }

// window_close
{ "window_id": "0x3200008" }
```

### Clipboard

```json theme={null}
// clipboard
{ "content": "copied text from the clipboard" }
```

### File changes

```json theme={null}
// file_change
{ "path": "/home/user/Desktop/report.pdf", "action": "created" }
{ "path": "/home/user/Downloads/data.csv", "action": "modified" }
{ "path": "/home/user/Desktop/old.txt", "action": "deleted" }
```

Monitored directories: `/home/user/Desktop` and `/home/user/Downloads`. Detection is via Linux inotify - file events are delivered instantly.

### Screen

```json theme={null}
// screen_change
{ "width": 1920, "height": 1080 }
```

### Audio

```json theme={null}
// audio_stream_start
{}

// audio_stream_stop
{}
```

### Process lifecycle

```json theme={null}
// process_start
{ "pid": 1234, "name": "chrome" }

// process_stop
{ "pid": 1234, "name": "chrome" }
```

### Idle / Active

```json theme={null}
// idle
{ "idle_seconds": 30 }

// active
{}
```

## Examples

<CodeGroup>
  ```javascript JavaScript theme={null}
  const computerId = 'a3bb189e-8bf9-3888-9912-ace4e6543002';
  const apiKey = process.env.ORGO_API_KEY;

  // Step 1: Get the computer password
  const res = await fetch(
    `https://www.orgo.ai/api/computers/${computerId}/vnc-password`,
    { headers: { 'Authorization': `Bearer ${apiKey}` } }
  );
  const { password } = await res.json();

  // Step 2: Connect to events stream
  const ws = new WebSocket(
    `wss://www.orgo.ai/desktops/${computerId}/ws/events?token=${password}`
  );

  ws.onopen = () => {
    // Subscribe to the events you care about
    ws.send(JSON.stringify({
      type: 'subscribe',
      event_types: ['window_focus', 'clipboard', 'file_change', 'idle', 'active']
    }));
  };

  ws.onmessage = (event) => {
    const msg = JSON.parse(event.data);

    switch (msg.type) {
      case 'event':
        const { type, timestamp, data } = msg.event;
        console.log(`[${timestamp}] ${type}:`, data);

        // React to specific events
        if (type === 'window_focus') {
          console.log(`User switched to: ${data.title}`);
        }
        if (type === 'clipboard') {
          console.log(`Clipboard: ${data.content}`);
        }
        if (type === 'idle') {
          console.log(`Desktop idle for ${data.idle_seconds}s`);
        }
        break;

      case 'subscribed':
        console.log('Subscription confirmed');
        break;

      case 'error':
        console.error('Event error:', msg.message);
        break;
    }
  };

  // Later: add more subscriptions
  ws.send(JSON.stringify({
    type: 'subscribe',
    event_types: ['process_start', 'process_stop']
  }));

  // Or unsubscribe from some
  ws.send(JSON.stringify({
    type: 'unsubscribe',
    event_types: ['idle', 'active']
  }));
  ```

  ```python Python theme={null}
  import asyncio
  import websockets
  import json
  import requests

  async def monitor_events(computer_id: str, api_key: str):
      # Step 1: Get the computer password
      res = requests.get(
          f"https://www.orgo.ai/api/computers/{computer_id}/vnc-password",
          headers={"Authorization": f"Bearer {api_key}"}
      )
      password = res.json()["password"]

      # Step 2: Connect to events stream
      url = f"wss://www.orgo.ai/desktops/{computer_id}/ws/events?token={password}"

      async with websockets.connect(url) as ws:
          # Subscribe to events
          await ws.send(json.dumps({
              "type": "subscribe",
              "event_types": ["window_focus", "clipboard", "file_change"]
          }))

          # Listen for events
          async for message in ws:
              data = json.loads(message)

              if data["type"] == "event":
                  event = data["event"]
                  print(f"[{event['timestamp']}] {event['type']}: {event['data']}")

                  if event["type"] == "file_change":
                      print(f"  File {event['data']['action']}: {event['data']['path']}")

              elif data["type"] == "subscribed":
                  print("Subscribed successfully")

              elif data["type"] == "error":
                  print(f"Error: {data['message']}")

  computer_id = "a3bb189e-8bf9-3888-9912-ace4e6543002"
  api_key = "sk_live_..."
  asyncio.run(monitor_events(computer_id, api_key))
  ```

  ```typescript TypeScript theme={null}
  interface DesktopEvent {
    type: string;
    timestamp: string;
    data: Record<string, unknown>;
  }

  interface EventMessage {
    type: 'event' | 'subscribed' | 'unsubscribed' | 'pong' | 'error';
    event?: DesktopEvent;
    message?: string;
  }

  class EventStream {
    private ws!: WebSocket;
    private handlers: Map<string, (event: DesktopEvent) => void> = new Map();

    static async connect(computerId: string, apiKey: string) {
      const stream = new EventStream();

      // Get the computer password
      const res = await fetch(
        `https://www.orgo.ai/api/computers/${computerId}/vnc-password`,
        { headers: { 'Authorization': `Bearer ${apiKey}` } }
      );
      const { password } = await res.json();

      // Connect
      stream.ws = new WebSocket(
        `wss://www.orgo.ai/desktops/${computerId}/ws/events?token=${password}`
      );

      stream.ws.onmessage = (event) => {
        const msg: EventMessage = JSON.parse(event.data);
        if (msg.type === 'event' && msg.event) {
          const handler = stream.handlers.get(msg.event.type);
          handler?.(msg.event);
        }
      };

      return stream;
    }

    on(eventType: string, handler: (event: DesktopEvent) => void) {
      this.handlers.set(eventType, handler);

      // Auto-subscribe
      this.ws.send(JSON.stringify({
        type: 'subscribe',
        event_types: [eventType]
      }));

      return this;
    }

    off(eventType: string) {
      this.handlers.delete(eventType);
      this.ws.send(JSON.stringify({
        type: 'unsubscribe',
        event_types: [eventType]
      }));
      return this;
    }

    close() {
      this.ws.close();
    }
  }

  // Usage
  const events = await EventStream.connect(
    'a3bb189e-8bf9-3888-9912-ace4e6543002',
    'sk_live_...'
  );

  events
    .on('window_focus', (e) => console.log('Window:', e.data.title))
    .on('clipboard', (e) => console.log('Clipboard:', e.data.content))
    .on('file_change', (e) => console.log('File:', e.data.action, e.data.path))
    .on('idle', (e) => console.log('Idle for', e.data.idle_seconds, 'seconds'));
  ```
</CodeGroup>

## Use Cases

<CardGroup cols={2}>
  <Card title="Agent Awareness" icon="robot">
    Subscribe to `window_focus` and `idle` to give your AI agent context about what the user is doing and when to act.
  </Card>

  <Card title="File Monitoring" icon="folder-open">
    Watch `file_change` events to detect when downloads complete, documents save, or files are created on the Desktop.
  </Card>

  <Card title="Clipboard Sync" icon="clipboard">
    Monitor `clipboard` events to sync clipboard content between the VM and your application in real time.
  </Card>

  <Card title="Process Tracking" icon="microchip">
    Use `process_start` and `process_stop` to track application lifecycle - know when Chrome launches, when builds finish, etc.
  </Card>
</CardGroup>

## Best Practices

<CardGroup cols={2}>
  <Card title="Subscribe Selectively" icon="filter">
    Only subscribe to event types you need. This reduces message volume and keeps your handler logic simple.
  </Card>

  <Card title="Heartbeat" icon="heart-pulse">
    Send periodic `ping` messages (every 30 seconds) to keep the connection alive and detect disconnections early.
  </Card>

  <Card title="Handle Backpressure" icon="gauge-high">
    Events are dropped for slow consumers (buffer size: 256). Process events quickly or offload to a queue.
  </Card>

  <Card title="Reconnection" icon="rotate">
    Implement automatic reconnection with exponential backoff. Re-send your `subscribe` message after reconnecting.
  </Card>
</CardGroup>

<Note>
  Events require the computer to be running. If the computer is stopped, the WebSocket connection will be rejected. After reconnecting, you must re-subscribe - subscriptions are not persisted across connections.
</Note>

## Close codes

| Code   | Meaning                                    |
| ------ | ------------------------------------------ |
| `1000` | Normal closure.                            |
| `1001` | Server shutting down or computer stopping. |
| `4401` | Missing or invalid `token`.                |
| `4404` | Computer not found or not running.         |
