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: nodealong 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, andcron.
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 (rolenode), with approval stored in the device pairing store. - Commands such as
camera.*,screen.record, andlocation.getare exposed; the macOS app additionally provides widget-panel commands undercanvas.*.
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?}
- Requests:
hello-ok.features.methodsandeventsserve as discovery metadata, not as a generated listing of every callable helper route.- Shared-secret auth relies on
connect.params.auth.tokenorconnect.params.auth.password, chosen by the configured gateway auth mode. - Identity-bearing modes, including Tailscale Serve (
gateway.auth.allowTailscale: true) or non-loopbackgateway.auth.mode: "trusted-proxy", derive auth from request headers rather thanconnect.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 inconnect.
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.challengenonce. The signature payloadv3also bindsplatformanddeviceFamily; 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
healthover WS, which is also part ofhello-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.
Related
- 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