For AI agents: a documentation index is available at the root level at /llms.txt and /llms-full.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
DashboardGet API Key
GuidesAPI ReferenceCommon Patterns
  • Getting Started
    • Welcome
    • Introduction
    • Quickstart
    • Authentication
  • Core Concepts
    • Sessions & Cookies
    • Profiles
    • Messaging
    • Posts & Engagement
    • Connections
    • Search
    • SalesNav
    • SalesNav Filters
  • Playbooks
    • Multi-Step Workflows
    • Query Patterns
  • Workflow Guides
    • Lead Discovery
    • Warm Outreach
    • Content Intelligence
    • Company Research
    • Job Market Intelligence
    • Webhooks
  • Integration
    • MCP Server
    • WebSocket Relay
DashboardGet API Key
On this page
  • WebSocket Relay
  • Why use it
  • How it works
  • Protocol
  • Authentication
  • Request / Response
  • Ping / Pong Keepalive
  • Auto-Reconnect
  • Extension Badge
  • Status Endpoints
  • Check relay status (user-scoped)
  • Admin relay status
  • Fallback Behaviour
  • Configuration
Integration

WebSocket Relay

Was this page helpful?
Previous
Built with

WebSocket Relay

The WebSocket relay lets the Voyager server dispatch LinkedIn API calls through the user’s real Chrome browser instead of issuing them from the server. The Chrome extension holds a WebSocket to the server, receives requests, executes them with fetch() and credentials: 'include', and streams the response back. LinkedIn sees requests coming from the user’s actual browser with real cookies and a real residential IP.

Why use it

  • Session fidelity. Requests carry the user’s real browser cookies, User-Agent, TLS fingerprint, and IP. No datacenter-IP tell, no fingerprint mismatch.
  • Zero LinkedIn tabs needed. The extension’s service worker handles fetches directly — users don’t have to keep a LinkedIn tab open.
  • Self-healing cookies. When the server detects a stale session, it sends a cookie_sync_request; the extension grabs fresh cookies from Chrome’s cookie store and syncs them back automatically.
  • Graceful fallback. When the relay isn’t available (Chrome closed, extension uninstalled, WebSocket drop), every endpoint falls back to direct HTTP + residential proxy using the stored cookies — just regular HTTPS. There is no third tier; Puppeteer was removed 2026-04-22.
  • Authwall detection. If the extension observes a redirect to a LinkedIn authwall or checkpoint page while loading content, it alerts the server immediately.

How it works

Chrome Extension (service worker)
│ WebSocket (wss://)
▼
Voyager Server (/api/relay)
│
│ Routes an outbound LinkedIn call to the relay
│ (if the connection is healthy)
▼
Extension executes: fetch(url, { credentials: 'include' })
│ Chrome attaches real cookies, uses real IP
▼
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 Voyager needs to call LinkedIn, it picks the relay if available, or falls back to direct HTTP + residential proxy.
  5. The extension executes the request, Chrome attaches real cookies, the response comes back through the WebSocket.
  6. Voyager returns the result to the endpoint caller.

The relay is transparent to all Voyager endpoints. Whether a request goes through the relay or direct HTTP, the API response format is identical.

Protocol

Authentication

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

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

The server responds:

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 replies with the same id:

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

Or on failure:

1{ "type": "error", "id": "r1_m3k9f", "error": "net::ERR_NETWORK_CHANGED" }

Ping / Pong Keepalive

The server pings every 20 seconds to keep the MV3 service worker alive:

1{ "type": "ping" }

The extension replies with { "type": "pong" }.

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

Auto-Reconnect

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

AttemptDelay
1st1s
2nd2s
3rd4s
4th8s
…doubles each time
Max60s

The backoff resets to 1s on successful connection. When the user changes the API key, user ID, or server URL in options, the extension closes and immediately reconnects.

Voyager’s retry layer is explicitly aware of relay drops. An outbound call that fails with "Relay connection closed" is classified as a retryable transport error — the next attempt falls through to direct HTTP + proxy, so a momentary relay drop rarely surfaces to the caller as a failure.

Extension Badge

The Chrome extension icon reflects the relay status:

BadgeColourMeaning
RBlue (#2563eb)Relay active — requests route through Chrome
!Red (#dc2626)Server unreachable, or LinkedIn session invalid
(none)Green (#059669)Connected to the server; traffic will use direct HTTP until Chrome is available

Status Endpoints

Check relay status (user-scoped)

$curl -H "Authorization: Bearer $KEY" -H "X-User-Id: $USER" \
> "$BASE/api/relay/check"
1{ "relayConnected": true, "fetcherSet": true }
  • relayConnected — WebSocket 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 Behaviour

When the relay isn’t usable, Voyager falls back to direct HTTP + residential proxy — not to a headless browser.

ScenarioBehaviour
Chrome closed / extension disabledTier 2 (direct HTTP + IPRoyal proxy) handles every request
WebSocket drops mid-requestThe in-flight request fails with "Relay connection closed"; the retry attempt falls through to Tier 2
Chrome reopensExtension reconnects within ~1s; subsequent calls use the relay again
Server redeploysWebSocket drops, extension reconnects with backoff once the new instance is up

SalesNav endpoints that fingerprint a real Chrome session are more robust through the relay, but most still work on Tier 2 as well. If SalesNav specifically fails on direct HTTP for a tenant, check whether li_a and li_ep_auth_context are synced.

Configuration

The server side needs no configuration — the relay is always available at /api/relay. The extension 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 options

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