This document describes the WebSocket protocol used between iOSLobster and the OpenClaw Gateway.
Pairing uses an HTTPS endpoint derived from the gateway WebSocket URL:
https://<gateway-host>/pair
{
"device_id": "uuid",
"public_key": "base64-encoded-Ed25519-public-key",
"challenge": "nonce",
"app_version": "1.0.0",
"platform": "ios"
}{
"device_token": "opaque-device-token",
"gateway_name": "My Gateway",
"fingerprint": "SHA256:...",
"gateway_public_key": "<base64-encoded-ed25519-public-key>"
}wss://<gateway-host>/ws
The client connects with a device token in the Authorization header:
Authorization: Bearer <device-token>
- Client connects with device token
- Gateway validates token and sends
connectedmessage - Client receives session ID
- Bidirectional messaging begins
All messages are JSON-encoded.
| Field | Type | Description |
|---|---|---|
type |
string | Message type |
timestamp |
int64 | Unix timestamp in milliseconds |
{
"type": "text",
"session_id": "abc123",
"timestamp": 1706745600000,
"payload": {
"text": "Hello, world!"
}
}{
"type": "voice",
"session_id": "abc123",
"timestamp": 1706745600000,
"payload": {
"transcription": "What's the weather like?",
"language": "en-US"
}
}{
"type": "canvas_event",
"session_id": "abc123",
"timestamp": 1706745600000,
"payload": {
"element_id": "button-1",
"action": "tap",
"position": { "x": 100, "y": 200 }
}
}{
"type": "capability_response",
"session_id": "abc123",
"timestamp": 1706745600000,
"payload": {
"request_id": "req-456",
"granted": true,
"capability": "mic.foreground.60s",
"token": {
"expires_at": "2026-02-01T12:05:00Z",
"nonce": "random123"
}
}
}{
"type": "image",
"session_id": "abc123",
"timestamp": 1706745600000,
"payload": {
"data": "<base64>",
"mime_type": "image/jpeg"
}
}{
"type": "canvas_drawing",
"session_id": "abc123",
"timestamp": 1706745600000,
"payload": {
"data": "<base64>",
"mime_type": "image/png"
}
}{
"type": "panic_abort",
"session_id": "abc123",
"timestamp": 1706745600000,
"payload": {
"reason": "user_abort"
}
}{
"type": "agent_selection",
"session_id": "abc123",
"timestamp": 1706745600000,
"payload": {
"agent_id": "claude-main"
}
}{
"type": "heartbeat",
"session_id": "abc123",
"timestamp": 1706745600000,
"payload": {}
}When message signing is enabled, an Ed25519 signature is attached to the payload:
{
"payload": {
"sig": "<base64 signature>",
"kid": "device-id",
"...": "payload"
}
}{
"type": "connected",
"agent_id": null,
"attention_level": null,
"timestamp": 1706745600000,
"payload": {
"session_id": "abc123",
"gateway_version": "1.0.0"
}
}{
"type": "text",
"agent_id": "claude-main",
"attention_level": "active",
"timestamp": 1706745600000,
"payload": {
"text": "Hello from the agent!"
}
}{
"type": "canvas_update",
"agent_id": "claude-main",
"attention_level": "passive",
"timestamp": 1706745600000,
"payload": {
"action": "draw",
"elements": [
{
"id": "text-1",
"type": "text",
"bounds": { "x": 10, "y": 20, "width": 200, "height": 50 },
"content": "Hello, Canvas!",
"interactive": false
}
],
"transition": "fade"
}
}{
"type": "capability_request",
"agent_id": "claude-main",
"attention_level": "interrupt",
"timestamp": 1706745600000,
"payload": {
"request_id": "req-456",
"capability": "mic.foreground.60s",
"duration": 60,
"purpose": "Voice transcription"
}
}{
"type": "capability_granted",
"agent_id": "claude-main",
"attention_level": "notify",
"timestamp": 1706745600000,
"payload": {
"request_id": "req-456",
"token": {
"capability": "mic.foreground.60s",
"granted_at": "2026-02-01T12:00:00Z",
"expires_at": "2026-02-01T12:05:00Z",
"session_id": "abc123",
"nonce": "random_bytes",
"signature": "gateway_signature"
}
}
}{
"type": "attention",
"agent_id": "claude-main",
"attention_level": "urgent",
"timestamp": 1706745600000,
"payload": {
"reason": "Timer completed"
}
}{
"type": "status",
"agent_id": "claude-main",
"attention_level": "passive",
"timestamp": 1706745600000,
"payload": {
"status": "thinking",
"progress": 0.5
}
}{
"type": "error",
"agent_id": null,
"attention_level": null,
"timestamp": 1706745600000,
"payload": {
"code": "INVALID_SESSION",
"message": "Session expired"
}
}| Level | Description | Client Behavior |
|---|---|---|
passive |
Background update | No notification |
notify |
Worth noting | Badge/subtle indicator |
active |
Needs attention | Visual highlight |
interrupt |
Requires action | Modal/sheet |
urgent |
Time-sensitive | Haptic + sound |
Capabilities are time-boxed permissions granted by the user.
{
"capability": "mic.foreground.60s",
"granted_at": "2026-02-01T12:00:00Z",
"expires_at": "2026-02-01T12:01:00Z",
"session_id": "abc123",
"nonce": "random_bytes",
"signature": "gateway_signature"
}| Capability | Duration | Description |
|---|---|---|
mic.foreground.60s |
60s | Microphone access |
camera.front.once |
5s | Single photo |
location.precise.5min |
300s | GPS location |
canvas.control.90s |
90s | Canvas interaction |
| Type | Description |
|---|---|
text |
Text block |
image |
Raster image (base64 or URL) |
shape |
Vector shape |
video |
Video player |
highlight |
Overlay highlight |
{
"id": "unique-id",
"type": "text",
"bounds": {
"x": 0,
"y": 0,
"width": 100,
"height": 50
},
"content": "Text or base64 data",
"style": {
"color": "#000000",
"fontSize": 16
},
"interactive": true
}| Code | Description |
|---|---|
INVALID_TOKEN |
Device token invalid |
INVALID_SESSION |
Session expired |
RATE_LIMITED |
Too many requests |
CAPABILITY_DENIED |
Capability not granted |
INTERNAL_ERROR |
Server error |