WebSocket Relay

WebSocket Relay

The WebSocket relay routes Voyager’s LinkedIn API calls through your real Chrome browser instead of the headless Chromium instance. The extension opens a WebSocket connection to the server, receives API requests, executes them with fetch() and credentials: 'include', and returns the responses. LinkedIn sees requests coming from your actual browser with real cookies and your real IP address.

Why use it

  • Session fidelity — requests use your real browser cookies, User-Agent, and IP address. No fingerprint mismatch risk.
  • No proxy needed — traffic originates from your machine, not a datacenter IP.
  • Zero LinkedIn tabs — the extension’s service worker handles everything. You do not need a LinkedIn tab open.
  • Automatic fallback — when Chrome is closed or the extension disconnects, Voyager falls back to its headless Chromium browser seamlessly.

How it works

Chrome Extension (service worker)
|
| WebSocket (wss://)
|
Voyager Server (/api/relay)
|
| Receives API call from phantoms/actions
| Routes through relay instead of headless browser
|
v
Extension executes: fetch(url, { credentials: 'include' })
|
| Chrome attaches real cookies automatically
|
LinkedIn API
  1. The Chrome extension connects to wss://<server>/api/relay on startup
  2. It sends an auth message with the API key and user ID
  3. The server validates the key and registers the connection
  4. When any Voyager API call needs to reach LinkedIn, the server sends a request message through the WebSocket
  5. The extension executes the request using fetch() with credentials: 'include' — Chrome attaches real cookies
  6. The extension sends the response back through the WebSocket
  7. Voyager returns the result to the original API caller

The relay is transparent to all Voyager endpoints. Whether a request goes through the relay or headless Chromium, the API response format is identical. You can enable and disable the relay at any time without changing your integration code.

Protocol

Authentication

The first message on the WebSocket must be an auth message:

1{ "type": "auth", "apiKey": "voy_xxx", "userId": "charis" }

The server responds with:

1{ "type": "authenticated", "tenantId": "32e365dc", "userId": "charis" }

If authentication fails or no auth message arrives within 10 seconds, the server closes the connection.

Request / Response

The server sends request messages:

1{
2 "type": "request",
3 "id": "r1_m3k9f",
4 "method": "GET",
5 "url": "https://www.linkedin.com/voyager/api/identity/profiles/...",
6 "headers": { "Accept": "application/json" },
7 "body": null
8}

The extension responds with the same id:

1{
2 "type": "response",
3 "id": "r1_m3k9f",
4 "status": 200,
5 "body": "{...}"
6}

Or on failure:

1{
2 "type": "error",
3 "id": "r1_m3k9f",
4 "error": "net::ERR_NETWORK_CHANGED"
5}

Ping / Pong Keepalive

The server sends a ping every 20 seconds to keep the Manifest V3 service worker alive:

1{ "type": "ping" }

The extension replies:

1{ "type": "pong" }

MV3 service workers are killed after 30 seconds of inactivity. The 20-second ping interval keeps the worker alive. As a backup, the extension also creates a Chrome alarm (relay-keepalive) that fires every 24 seconds — if the worker was killed and restarted, it reconnects automatically.

Auto-Reconnect

If the WebSocket disconnects (server restart, network change, etc.), the extension reconnects with exponential backoff:

AttemptDelay
1st1 second
2nd2 seconds
3rd4 seconds
4th8 seconds
doubles each time
Max60 seconds

The backoff resets to 1 second on successful connection. When settings change (API key, user ID, or server URL), the extension closes and immediately reconnects.

Extension Badge

The Chrome extension icon shows relay status:

BadgeColorMeaning
RBlue (#2563eb)Relay active — requests route through Chrome
!Red (#dc2626)Server unreachable or session invalid
(none)Green (#059669)Connected to server, no relay (normal headless mode)

Status Endpoints

Check Relay Status (User)

$curl -H "Authorization: Bearer $KEY" \
> -H "X-User-Id: $USER" \
> "$BASE/api/relay/check"
1{
2 "relayConnected": true,
3 "fetcherSet": true
4}
  • relayConnected — WebSocket connection is open for this user
  • fetcherSet — the relay fetcher is wired into VoyagerApi and SalesNavApi

Admin Relay Status

$curl -H "Authorization: Bearer $ADMIN_SECRET" \
> "$BASE/api/admin/relay/status"
1{
2 "connections": [
3 {
4 "tenantId": "32e365dc",
5 "userId": "charis",
6 "connectedAt": 1742601234567,
7 "requestCount": 47
8 }
9 ]
10}

Fallback Behavior

When the relay is not available, Voyager automatically falls back to headless Chromium:

ScenarioBehavior
Chrome is closedHeadless Chromium handles all requests
Extension is disabledHeadless Chromium handles all requests
WebSocket disconnects mid-requestIn-flight request times out (30s), next request uses headless
Chrome reopensExtension reconnects, relay resumes

Individual relay requests time out after 30 seconds. If the extension is connected but Chrome is unresponsive (e.g., system sleep), requests will time out before falling back. The fallback only activates when the WebSocket connection itself is closed.

Configuration

The relay requires no server-side configuration — it is always available at /api/relay. The extension side needs:

  1. API key — set in the extension options page
  2. User ID — set in the extension options page
  3. Server URL — defaults to https://li.scaleabm.org, configurable in extension options

The extension connects the relay automatically on startup. No manual activation is needed.