Peer-to-peer connection bootstrapping over WebSocket.
This server helps two peers find each other and exchange signalling data. Connect over WebSocket, discover a peer via short share codes, and relay messages to complete a handshake. Connections can stay open as long as needed — for example, to host a game and accept incoming peers — but throughput is rate-limited to signalling use.
There are two ways to use the server:
Ephemeral (share codes) — Connect and request a share code (4–6 letters), then give it to peers out-of-band. They look up the code to find your ID and start exchanging messages. The code is released when you disconnect. Good for hosting a session that others join by code.
Persistent (registered identity) — Connect and register a token. Store the token and reconnect with it later to keep the same UUID across sessions. Peers who already know your ID can message you whenever you're connected. Good for a server with a permanent ID.
Open a WebSocket connection to the /v1 endpoint:
ws://HOST/v1 # new identity (assigned a random UUID) ws://HOST/v1?token=TOKEN # resume a registered identity
On success the server immediately sends your client ID:
← { "type": "id", "id": "550e8400-e29b-41d4-a716-446655440000" }
All subsequent messages are JSON text frames. Every request you send can
include an optional "mid" field (any JSON value). The server
echoes it back on the corresponding response so you can match
request/response pairs.
Generate a token that persists your identity across connections. Store
the token and pass it as the ?token= query parameter on
reconnect to keep the same UUID.
→ { "type": "register" } ← { "type": "register", "token": "BASE64_TOKEN" }
Acquire a short alphanumeric share code that other peers can look up to
find your ID. The code is assigned once per connection — calling
share again returns the same code. The code is released
when you disconnect.
→ { "type": "share" } ← { "type": "share", "code": "BFKM" }
Codes are 4–6 uppercase consonants (no vowels), all distinct. Shorter codes are preferred when available.
Resolve a share code to the peer's UUID.
→ { "type": "lookup", "code": "BFKM" } ← { "type": "lookup", "id": "550e8400-e29b-41d4-a716-446655440000" }
Send a text payload to another connected peer by UUID. The server relays the message and responds with an acknowledgement.
# sender → { "type": "message", "recipient": "RECIPIENT_UUID", "text": "..." } ← { "type": "ack" } # recipient receives ← { "type": "message", "sender": "SENDER_UUID", "text": "..." }
Returned when a request fails. Includes the mid from the
original request if one was provided.
← { "type": "error", "message": "Code not found: ZZZZ" }
A host opens a session and gives out a share code. Players join by code.
# Host connects Host ← server { "type": "id", "id": "HOST_UUID" } # Host requests a share code Host → server { "type": "share" } Host ← server { "type": "share", "code": "BFKM" } # Host displays "BFKM" in the lobby UI # Player connects and looks up the code Player ← server { "type": "id", "id": "PLAYER_UUID" } Player → server { "type": "lookup", "code": "BFKM" } Player ← server { "type": "lookup", "id": "HOST_UUID" } # Player sends signalling data to Host Player → server { "type": "message", "recipient": "HOST_UUID", "text": "{\"sdp\":\"...\"}" } Host ← server { "type": "message", "sender": "PLAYER_UUID", "text": "{\"sdp\":\"...\"}" } # They exchange messages until the P2P handshake completes # Host stays connected to accept more players
A server registers once, stores its token, and reconnects with the same UUID across restarts. Clients who know the UUID can reach it any time it's connected.
# Server connects for the first time Server ← server { "type": "id", "id": "SERVER_UUID" } # Server registers to get a token Server → server { "type": "register" } Server ← server { "type": "register", "token": "BASE64_TOKEN" } # Server stores TOKEN and SERVER_UUID (e.g. publish UUID in app config) # ... later, server reconnects with the same identity Server ← ws://HOST/v1?token=BASE64_TOKEN Server ← server { "type": "id", "id": "SERVER_UUID" } # same UUID as before # Client already knows SERVER_UUID, connects and messages directly Client ← server { "type": "id", "id": "CLIENT_UUID" } Client → server { "type": "message", "recipient": "SERVER_UUID", "text": "{\"sdp\":\"...\"}" } Server ← server { "type": "message", "sender": "CLIENT_UUID", "text": "{\"sdp\":\"...\"}" } # They exchange messages until the P2P handshake completes
Throughput is rate-limited to signalling use.
| Scope | Limit |
|---|---|
| WebSocket connections per IP | burst 3, ~6/min sustained |
| Frames per client | burst 5, 2/sec sustained |
| Lookups per client | burst 2, 1 every 2 sec sustained |
| Max frame size | 4 KB |
| Message buffer per recipient | 32 messages |
Exceeding the per-IP connection limit returns HTTP 429. Exceeding per-client limits returns a WebSocket error frame.
| Message | Cause |
|---|---|
| Bad token. | Invalid or corrupted ?token= value (close code 4000) |
| Token already in use. | Another connection is already using this token (close code 4000) |
| Frame too large | Payload exceeds 4 KB |
| Rate limited | Too many requests |
| Expected JSON object | Frame was not a JSON object |
| Missing or invalid 'type' field | No type string in the object |
| Missing or invalid 'code' field | Lookup without a valid code |
| Missing or invalid 'recipient' field | Message without a valid UUID recipient |
| Missing or invalid 'text' field | Message without a text string |
| Code not found: XXXX | Share code does not exist or expired |
| Recipient not found | No connected client with that UUID |
| Recipient busy | Recipient's message buffer is full |
| Recipient disconnected | Recipient disconnected before delivery |
| No share codes available | Code space exhausted (unlikely) |
| Unknown message type: X | Unrecognized type value |
Open the browser console to use the built-in test client.