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 endpoint before connecting.
Connections without a valid token are rejected with close code 4401.
Query Parameters
Event Catalog
You can also fetch the event catalog as JSON without a WebSocket connection:
curl https://www.orgo.ai/api/computers/{id}/events
Available Event Types
Type Description Detection window_focusActive window changed Polled every 100 ms window_openNew window opened Polled every 100 ms window_closeWindow closed Polled every 100 ms clipboardClipboard content changed Polled every 250 ms file_changeFile created, modified, or deleted on Desktop or Downloads inotify (instant) screen_changeDisplay resolution changed Polled every 1 s audio_stream_startAudio playback started Polled every 500 ms audio_stream_stopAudio playback stopped Polled every 500 ms process_startNew process started Polled every 2 s process_stopProcess exited Polled every 2 s idleNo user input for 30 seconds Polled every 1 s activeUser 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
Start receiving specific event types. You can call this multiple times to add more types. {
"type" : "subscribe" ,
"event_types" : [ "window_focus" , "clipboard" , "file_change" ]
}
Array of event type names to subscribe to. See the event catalog for valid types.
Stop receiving specific event types. {
"type" : "unsubscribe" ,
"event_types" : [ "clipboard" ]
}
Array of event type names to unsubscribe from.
Send a heartbeat ping to keep the connection alive.
Server → Client Messages
A desktop event matching one of your subscribed types. {
"type" : "event" ,
"event" : {
"type" : "window_focus" ,
"timestamp" : "2026-03-03T12:00:00.123Z" ,
"data" : {
"window_id" : "0x3200004" ,
"title" : "Google Chrome"
}
}
}
Event type name (e.g., "window_focus", "clipboard").
ISO 8601 timestamp of when the event occurred.
Confirmation that your subscription was updated. {
"type" : "subscribed" ,
"message" : "ok"
}
Confirmation that event types were removed from your subscription. {
"type" : "unsubscribed" ,
"message" : "ok"
}
Response to a ping message.
Error message from the server. {
"type" : "error" ,
"message" : "Unknown event type: invalid_type"
}
Human-readable error description.
Event Data Schemas
Each event type includes a data object with type-specific fields:
Window events
// window_focus
{ "window_id" : "0x3200004" , "title" : "Google Chrome" }
// window_open
{ "window_id" : "0x3200008" , "title" : "Terminal" }
// window_close
{ "window_id" : "0x3200008" }
Clipboard
// clipboard
{ "content" : "copied text from the clipboard" }
File changes
// 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
// screen_change
{ "width" : 1920 , "height" : 1080 }
Audio
// audio_stream_start
{}
// audio_stream_stop
{}
Process lifecycle
// process_start
{ "pid" : 1234 , "name" : "chrome" }
// process_stop
{ "pid" : 1234 , "name" : "chrome" }
Idle / Active
// idle
{ "idle_seconds" : 30 }
// active
{}
Examples
JavaScript
Python
TypeScript
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' ]
}));
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))
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' ));
Use Cases
Agent Awareness Subscribe to window_focus and idle to give your AI agent context about what the user is doing and when to act.
File Monitoring Watch file_change events to detect when downloads complete, documents save, or files are created on the Desktop.
Clipboard Sync Monitor clipboard events to sync clipboard content between the VM and your application in real time.
Process Tracking Use process_start and process_stop to track application lifecycle - know when Chrome launches, when builds finish, etc.
Best Practices
Subscribe Selectively Only subscribe to event types you need. This reduces message volume and keeps your handler logic simple.
Heartbeat Send periodic ping messages (every 30 seconds) to keep the connection alive and detect disconnections early.
Handle Backpressure Events are dropped for slow consumers (buffer size: 256). Process events quickly or offload to a queue.
Reconnection Implement automatic reconnection with exponential backoff. Re-send your subscribe message after reconnecting.
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.
Close codes
Code Meaning 1000Normal closure. 1001Server shutting down or computer stopping. 4401Missing or invalid token. 4404Computer not found or not running.