OpenClawSkills
GitHub
Core Concepts β€’ TutorialHeader.readTime

Gateway Architecture

WebSocket gateway architecture, components, and client flows.

Last updated: 2026-01-22

Tutorial.step

Overview

- A long-lived gateway owns all messaging interfaces (via WhatsApp

Baileys, GrammY Telegram, Slack, Discord, Signal, iMessage, WebChat).

- Control plane clients (macOS app, CLI, Web UI, automation) connect to the

gateway via WebSocket on the config-bound host (default

''127.0.0.1:18789'').

- Nodes (macOS/iOS/Android/headless) also connect via WebSocket, but

use an explicit uppercase/command declaration ''role: node''.

- One gateway per host; this is the only place WhatsApp sessions are opened.

Tutorial.step

Components and Flow

#

Tutorial.step

Gateway (Daemon)

- Maintains provider connections.

- Exposes typed WS API (requests, responses, server-sent events).

- Validates inbound frames against JSON schemas.

- Emits ''agent'', ''chat'', ''presence'', ''health'', ''heartbeat'', ''cron'' events, etc.

#

Tutorial.step

Clients (mac app / CLI / Web admin)

- One WS connection per client.

- Sends requests (''health'', ''status'', ''send'', ''agent'', ''system-presence'').

- Subscribes to events (''tick'', ''agent'', ''presence'', ''shutdown'').

#

Tutorial.step

Nodes (macOS / iOS / Android / headless)

- Connect to ''the same WS server'' using ''role: node''.

- Provides device identity in ''connect''; pairing is ''device-based'' (role ''node'') and

approval exists in device pairing store.

- Exposes commands like ''canvas.*'', ''camera.*'', ''screen.record'', ''location.get''.

Protocol details:

- ''Gateway Protocol''

#

Tutorial.step

Web Chat

- Static UI for chat history and sending using Gateway WS API.

- In remote setups, connects via the same SSH/Tailscale tunnel as other

clients.

Tutorial.step

Connection Lifecycle (Single Client)

Terminal
Client                    Gateway
  |                          |
  ||   (or res error + close)
  |   (payload=hello-ok carries snapshot: presence + health)
  |                          |
  |< event:presence -----|   (final: {runId,status,summary})
  |                          |
Tutorial.step

Wire Protocol (Summary)

- Transport: WebSocket, text frames with JSON payloads.

- First frame ''must'' be ''connect''.

- After handshake:

- Requests: ''{type:"req", id, method, params}'' β†’ ''{type:"res", id, ok, payload|error}''

- Events: ''{type:"event", event, payload, seq?, stateVersion?}''

- If ''OPENCLAW_GATEWAY_TOKEN'' (or ''--token'') is set, ''connect.params.auth.token''

must match or the socket will close.

- Side-effect methods (''send'', ''agent'') require idempotency keys

for safe retries; server keeps a short-lived deduplication cache.

Tutorial.step

Pairing + Local Trust

- All WS clients (operators + nodes) include ''device identity'' on ''connect''.

- New device IDs require pairing approval; gateway issues a device token

for subsequent connections.

- Local connections (loopback or gateway host's own tailnet address) can

be auto-approved to keep same-host UX smooth.

- ''Non-local'' connections must sign ''connect.challenge'' nonce and require

explicit approval.

- Gateway auth (''gateway.auth.*'') still applies to ''all'' connections, local or

remote. Details: ''Gateway Protocol'', ''Pairing'', ''Security''.

Tutorial.step

Protocol Types and Code Generation

- TypeBox schemas define the protocol.

- JSON schemas are generated from these schemas.

- Swift models are generated from JSON schemas.

Tutorial.step

Remote Access

- Preferred: Tailscale or VPN.

- Alternative: SSH tunnel

''ssh -N -L 18789:127.0.0.1:18789 user@host''

- Same handshake + auth tokens apply over tunnel.

- TLS + optional pinning can be enabled for WS in remote setup.

Tutorial.step

Operational Snapshot

- Start: ''openclaw gateway'' (foreground, logs to stdout).

- Health: ''health'' over WS (also included in ''hello-ok'').

- Supervision: launchd/systemd for auto-restart.

Tutorial.step

Invariants

- Only one gateway per host controls one Baileys session.

- Handshake is mandatory; any non-JSON or non-connect first frame is hard-close.

- Events are not replayed; clients must catch up gaps.