Skip to content

Master OpenClaw Gateway Architecture: WebSocket API Guide

Ever tried to build a bot or a custom UI that needs to talk to several different messaging apps at once? It usually turns into a mess of conflicting libraries and connection drops. You need a way to centralize that logic so your apps stay light and your connections stay stable.

The Gateway architecture is designed to be that central brain for your messaging setup. It handles the heavy lifting of protocol management while providing a clean, unified interface for your clients and nodes.

A single long‑lived Gateway owns all your messaging surfaces, including WhatsApp via Baileys, Telegram via grammY, Slack, Discord, Signal, iMessage, and WebChat.

Your control-plane clients, such as the macOS app, CLI, web UI, or automations, connect to this Gateway over WebSocket. By default, it binds to 127.0.0.1:18789.

Nodes on macOS, iOS, Android, or headless setups also connect over WebSocket, but they declare a role: node with specific capabilities and commands.

You only run one Gateway per host. This is the specific place where your WhatsApp session stays open. The Gateway also runs an HTTP server to serve the canvas host at these locations:

  • /__openclaw__/canvas/ (agent-editable HTML/CSS/JS)
  • /__openclaw__/a2ui/ (A2UI host)

This server uses the same port as the Gateway (default 18789).

The Gateway maintains your provider connections and exposes a typed WS API for requests, responses, and server‑push events. It validates every inbound frame against a JSON Schema. You can expect it to emit events like agent, chat, presence, health, heartbeat, and cron.

Each client uses one WS connection. You can use these to send requests like health, status, send, agent, or system-presence. Clients also subscribe to events such as tick, agent, presence, and shutdown.

Nodes connect to the same WS server but use role: node. When you connect a node, you provide a device identity. Pairing is device‑based, and the approval is stored in the device pairing store. Nodes allow you to use commands like canvas.*, camera.*, screen.record, and location.get.

Protocol details:

This is a static UI that uses the Gateway WS API to handle chat history and sending messages. If you are running a remote setup, it connects through the same SSH or Tailscale tunnel as your other clients.

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}

The transport uses WebSocket text frames with JSON payloads. Your first frame must be a connect frame.

After the handshake, the communication follows these patterns:

  • Requests: {type:"req", id, method, params} → {type:"res", id, ok, payload|error}
  • Events: {type:"event", event, payload, seq?, stateVersion?}

If you set OPENCLAW_GATEWAY_TOKEN or use the --token flag, you must provide a matching token in connect.params.auth.token or the socket will close.

You need to use idempotency keys for methods that cause side effects, like send or agent. This allows you to retry safely because the server maintains a short-lived cache to deduplicate requests. Nodes are required to include role: "node" along with their specific caps, commands, and permissions during the connect phase.

Every WS client, whether it is an operator or a node, must include a device identity when calling connect. New device IDs need pairing approval. Once approved, the Gateway gives you a device token for your next connections.

To make things easier, local connects (using loopback or the host’s own tailnet address) can be auto‑approved. This keeps the experience smooth when you are working on the same host.

All connections must sign the connect.challenge nonce. The v3 signature payload also binds the platform and deviceFamily. The gateway pins this metadata when you reconnect and will require a repair pairing if your metadata changes. Non‑local connects always require explicit approval, and gateway authentication (gateway.auth.*) applies to every connection regardless of whether it is local or remote.

Details: Gateway protocol, Pairing, Security.

The protocol is defined using TypeBox schemas. From these, a JSON Schema is generated, which is then used to generate Swift models. This ensures the types stay consistent across different parts of the system.

If you need to access the Gateway remotely, using Tailscale or a VPN is the best way to go. If those aren’t options, you can use an SSH tunnel:

Terminal window
ssh -N -L 18789:127.0.0.1:18789 user@host

The same handshake and auth token rules apply when you are using a tunnel. You can also enable TLS and optional pinning for WebSockets in remote environments.

You can start the Gateway in the foreground by running openclaw gateway, which will send logs to stdout. You can check the system status by sending a health request over WS, which is also included in the hello-ok message. For production setups, you should use launchd or systemd to handle auto‑restarts and supervision.

The architecture relies on a few strict rules:

  • Exactly one Gateway controls a single Baileys session per host.
  • The handshake is mandatory; the connection closes if the first frame isn’t a valid JSON connect frame.
  • The Gateway only accepts JSON frames.
  • Events are not replayed, so you must refresh your state if you detect a gap.

Need help getting started? Try the AI Setup Assistant.

OpenClaw

OpenClaw Expert

Still stuck?

If this page didn't answer your case, ask OpenClaw Expert for step-by-step guidance.