WebSocket Protocol

All real-time communication goes through a single WebSocket endpoint. Both ESP32 firmware (device connections) and client apps (Android, CLI) connect here. The server acts as a blind relay — it never decrypts EWSP payloads, it only routes them.

ℹ️Info
Blind relay by design. EWSP packets are end-to-end encrypted between the client app and the ESP32. The server forwards bytes it cannot read. Wake targets (MAC addresses) are never visible to the server.

Connection

Endpoint
wss://wakelink-project.org/wss
Plain HTTP GET /wss returns 426 Upgrade Required. Always use WebSocket upgrade. TLS required in production.
Auth window
After connecting, the server waits 30 seconds for an auth message. If none arrives, the connection is closed with code 1008.

Authentication Handshake

Send an auth message immediately after connecting. The api_token is the persistent token from your account (wl_… format), or use a short-livedsession_token obtained via POST /api/v1/auth/session.

→ Client app auth (Android / CLI)
json
{
  "type": "auth",
  "api_token": "wl_abc123...",   // persistent token, OR:
  "session_token": "...",        // short-lived 24h token (preferred)
  "client_type": "client"
}
→ ESP32 firmware auth
json
{
  "type": "auth",
  "api_token": "wld_abc123...",  // per-agent token (wld_ prefix), NOT the user wl_ token
  "client_type": "device",       // or "firmware" — both accepted
  "agent_id": "WL35080814"       // must match registered agent_id
}
Firmware must use the per-agent token (wld_…), obtained via GET /api/v1/agents/{agent_id}/export or rotated with POST /api/v1/agents/{agent_id}/rotate-token. Do not use the user's account token (wl_…) in firmware.
← Server auth response (success)
json
{
  "type": "auth_response",
  "status": "authenticated",
  "session_token": "...",
  "expires_in": 86400,
  "max_requests": 10000
}
← Server auth response (failure)
json
{
  "type": "auth_response",
  "status": "failed",
  "error": "Invalid token",
  "retry_after": 600          // seconds (after 5 failures, 10-min lockout)
}
Rate limit: 5 failures per IP per 300 seconds → 10-minute lockout.

Message Types

Keepalive — ping / pong
json
// Server → any (every 30 seconds)
{ "type": "ping" }

// Client/Device → Server (must respond)
{ "type": "pong" }
Connections that miss two consecutive pings are closed.
Server → Client — device online/offline
json
{
  "type": "agent_status",
  "agent_id": "WL35080814",
  "online": true
}
Sent to all connected clients of the same user when an ESP32 connects or disconnects.
Client → Server → Device — relay envelope
json
{
  "type": "relay",
  "payload": "...",         // EWSP packet as JSON string (opaque to server)
  "signature": "...",       // optional HMAC-SHA256 of payload
  "agent_id": "WL35080814",
  "request_id": "X7K2M9P1" // optional tracking ID
}
The server validates device ownership, then blindly forwards the payload to the ESP32. It cannot read the encrypted content.
Client → Server → Device — EWSP packet (direct route)
json
{
  "v": "1.0",
  "sid": "a1b2c3d4e5f6a7b8",  // session ID (routes to correct device)
  "seq": 42,
  "p": "base64url(ciphertext+tag)"
}
EWSP packets can be sent directly (without relay envelope). Server routes by sid field. Payload is always encrypted.
Server → any — session limit exceeded
json
{
  "type": "error",
  "error": "Session request limit exceeded",
  "code": "LIMIT_EXCEEDED"
}
// Connection closed with code 1008
Each session allows up to 10,000 requests. After that, reconnect to get a new session.

Session Tokens (Recommended for Clients)

Instead of sending your long-lived api_token over WebSocket, create a short-lived session token via REST first. This limits exposure if a session is compromised.

Create session token
json
POST /api/v1/auth/session
Authorization: Bearer wl_abc123...

→ {
    "session_token": "...",
    "expires_at": "2026-01-04T12:00:00Z",
    "expires_in": 86400,
    "max_requests": 10000
  }
Session tokens expire after 24 hours or 10,000 requests. They are stored in Redis and never touch the database.

Connection Lifecycle

1
Client connects
WebSocket upgrade handshake to /wss
2
Auth window opens
Server waits up to 30 s for auth message, else closes with 1008
3
Client sends auth
Type auth with api_token or session_token + client_type
4
Server validates
Sends auth_response — status: authenticated or failed
5
Ping loop starts
Server sends keep-alive pings every 30 s
6
EWSP packets flow
Client ↔ Device encrypted relay — server never decrypts
7
Session expires
After 24 h or 10 000 requests — client must reconnect and re-auth