Skip to main content

Gateway protocol (WebSocket)

The Gateway WS protocol is the single control plane + node transport for OpenClaw. All clients (CLI, web UI, macOS app, iOS/Android nodes, headless nodes) connect over WebSocket and declare their role + scope at handshake time.

Transport

  • WebSocket, text frames with JSON payloads.
  • First frame must be a connect request.

Handshake (connect)

Gateway → Client (pre-connect challenge):
{
  "type": "event",
  "event": "connect.challenge",
  "payload": { "nonce": "…", "ts": 1737264000000 }
}
Client → Gateway:
{
  "type": "req",
  "id": "…",
  "method": "connect",
  "params": {
    "minProtocol": 3,
    "maxProtocol": 3,
    "client": {
      "id": "cli",
      "version": "1.2.3",
      "platform": "macos",
      "mode": "operator"
    },
    "role": "operator",
    "scopes": ["operator.read", "operator.write"],
    "caps": [],
    "commands": [],
    "permissions": {},
    "auth": { "token": "…" },
    "locale": "en-US",
    "userAgent": "openclaw-cli/1.2.3",
    "device": {
      "id": "device_fingerprint",
      "publicKey": "…",
      "signature": "…",
      "signedAt": 1737264000000,
      "nonce": "…"
    }
  }
}
Gateway → Client:
{
  "type": "res",
  "id": "…",
  "ok": true,
  "payload": { "type": "hello-ok", "protocol": 3, "policy": { "tickIntervalMs": 15000 } }
}
When a device token is issued, hello-ok also includes:
{
  "auth": {
    "deviceToken": "…",
    "role": "operator",
    "scopes": ["operator.read", "operator.write"]
  }
}
During trusted bootstrap handoff, hello-ok.auth may also include additional bounded role entries in deviceTokens:
{
  "auth": {
    "deviceToken": "…",
    "role": "node",
    "scopes": [],
    "deviceTokens": [
      {
        "deviceToken": "…",
        "role": "operator",
        "scopes": ["operator.approvals", "operator.read", "operator.talk.secrets", "operator.write"]
      }
    ]
  }
}
For the built-in node/operator bootstrap flow, the primary node token stays scopes: [] and any handed-off operator token stays bounded to the bootstrap operator allowlist (operator.approvals, operator.read, operator.talk.secrets, operator.write). Bootstrap scope checks stay role-prefixed: operator entries only satisfy operator requests, and non-operator roles still need scopes under their own role prefix.

Node example

{
  "type": "req",
  "id": "…",
  "method": "connect",
  "params": {
    "minProtocol": 3,
    "maxProtocol": 3,
    "client": {
      "id": "ios-node",
      "version": "1.2.3",
      "platform": "ios",
      "mode": "node"
    },
    "role": "node",
    "scopes": [],
    "caps": ["camera", "canvas", "screen", "location", "voice"],
    "commands": ["camera.snap", "canvas.navigate", "screen.record", "location.get"],
    "permissions": { "camera.capture": true, "screen.record": false },
    "auth": { "token": "…" },
    "locale": "en-US",
    "userAgent": "openclaw-ios/1.2.3",
    "device": {
      "id": "device_fingerprint",
      "publicKey": "…",
      "signature": "…",
      "signedAt": 1737264000000,
      "nonce": "…"
    }
  }
}

Framing

  • Request: {type:"req", id, method, params}
  • Response: {type:"res", id, ok, payload|error}
  • Event: {type:"event", event, payload, seq?, stateVersion?}
Side-effecting methods require idempotency keys (see schema).

Roles + scopes

Roles

  • operator = control plane client (CLI/UI/automation).
  • node = capability host (camera/screen/canvas/system.run).

Scopes (operator)

Common scopes:
  • operator.read
  • operator.write
  • operator.admin
  • operator.approvals
  • operator.pairing
  • operator.talk.secrets
talk.config with includeSecrets: true requires operator.talk.secrets (or operator.admin). Plugin-registered gateway RPC methods may request their own operator scope, but reserved core admin prefixes (config.*, exec.approvals.*, wizard.*, update.*) always resolve to operator.admin. Method scope is only the first gate. Some slash commands reached through chat.send apply stricter command-level checks on top. For example, persistent /config set and /config unset writes require operator.admin. node.pair.approve also has an extra approval-time scope check on top of the base method scope:
  • commandless requests: operator.pairing
  • requests with non-exec node commands: operator.pairing + operator.write
  • requests that include system.run, system.run.prepare, or system.which: operator.pairing + operator.admin

Caps/commands/permissions (node)

Nodes declare capability claims at connect time:
  • caps: high-level capability categories.
  • commands: command allowlist for invoke.
  • permissions: granular toggles (e.g. screen.record, camera.capture).
The Gateway treats these as claims and enforces server-side allowlists.

Presence

  • system-presence returns entries keyed by device identity.
  • Presence entries include deviceId, roles, and scopes so UIs can show a single row per device even when it connects as both operator and node.

Common RPC method families

This page is not a generated full dump, but the public WS surface is broader than the handshake/auth examples above. These are the main method families the Gateway exposes today. hello-ok.features.methods is a conservative discovery list built from src/gateway/server-methods-list.ts plus loaded plugin/channel method exports. Treat it as feature discovery, not as a generated dump of every callable helper implemented in src/gateway/server-methods/*.ts.

System and identity

  • health returns the cached or freshly probed gateway health snapshot.
  • status returns the /status-style gateway summary; sensitive fields are included only for admin-scoped operator clients.
  • gateway.identity.get returns the gateway device identity used by relay and pairing flows.
  • system-presence returns the current presence snapshot for connected operator/node devices.
  • system-event appends a system event and can update/broadcast presence context.
  • last-heartbeat returns the latest persisted heartbeat event.
  • set-heartbeats toggles heartbeat processing on the gateway.

Models and usage

  • models.list returns the runtime-allowed model catalog.
  • usage.status returns provider usage windows/remaining quota summaries.
  • usage.cost returns aggregated cost usage summaries for a date range.
  • doctor.memory.status returns vector-memory / embedding readiness for the active default agent workspace.
  • sessions.usage returns per-session usage summaries.
  • sessions.usage.timeseries returns timeseries usage for one session.
  • sessions.usage.logs returns usage log entries for one session.

Channels and login helpers

  • channels.status returns built-in + bundled channel/plugin status summaries.
  • channels.logout logs out a specific channel/account where the channel supports logout.
  • web.login.start starts a QR/web login flow for the current QR-capable web channel provider.
  • web.login.wait waits for that QR/web login flow to complete and starts the channel on success.
  • push.test sends a test APNs push to a registered iOS node.
  • voicewake.get returns the stored wake-word triggers.
  • voicewake.set updates wake-word triggers and broadcasts the change.

Messaging and logs

  • send is the direct outbound-delivery RPC for channel/account/thread-targeted sends outside the chat runner.
  • logs.tail returns the configured gateway file-log tail with cursor/limit and max-byte controls.

Talk and TTS

  • talk.config returns the effective Talk config payload; includeSecrets requires operator.talk.secrets (or operator.admin).
  • talk.mode sets/broadcasts the current Talk mode state for WebChat/Control UI clients.
  • talk.speak synthesizes speech through the active Talk speech provider.
  • tts.status returns TTS enabled state, active provider, fallback providers, and provider config state.
  • tts.providers returns the visible TTS provider inventory.
  • tts.enable and tts.disable toggle TTS prefs state.
  • tts.setProvider updates the preferred TTS provider.
  • tts.convert runs one-shot text-to-speech conversion.

Secrets, config, update, and wizard

  • secrets.reload re-resolves active SecretRefs and swaps runtime secret state only on full success.
  • secrets.resolve resolves command-target secret assignments for a specific command/target set.
  • config.get returns the current config snapshot and hash.
  • config.set writes a validated config payload.
  • config.patch merges a partial config update.
  • config.apply validates + replaces the full config payload.
  • config.schema returns the live config schema payload used by Control UI and CLI tooling: schema, uiHints, version, and generation metadata, including plugin + channel schema metadata when the runtime can load it. The schema includes field title / description metadata derived from the same labels and help text used by the UI, including nested object, wildcard, array-item, and anyOf / oneOf / allOf composition branches when matching field documentation exists.
  • config.schema.lookup returns a path-scoped lookup payload for one config path: normalized path, a shallow schema node, matched hint + hintPath, and immediate child summaries for UI/CLI drill-down.
    • Lookup schema nodes keep the user-facing docs and common validation fields: title, description, type, enum, const, format, pattern, numeric/string/array/object bounds, and boolean flags like additionalProperties, deprecated, readOnly, writeOnly.
    • Child summaries expose key, normalized path, type, required, hasChildren, plus the matched hint / hintPath.
  • update.run runs the gateway update flow and schedules a restart only when the update itself succeeded.
  • wizard.start, wizard.next, wizard.status, and wizard.cancel expose the onboarding wizard over WS RPC.

Existing major families

Agent and workspace helpers

  • agents.list returns configured agent entries.
  • agents.create, agents.update, and agents.delete manage agent records and workspace wiring.
  • agents.files.list, agents.files.get, and agents.files.set manage the bootstrap workspace files exposed for an agent.
  • agent.identity.get returns the effective assistant identity for an agent or session.
  • agent.wait waits for a run to finish and returns the terminal snapshot when available.

Session control

  • sessions.list returns the current session index.
  • sessions.subscribe and sessions.unsubscribe toggle session change event subscriptions for the current WS client.
  • sessions.messages.subscribe and sessions.messages.unsubscribe toggle transcript/message event subscriptions for one session.
  • sessions.preview returns bounded transcript previews for specific session keys.
  • sessions.resolve resolves or canonicalizes a session target.
  • sessions.create creates a new session entry.
  • sessions.send sends a message into an existing session.
  • sessions.steer is the interrupt-and-steer variant for an active session.
  • sessions.abort aborts active work for a session.
  • sessions.patch updates session metadata/overrides.
  • sessions.reset, sessions.delete, and sessions.compact perform session maintenance.
  • sessions.get returns the full stored session row.
  • chat execution still uses chat.history, chat.send, chat.abort, and chat.inject.
  • chat.history is display-normalized for UI clients: inline directive tags are stripped from visible text, plain-text tool-call XML payloads (including <tool_call>...</tool_call>, <function_call>...</function_call>, <tool_calls>...</tool_calls>, <function_calls>...</function_calls>, and truncated tool-call blocks) and leaked ASCII/full-width model control tokens are stripped, pure silent-token assistant rows such as exact NO_REPLY / no_reply are omitted, and oversized rows can be replaced with placeholders.

Device pairing and device tokens

  • device.pair.list returns pending and approved paired devices.
  • device.pair.approve, device.pair.reject, and device.pair.remove manage device-pairing records.
  • device.token.rotate rotates a paired device token within its approved role and scope bounds.
  • device.token.revoke revokes a paired device token.

Node pairing, invoke, and pending work

  • node.pair.request, node.pair.list, node.pair.approve, node.pair.reject, and node.pair.verify cover node pairing and bootstrap verification.
  • node.list and node.describe return known/connected node state.
  • node.rename updates a paired node label.
  • node.invoke forwards a command to a connected node.
  • node.invoke.result returns the result for an invoke request.
  • node.event carries node-originated events back into the gateway.
  • node.canvas.capability.refresh refreshes scoped canvas-capability tokens.
  • node.pending.pull and node.pending.ack are the connected-node queue APIs.
  • node.pending.enqueue and node.pending.drain manage durable pending work for offline/disconnected nodes.

Approval families

  • exec.approval.request and exec.approval.resolve cover one-shot exec approval requests.
  • exec.approval.waitDecision waits on one pending exec approval and returns the final decision (or null on timeout).
  • exec.approvals.get and exec.approvals.set manage gateway exec approval policy snapshots.
  • exec.approvals.node.get and exec.approvals.node.set manage node-local exec approval policy via node relay commands.
  • plugin.approval.request, plugin.approval.waitDecision, and plugin.approval.resolve cover plugin-defined approval flows.

Other major families

  • automation:
    • wake schedules an immediate or next-heartbeat wake text injection
    • cron.list, cron.status, cron.add, cron.update, cron.remove, cron.run, cron.runs
  • skills/tools: skills.*, tools.catalog, tools.effective

Common event families

  • chat: UI chat updates such as chat.inject and other transcript-only chat events.
  • session.message and session.tool: transcript/event-stream updates for a subscribed session.
  • sessions.changed: session index or metadata changed.
  • presence: system presence snapshot updates.
  • tick: periodic keepalive / liveness event.
  • health: gateway health snapshot update.
  • heartbeat: heartbeat event stream update.
  • cron: cron run/job change event.
  • shutdown: gateway shutdown notification.
  • node.pair.requested / node.pair.resolved: node pairing lifecycle.
  • node.invoke.request: node invoke request broadcast.
  • device.pair.requested / device.pair.resolved: paired-device lifecycle.
  • voicewake.changed: wake-word trigger config changed.
  • exec.approval.requested / exec.approval.resolved: exec approval lifecycle.
  • plugin.approval.requested / plugin.approval.resolved: plugin approval lifecycle.

Node helper methods

  • Nodes may call skills.bins to fetch the current list of skill executables for auto-allow checks.

Operator helper methods

  • Operators may call tools.catalog (operator.read) to fetch the runtime tool catalog for an agent. The response includes grouped tools and provenance metadata:
    • source: core or plugin
    • pluginId: plugin owner when source="plugin"
    • optional: whether a plugin tool is optional
  • Operators may call tools.effective (operator.read) to fetch the runtime-effective tool inventory for a session.
    • sessionKey is required.
    • The gateway derives trusted runtime context from the session server-side instead of accepting caller-supplied auth or delivery context.
    • The response is session-scoped and reflects what the active conversation can use right now, including core, plugin, and channel tools.
  • Operators may call skills.status (operator.read) to fetch the visible skill inventory for an agent.
    • agentId is optional; omit it to read the default agent workspace.
    • The response includes eligibility, missing requirements, config checks, and sanitized install options without exposing raw secret values.
  • Operators may call skills.search and skills.detail (operator.read) for ClawHub discovery metadata.
  • Operators may call skills.install (operator.admin) in two modes:
    • ClawHub mode: { source: "clawhub", slug, version?, force? } installs a skill folder into the default agent workspace skills/ directory.
    • Gateway installer mode: { name, installId, dangerouslyForceUnsafeInstall?, timeoutMs? } runs a declared metadata.openclaw.install action on the gateway host.
  • Operators may call skills.update (operator.admin) in two modes:
    • ClawHub mode updates one tracked slug or all tracked ClawHub installs in the default agent workspace.
    • Config mode patches skills.entries.<skillKey> values such as enabled, apiKey, and env.

Exec approvals

  • When an exec request needs approval, the gateway broadcasts exec.approval.requested.
  • Operator clients resolve by calling exec.approval.resolve (requires operator.approvals scope).
  • For host=node, exec.approval.request must include systemRunPlan (canonical argv/cwd/rawCommand/session metadata). Requests missing systemRunPlan are rejected.
  • After approval, forwarded node.invoke system.run calls reuse that canonical systemRunPlan as the authoritative command/cwd/session context.
  • If a caller mutates command, rawCommand, cwd, agentId, or sessionKey between prepare and the final approved system.run forward, the gateway rejects the run instead of trusting the mutated payload.

Agent delivery fallback

  • agent requests can include deliver=true to request outbound delivery.
  • bestEffortDeliver=false keeps strict behavior: unresolved or internal-only delivery targets return INVALID_REQUEST.
  • bestEffortDeliver=true allows fallback to session-only execution when no external deliverable route can be resolved (for example internal/webchat sessions or ambiguous multi-channel configs).

Versioning

  • PROTOCOL_VERSION lives in src/gateway/protocol/schema.ts.
  • Clients send minProtocol + maxProtocol; the server rejects mismatches.
  • Schemas + models are generated from TypeBox definitions:
    • pnpm protocol:gen
    • pnpm protocol:gen:swift
    • pnpm protocol:check

Auth

  • Shared-secret gateway auth uses connect.params.auth.token or connect.params.auth.password, depending on the configured auth mode.
  • Identity-bearing modes such as Tailscale Serve (gateway.auth.allowTailscale: true) or non-loopback gateway.auth.mode: "trusted-proxy" satisfy the connect auth check from request headers instead of connect.params.auth.*.
  • Private-ingress gateway.auth.mode: "none" skips shared-secret connect auth entirely; do not expose that mode on public/untrusted ingress.
  • After pairing, the Gateway issues a device token scoped to the connection role + scopes. It is returned in hello-ok.auth.deviceToken and should be persisted by the client for future connects.
  • Clients should persist the primary hello-ok.auth.deviceToken after any successful connect.
  • Reconnecting with that stored device token should also reuse the stored approved scope set for that token. This preserves read/probe/status access that was already granted and avoids silently collapsing reconnects to a narrower implicit admin-only scope.
  • Normal connect auth precedence is explicit shared token/password first, then explicit deviceToken, then stored per-device token, then bootstrap token.
  • Additional hello-ok.auth.deviceTokens entries are bootstrap handoff tokens. Persist them only when the connect used bootstrap auth on a trusted transport such as wss:// or loopback/local pairing.
  • If a client supplies an explicit deviceToken or explicit scopes, that caller-requested scope set remains authoritative; cached scopes are only reused when the client is reusing the stored per-device token.
  • Device tokens can be rotated/revoked via device.token.rotate and device.token.revoke (requires operator.pairing scope).
  • Token issuance/rotation stays bounded to the approved role set recorded in that device’s pairing entry; rotating a token cannot expand the device into a role that pairing approval never granted.
  • For paired-device token sessions, device management is self-scoped unless the caller also has operator.admin: non-admin callers can remove/revoke/rotate only their own device entry.
  • device.token.rotate also checks the requested operator scope set against the caller’s current session scopes. Non-admin callers cannot rotate a token into a broader operator scope set than they already hold.
  • Auth failures include error.details.code plus recovery hints:
    • error.details.canRetryWithDeviceToken (boolean)
    • error.details.recommendedNextStep (retry_with_device_token, update_auth_configuration, update_auth_credentials, wait_then_retry, review_auth_configuration)
  • Client behavior for AUTH_TOKEN_MISMATCH:
    • Trusted clients may attempt one bounded retry with a cached per-device token.
    • If that retry fails, clients should stop automatic reconnect loops and surface operator action guidance.

Device identity + pairing

  • Nodes should include a stable device identity (device.id) derived from a keypair fingerprint.
  • Gateways issue tokens per device + role.
  • Pairing approvals are required for new device IDs unless local auto-approval is enabled.
  • Pairing auto-approval is centered on direct local loopback connects.
  • OpenClaw also has a narrow backend/container-local self-connect path for trusted shared-secret helper flows.
  • Same-host tailnet or LAN connects are still treated as remote for pairing and require approval.
  • All WS clients must include device identity during connect (operator + node). Control UI can omit it only in these modes:
    • gateway.controlUi.allowInsecureAuth=true for localhost-only insecure HTTP compatibility.
    • successful gateway.auth.mode: "trusted-proxy" operator Control UI auth.
    • gateway.controlUi.dangerouslyDisableDeviceAuth=true (break-glass, severe security downgrade).
  • All connections must sign the server-provided connect.challenge nonce.

Device auth migration diagnostics

For legacy clients that still use pre-challenge signing behavior, connect now returns DEVICE_AUTH_* detail codes under error.details.code with a stable error.details.reason. Common migration failures:
Messagedetails.codedetails.reasonMeaning
device nonce requiredDEVICE_AUTH_NONCE_REQUIREDdevice-nonce-missingClient omitted device.nonce (or sent blank).
device nonce mismatchDEVICE_AUTH_NONCE_MISMATCHdevice-nonce-mismatchClient signed with a stale/wrong nonce.
device signature invalidDEVICE_AUTH_SIGNATURE_INVALIDdevice-signatureSignature payload does not match v2 payload.
device signature expiredDEVICE_AUTH_SIGNATURE_EXPIREDdevice-signature-staleSigned timestamp is outside allowed skew.
device identity mismatchDEVICE_AUTH_DEVICE_ID_MISMATCHdevice-id-mismatchdevice.id does not match public key fingerprint.
device public key invalidDEVICE_AUTH_PUBLIC_KEY_INVALIDdevice-public-keyPublic key format/canonicalization failed.
Migration target:
  • Always wait for connect.challenge.
  • Sign the v2 payload that includes the server nonce.
  • Send the same nonce in connect.params.device.nonce.
  • Preferred signature payload is v3, which binds platform and deviceFamily in addition to device/client/role/scopes/token/nonce fields.
  • Legacy v2 signatures remain accepted for compatibility, but paired-device metadata pinning still controls command policy on reconnect.

TLS + pinning

  • TLS is supported for WS connections.
  • Clients may optionally pin the gateway cert fingerprint (see gateway.tls config plus gateway.remote.tlsFingerprint or CLI --tls-fingerprint).

Scope

This protocol exposes the full gateway API (status, channels, models, chat, agent, sessions, nodes, approvals, etc.). The exact surface is defined by the TypeBox schemas in src/gateway/protocol/schema.ts.