Architecture

WebSocket API

Complete reference for the WebSocket API between Constclaw and the OpenClaw gateway, including all frame types, events, and error codes.

Endpoint

ws://127.0.0.1:18789 Default — configurable in ~/.openclaw/openclaw.json

Connection lifecycle

01
Open
Client opens a WebSocket connection to the gateway endpoint.
02
Challenge
Gateway sends a connect.challenge event with a nonce.
03
Connect
Client replies with a connect request including auth, scopes, and nonce.
04
Hello
Gateway responds with hello-ok (res with ok: true). Handshake complete.
05
Ready
Client can now send "send" requests to the agent.
06
Close
Either side may close the connection at any time.

Frame types

All frames are JSON objects with a type field:

Type
Direction
Fields
Description
req
Client → Server
id, method, params
Request frame
res
Server → Client
id, ok, payload
Response to a request
event
Server → Client
event, payload, seq
Server-pushed event

Request: connect

Sent after receiving connect.challenge. Authenticates the client.

connect requestjson
{
  "type": "req",
  "id": "constclaw-001",
  "method": "connect",
  "params": {
    "minProtocol": 3,
    "maxProtocol": 3,
    "client": {
      "id": "constclaw-extension",
      "version": "1.0.0",
      "platform": "browser-extension",
      "mode": "operator"
    },
    "role": "operator",
    "scopes": ["operator.read", "operator.write"],
    "auth": { "token": "" },
    "device": {
      "id": "constclaw-extension",
      "nonce": ""
    },
    "locale": "en-US"
  }
}

Request: send

Sends a message (prompt) to the AI agent. Requires a completed handshake.

send requestjson
{
  "type": "req",
  "id": "constclaw-002",
  "method": "send",
  "params": {
    "message": "Explain: quantum entanglement",
    "idempotencyKey": "constclaw-002"
  }
}

Events

Event
Payload
Description
connect.challenge
{ nonce }
Sent immediately on connection. Contains the nonce for auth.
agent
{ text, … }
AI response payload. May arrive in multiple chunks for streaming.
agent.thinking
{ status }
Indicates the model is processing. Use for loading indicators.
agent.done
{ }
Signals that the agent has finished its response.
error
{ code, message }
Gateway or provider error.

Error codes

Code
Name
Description
1001
AUTH_FAILED
Invalid or missing gateway token.
1002
PROTOCOL_MISMATCH
Client/server protocol version incompatible.
1003
RATE_LIMITED
Too many requests in a short period.
2001
PROVIDER_ERROR
AI provider returned an error (quota, model unavailable).
2002
PROVIDER_TIMEOUT
AI provider did not respond within the timeout.
3001
INVALID_REQUEST
Malformed request frame (missing fields, bad JSON).

Full example

Complete request/response flow from connection to response:

Full WebSocket flowjs
"kw">const ws = "kw">new WebSocket("ws:">class="c">//127.0.0.1:18789");

ws.onopen = () => console.log("Connected");

ws.onmessage = (event) => {
  "kw">const frame = JSON.parse(event.data);

  switch (frame.event || frame.type) {
    case "connect.challenge":
      "kw">class="c">// Step 1: Reply with connect request
      ws.send(JSON.stringify({
        type: "req",
        id: "cc-001",
        method: "connect",
        params: {
          minProtocol: 3, maxProtocol: 3,
          client: { id: "constclaw", version: "1.0.0",
                    platform: "browser-extension", mode: "operator" },
          role: "operator",
          scopes: ["operator.read", "operator.write"],
          auth: { token: "" },
          device: { id: "constclaw", nonce: frame.payload.nonce },
          locale: "en-US",
        }
      }));
      break;

    case "res":
      "kw">if (frame.ok) {
        "kw">class="c">// Step 2: Handshake done — send query
        ws.send(JSON.stringify({
          type: "req",
          id: "cc-002",
          method: "send",
          params: {
            message: "Explain: quantum entanglement",
            idempotencyKey: "cc-002"
          }
        }));
      }
      break;

    case "agent":
      "kw">class="c">// Step 3: Receive AI response
      console.log(frame.payload.text);
      break;

    case "agent.done":
      console.log("Response complete");
      ws.close();
      break;

    case "error":
      console.error(frame.payload.code, frame.payload.message);
      break;
  }
};