Skip to main content

Control WebSocket

Greyproxy exposes a WebSocket endpoint at GET /ws that companion apps and external tools can use to receive real-time events and drive the proxy programmatically. The dashboard itself is a client of this same endpoint.

Connection

Connect to ws://<host>:<port>/ws. On success, greyproxy sends:

{
"type": "connected",
"message": "Connected to proxy event stream",
"timestamp": "2026-04-10T10:30:00Z"
}

Each connection is assigned a random client ID for internal tracking. By default, a new connection receives every event type; sending a subscribe command switches it into filtered mode.

Commands

Commands are JSON objects with a command field. Unknown commands return an error of the form {"type": "error", "error": "...", "timestamp": "..."}.

ping

Keep-alive heartbeat.

{"command": "ping"}

Response: {"type": "pong", "timestamp": "..."}.

allow

Approve a pending request and create an allow rule for it.

{
"command": "allow",
"pending_id": 123,
"scope": "exact",
"duration": "permanent",
"notes": "optional note"
}
FieldDefaultValues
scopeexactRule scope for the generated allow rule
durationpermanentpermanent or a Go duration string (for example 1h, 30m)
notesnoneFree-text annotation

On success:

{"type": "command_success", "command": "allow", "pending_id": 123, "rule_id": 456, "timestamp": "..."}

dismiss

Dismiss a pending request without creating a rule.

{"command": "dismiss", "pending_id": 123}

subscribe and unsubscribe

Filter which events this connection receives. After subscribe, only subscribed event types are forwarded. unsubscribe removes a type from the filter.

{"command": "subscribe", "event_type": "pending_request.created"}
{"command": "unsubscribe", "event_type": "pending_request.created"}

claim_notifications

Tell greyproxy that this connection is handling user notifications itself (for example, a native companion app that shows its own UI). While at least one connection holds a claim, greyproxy suppresses OS desktop notifications (notify-send, terminal-notifier).

{"command": "claim_notifications"}

Response:

{"type": "notification_claimed", "active_claims": 1, "timestamp": "..."}

The claim is released automatically when the WebSocket closes for any reason (graceful disconnect, crash, network failure). If several connections hold claims at once, all of them must disconnect before system notifications resume. Claiming twice on the same connection returns an error.

Every change to the claim count is broadcast to all connected clients:

{"type": "notification.claims_changed", "data": {"active_claims": 0}}

The current claim count is also visible via REST at GET /api/notifications.

Events

Events flow from server to client and carry a type plus a data field. The table below lists the events a companion app is likely to care about.

EventTriggerData
pending_request.createdNew connection awaiting a decisionPendingRequest
pending_request.updatedPending metadata changedPendingRequest
pending_request.allowedPending request approved{pending_id, rule}
pending_request.dismissedPending request dismissed{pending_id}
waiters.changedActive connection count changed for a pending{container_name, host, port, previous_count, current_count}
transaction.newNew HTTP or WebSocket transaction recordedTransaction object
conversation.updatedDissector produced new conversation dataConversation object
maintenance.progressBackground maintenance task progressProgress object
allowall.changedAllow-all (silent) mode toggledAllowAll status
notification.claims_changedNotification claim count changed{active_claims}

Example: companion app flow

1. Connect to ws://<proxy>/ws
2. Send {"command": "claim_notifications"}
3. Receive {"type": "notification_claimed", "active_claims": 1}
4. Listen for pending_request.created events
5. Show native UI for each pending request
6. Send allow or dismiss commands as the user decides
7. On app exit, the connection closes and the claim is auto-released