{
  "title": "Bridge protocol (legacy node transport)",
  "content": "The Bridge protocol is a **legacy** node transport (TCP JSONL). New node clients\nshould use the unified Gateway WebSocket protocol instead.\n\nIf you are building an operator or node client, use the\n[Gateway protocol](/gateway/protocol).\n\n**Note:** Current OpenClaw builds no longer ship the TCP bridge listener; this document is kept for historical reference.\nLegacy `bridge.*` config keys are no longer part of the config schema.\n\n* **Security boundary**: the bridge exposes a small allowlist instead of the\n  full gateway API surface.\n* **Pairing + node identity**: node admission is owned by the gateway and tied\n  to a per-node token.\n* **Discovery UX**: nodes can discover gateways via Bonjour on LAN, or connect\n  directly over a tailnet.\n* **Loopback WS**: the full WS control plane stays local unless tunneled via SSH.\n\n* TCP, one JSON object per line (JSONL).\n* Optional TLS (when `bridge.tls.enabled` is true).\n* Legacy default listener port was `18790` (current builds do not start a TCP bridge).\n\nWhen TLS is enabled, discovery TXT records include `bridgeTls=1` plus\n`bridgeTlsSha256` so nodes can pin the certificate.\n\n## Handshake + pairing\n\n1. Client sends `hello` with node metadata + token (if already paired).\n2. If not paired, gateway replies `error` (`NOT_PAIRED`/`UNAUTHORIZED`).\n3. Client sends `pair-request`.\n4. Gateway waits for approval, then sends `pair-ok` and `hello-ok`.\n\n`hello-ok` returns `serverName` and may include `canvasHostUrl`.\n\n* `req` / `res`: scoped gateway RPC (chat, sessions, config, health, voicewake, skills.bins)\n* `event`: node signals (voice transcript, agent request, chat subscribe, exec lifecycle)\n\n* `invoke` / `invoke-res`: node commands (`canvas.*`, `camera.*`, `screen.record`,\n  `location.get`, `sms.send`)\n* `event`: chat updates for subscribed sessions\n* `ping` / `pong`: keepalive\n\nLegacy allowlist enforcement lived in `src/gateway/server-bridge.ts` (removed).\n\n## Exec lifecycle events\n\nNodes can emit `exec.finished` or `exec.denied` events to surface system.run activity.\nThese are mapped to system events in the gateway. (Legacy nodes may still emit `exec.started`.)\n\nPayload fields (all optional unless noted):\n\n* `sessionKey` (required): agent session to receive the system event.\n* `runId`: unique exec id for grouping.\n* `command`: raw or formatted command string.\n* `exitCode`, `timedOut`, `success`, `output`: completion details (finished only).\n* `reason`: denial reason (denied only).\n\n* Bind the bridge to a tailnet IP: `bridge.bind: \"tailnet\"` in\n  `~/.openclaw/openclaw.json`.\n* Clients connect via MagicDNS name or tailnet IP.\n* Bonjour does **not** cross networks; use manual host/port or wide-area DNS‑SD\n  when needed.\n\nBridge is currently **implicit v1** (no min/max negotiation). Backward‑compat\nis expected; add a bridge protocol version field before any breaking changes.",
  "code_samples": [],
  "headings": [
    {
      "level": "h2",
      "text": "Why we have both",
      "id": "why-we-have-both"
    },
    {
      "level": "h2",
      "text": "Transport",
      "id": "transport"
    },
    {
      "level": "h2",
      "text": "Handshake + pairing",
      "id": "handshake-+-pairing"
    },
    {
      "level": "h2",
      "text": "Frames",
      "id": "frames"
    },
    {
      "level": "h2",
      "text": "Exec lifecycle events",
      "id": "exec-lifecycle-events"
    },
    {
      "level": "h2",
      "text": "Tailnet usage",
      "id": "tailnet-usage"
    },
    {
      "level": "h2",
      "text": "Versioning",
      "id": "versioning"
    }
  ],
  "url": "llms-txt#bridge-protocol-(legacy-node-transport)",
  "links": []
}