Gateway Architecture: Components and Client Flows

Learn how the OpenClaw Gateway manages all messaging surfaces via WebSocket, and how control-plane clients and nodes connect. Essential for developers deploying or integrating with the Gateway.

Read this when

  • Working on gateway protocol, clients, or transports

Overview

  • Every messaging surface (WhatsApp through Baileys, Telegram via grammY, Slack, Discord, Signal, iMessage, WebChat) is managed by a single long-lived Gateway.

  • Control-plane clients, which include the macOS app, CLI, web UI, and automations, reach the Gateway via WebSocket at the configured bind host, defaulting to 127.0.0.1:18789.

  • Nodes (macOS/iOS/Android/headless) also use WebSocket, but they advertise role: node along with explicit caps and commands.

  • Only one Gateway runs per host, and it is the sole process that opens a WhatsApp session.

  • The hosted widget surface is delivered by the Gateway HTTP server at:

    • /__openclaw__/canvas/ (hosted widget documents)
    • /__openclaw__/a2ui/ (A2UI renderer assets)

    This surface shares the Gateway's port, which defaults to 18789.

Components and flows

Gateway (daemon)

  • Keeps provider connections alive.
  • Offers a typed WS API covering requests, responses, and server-push events.
  • Checks inbound frames against JSON Schema.
  • Fires events such as agent, chat, presence, health, heartbeat, and cron.

Clients (mac app / CLI / web admin)

  • Each client holds one WS connection.
  • Clients send requests (health, status, send, agent, system-presence).
  • Clients subscribe to events (tick, agent, presence, shutdown).

Nodes (macOS / iOS / Android / headless)

  • These connect to the same WS server using role: node.
  • A device identity is supplied in connect; pairing works on a device-based model (role node), with approval stored in the device pairing store.
  • Commands such as camera.*, screen.record, and location.get are exposed; the macOS app additionally provides widget-panel commands under canvas.*.

Protocol details: Gateway protocol

WebChat

  • A static UI that relies on the Gateway WS API for chat history and message sending.
  • In remote deployments, it connects via the same SSH/Tailscale tunnel used by other clients.

Connection lifecycle (single client)

sequenceDiagram
    participant Client
    participant Gateway

    Client->>Gateway: req:connect
    Gateway-->>Client: res (ok)
    Note right of Gateway: or res error + close
    Note left of Client: payload=hello-ok<br>snapshot: presence + health

    Gateway-->>Client: event:presence
    Gateway-->>Client: event:tick

    Client->>Gateway: req:agent
    Gateway-->>Client: res:agent<br>ack {runId, status:"accepted"}
    Gateway-->>Client: event:agent<br>(streaming)
    Gateway-->>Client: res:agent<br>final {runId, status, summary}

Wire protocol (summary)

  • Transport: WebSocket, text frames carrying JSON payloads.
  • The first frame must be connect.
  • After the handshake:
    • Requests: {type:"req", id, method, params}{type:"res", id, ok, payload|error}
    • Events: {type:"event", event, payload, seq?, stateVersion?}
  • hello-ok.features.methods and events serve as discovery metadata, not as a generated listing of every callable helper route.
  • Shared-secret auth relies on connect.params.auth.token or connect.params.auth.password, chosen by the configured gateway auth mode.
  • Identity-bearing modes, including Tailscale Serve (gateway.auth.allowTailscale: true) or non-loopback gateway.auth.mode: "trusted-proxy", derive auth from request headers rather than connect.params.auth.*.
  • Private-ingress gateway.auth.mode: "none" turns off shared-secret auth completely; avoid using this mode on public or untrusted ingress.
  • Side-effecting methods (send, agent) demand idempotency keys for safe retries; the server maintains a short-lived dedupe cache.
  • Nodes must supply role: "node" plus caps, commands, and permissions in connect.

Pairing and local trust

  • Every WS client, whether operator or node, carries a device identity on connect.
  • Unseen device IDs trigger pairing approval; the Gateway then issues a device token for future connects.
  • Direct local loopback connects may be auto-approved to keep same-host UX smooth.
  • OpenClaw also offers a narrow backend/container-local self-connect path for trusted shared-secret helper flows.
  • Tailnet and LAN connects, including same-host tailnet binds, still demand explicit pairing approval.
  • All connects must sign the connect.challenge nonce. The signature payload v3 also binds platform and deviceFamily; the gateway pins paired metadata on reconnect and requires repair pairing for metadata changes.
  • Non-local connects still demand explicit approval.
  • Gateway auth (gateway.auth.*) applies to all connections, local or remote.

Details: Gateway protocol, Pairing, Security.

Protocol typing and codegen

  • The protocol is defined by TypeBox schemas.
  • Those schemas are used to generate JSON Schema.
  • From that JSON Schema, Swift models are produced.

Remote access

  • Tailscale or a VPN is the preferred option.

  • An SSH tunnel serves as an alternative.

    ssh -N -L 18789:127.0.0.1:18789 user@gateway-host
    
  • The same auth token and handshake apply when using the tunnel.

  • For remote setups, TLS with optional pinning can be enabled for WS.

Operations snapshot

  • Launch with openclaw gateway (in the foreground, logging to stdout).
  • Check health via health over WS, which is also part of hello-ok.
  • Use launchd or systemd for supervision and automatic restarts.

Invariants

  • Per host, a single Baileys session is controlled by exactly one Gateway.
  • The handshake is required; a hard close occurs if the first frame is non-JSON or not a connect.
  • Events are not replayed; clients must refresh when gaps appear.
  • Agent Loop, details the agent execution cycle
  • Gateway Protocol, specifies the WebSocket contract
  • Queue, covers the command queue and concurrency
  • Security, explains the trust model and hardening
819 words · updated Aug 22, 2026