---
title: WebSocket
description: >-
  IvyWebSocket class for real-time job progress via WebSocket with
  auto-reconnection
---

`IvyWebSocket` lets you stream a job's progress in real time, pushing phase, percentage, and status changes to your app as the computation runs instead of polling for them. Reach for it when you want live updates and are willing to manage the connection yourself; it handles reconnection, keepalive, and multiple subscribers per job for you. In React apps you'd typically consume it through the [`useIvyWebSocket` hook](/docs/api/sdk-react) rather than instantiating this class directly.

## SDK WebSocket Reference

> **Planned specification.** `@ivybiosciences/sdk` is not released, and there is no package to install. This page describes the SDK's planned interface. To call the platform today, use the [REST API](/docs/api/rest-jobs), the [ivybloom CLI](/docs/cli), or the [MCP server](/docs/cli/mcp).

The `IvyWebSocket` class provides real-time job progress updates via WebSocket connections. It handles automatic reconnection, keepalive pings, and multi-listener subscriptions.

Access via `ivy.websocket` or instantiate directly.

---

## Overview

External clients cannot open a job WebSocket today (see [WebSocket Protocol](/docs/api/realtime-websocket)), so until the SDK ships, follow jobs with the [SSE job event stream](/docs/api/realtime-sse#job-event-proxy-sse); the Python tabs below use it. Inside the platform, the backend pushes status updates whenever a job's phase, progress percentage, or status changes — typically every 1-5 seconds during active computation.

### Features

- Automatic reconnection with exponential backoff on unexpected disconnection
- 25-second keepalive pings to prevent proxy/load balancer timeouts
- Multi-listener support (multiple subscribers per job)
- Transparent URL scheme conversion (`https→wss`, `http→ws`)

---

## Constructor

```ts
new IvyWebSocket(
  baseUrl: string,
  getToken: () => Promise<string>,
  options?: WebSocketOptions
)
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `baseUrl` | `string` | API base URL (`https://` or `http://`). Converted to `wss://` or `ws://` |
| `getToken` | `() => Promise<string>` | Async function returning the auth token |
| `options` | `WebSocketOptions` | Connection behavior configuration |

### WebSocketOptions

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `autoReconnect` | `boolean` | `true` | Reconnect on unexpected close (non-1000 code) |
| `maxReconnectAttempts` | `number` | `5` | Max consecutive reconnection attempts |
| `reconnectInterval` | `number` | `1000` | Base delay in ms (doubles each attempt) |
| `onReconnect` | `(attempt: number) => void` | — | Called on each reconnection attempt |
| `onClose` | `(code: number, reason: string) => void` | — | Called when connection closes |

---

## `websocket.connect(jobId)`

Connect to the authenticated WebSocket endpoint for a specific job.

```ts
async connect(jobId: string): Promise<void>
```

No public WebSocket URL serves job updates. Until the SDK ships, follow a job with the SSE stream `GET /api/v1/jobs/{jobId}/events` ([SSE Streaming](/docs/api/realtime-sse)).

**Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `jobId` | `string` | The job identifier to subscribe to |

**Throws:** `IvyWebSocketError` if the connection fails.

### Examples

**TypeScript**

```ts
await ivy.websocket.connect("job_abc123")
console.log(ivy.websocket.isConnected) // true
```

**Python**

```python
import json
import httpx

def follow_job(job_id: str, api_key: str):
    # Today: the SSE job event proxy (there is no public WebSocket)
    url = f"https://www.ivybiosciences.com/api/v1/jobs/{job_id}/events"
    headers = {"Authorization": f"Bearer {api_key}"}
    with httpx.stream("GET", url, headers=headers, timeout=None) as response:
        response.raise_for_status()
        event = None
        for line in response.iter_lines():
            if line.startswith("event:"):
                event = line[len("event:"):].strip()
            elif line.startswith("data:") and event == "job_update":
                update = json.loads(line[len("data:"):])["data"]
                print(f"[{update.get('phase_name', '?')}] {update.get('progress', 0)}%")
                if update["status"] in ("COMPLETED", "SUCCESS", "FAILURE", "FAILED", "CANCELLED"):
                    return update

follow_job("job_abc123", "ivy_your_key_here")
```

**React**

```tsx
import { useIvyWebSocket } from "@ivybiosciences/sdk/react"

function JobConnection({ jobId }: { jobId: string }) {
  const { isConnected, status } = useIvyWebSocket(jobId)

  return (
    <div>
      <span>{isConnected ? "Connected" : "Connecting..."}</span>
      {status && <p>{status.phase}: {status.progress}%</p>}
    </div>
  )
}
```

---

## `websocket.subscribe(jobId, callback)`

Subscribe to progress updates for a specific job.

```ts
subscribe(jobId: string, callback: (data: JobProgress) => void): () => void
```

Multiple subscribers can listen to the same job simultaneously.

**Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `jobId` | `string` | The job identifier to listen for |
| `callback` | `(data: JobProgress) => void` | Invoked on each progress message |

**Returns:** An unsubscribe function — call it to stop receiving events.

### Examples

**TypeScript**

```ts
const unsub = ivy.websocket.subscribe("job_abc123", (progress) => {
  console.log(`${progress.phase}: ${progress.progress}%`)
  if (progress.status === "COMPLETED") {
    console.log("Done!", progress.result)
    unsub()
  }
})

await ivy.websocket.connect("job_abc123")
```

**Python**

```python
import json
import httpx

def follow_job(job_id: str, api_key: str):
    # Today: the SSE job event proxy (there is no public WebSocket)
    url = f"https://www.ivybiosciences.com/api/v1/jobs/{job_id}/events"
    headers = {"Authorization": f"Bearer {api_key}"}
    with httpx.stream("GET", url, headers=headers, timeout=None) as response:
        response.raise_for_status()
        event = None
        for line in response.iter_lines():
            if line.startswith("event:"):
                event = line[len("event:"):].strip()
            elif line.startswith("data:") and event == "job_update":
                update = json.loads(line[len("data:"):])["data"]
                print(f"[{update.get('phase_name', '?')}] {update.get('progress', 0)}%")
                if update["status"] in ("COMPLETED", "SUCCESS", "FAILURE", "FAILED", "CANCELLED"):
                    return update

follow_job("job_abc123", "ivy_your_key_here")
```

**React**

```tsx
import { useIvyWebSocket } from "@ivybiosciences/sdk/react"

function JobProgress({ jobId }: { jobId: string }) {
  const { status, isConnected, error } = useIvyWebSocket(jobId)

  if (error) return <p>Error: {error.message}</p>
  if (!isConnected) return <p>Connecting...</p>

  return (
    <div>
      <p>{status?.phase}: {status?.progress ?? 0}%</p>
      {status?.status === "COMPLETED" && (
        <pre>{JSON.stringify(status.result, null, 2)}</pre>
      )}
    </div>
  )
}
```

---

## `websocket.disconnect()`

Gracefully disconnect the WebSocket connection.

```ts
disconnect(): void
```

Sends close code 1000 (normal closure) to the server, stops keepalive pings, and clears connection state. Does not trigger auto-reconnect.

---

## `websocket.isConnected`

Whether the WebSocket is currently in the OPEN state.

```ts
get isConnected(): boolean
```

---

## Reconnection Strategy

When the connection drops unexpectedly (non-1000 close code):

```
Attempt 1: wait reconnectInterval ms         (1s)
Attempt 2: wait reconnectInterval * 2 ms     (2s)
Attempt 3: wait reconnectInterval * 4 ms     (4s)
Attempt 4: wait reconnectInterval * 8 ms     (8s)
Attempt 5: wait reconnectInterval * 16 ms    (16s)
— Give up after maxReconnectAttempts —
```

The `onReconnect` callback fires on each attempt, useful for showing UI indicators.

---

## Message Format

The WebSocket receives JSON messages with this structure:

```json
{
  "job_id": "job_abc123",
  "status": "PROCESSING",
  "progress": 45,
  "phase_name": "main_computation",
  "result": null,
  "error_message": null
}
```

The SDK normalizes this to a `JobProgress` object:

```ts
{
  jobId: "job_abc123",
  status: "PROCESSING",
  progress: 45,
  phase: "main_computation",
  result: undefined,
  error: undefined,
}
```

---

## WebSocket Close Codes

| Code | Meaning | SDK Behavior |
|------|---------|--------------|
| `1000` | Normal closure | No reconnect |
| `1001` | Server going away (deploy) | Auto-reconnect |
| `1006` | Abnormal closure (network) | Auto-reconnect |
| `1008` | Policy violation (bad token) | Auto-reconnect (new token fetched) |
| `4001` | Too many connections per user | No reconnect (close other connections) |
| `4002` | Too many connections per job | No reconnect (close other connections) |
| `4003` | Authentication failed | No reconnect (re-authenticate) |
| `4004` | Guest feature disabled | No reconnect |

---

## Full Example

**TypeScript**

```ts
const ivy = createIvyClient({ apiKey: "ivy_..." })

// Subscribe BEFORE connecting (messages arrive immediately after open)
const unsub = ivy.websocket.subscribe("job_abc123", (progress) => {
  console.log(`[${progress.phase}] ${progress.progress}% — ${progress.status}`)

  if (progress.status === "COMPLETED") {
    console.log("Results:", progress.result)
    unsub()
    ivy.websocket.disconnect()
  }
})

// Connect with custom options
await ivy.websocket.connect("job_abc123")

// Later: manual disconnect
// ivy.websocket.disconnect()
```

**Python**

```python
import json
import time
import httpx

TERMINAL = ("COMPLETED", "SUCCESS", "FAILURE", "FAILED", "CANCELLED")

def monitor_job(job_id: str, api_key: str, max_retries: int = 5):
    """Today: the SSE job event proxy. Each stream lasts up to about five minutes."""
    url = f"https://www.ivybiosciences.com/api/v1/jobs/{job_id}/events"
    headers = {"Authorization": f"Bearer {api_key}"}

    for attempt in range(max_retries):
        try:
            with httpx.stream("GET", url, headers=headers, timeout=None) as response:
                response.raise_for_status()
                event = None
                for line in response.iter_lines():
                    if line.startswith("event:"):
                        event = line[len("event:"):].strip()
                    elif line.startswith("data:") and event == "job_update":
                        update = json.loads(line[len("data:"):])["data"]
                        print(f"[{update.get('phase_name', '?')}] {update.get('progress', 0)}% — {update['status']}")
                        if update["status"] in TERMINAL:
                            return update
        except httpx.TransportError:
            pass
        wait = 2 ** attempt
        print(f"Stream ended. Reopening in {wait}s (attempt {attempt + 1})")
        time.sleep(wait)

    raise RuntimeError("Max reconnection attempts exceeded")

result = monitor_job("job_abc123", "ivy_your_key_here")
```

**React**

```tsx
import { useIvyWebSocket } from "@ivybiosciences/sdk/react"

// useIvyWebSocket takes only { enabled }; the client's IvyWebSocket
// reconnects on its own (5 attempts, backoff from 1s).
function FullJobMonitor({ jobId }: { jobId: string }) {
  const { status, isConnected, error } = useIvyWebSocket(jobId)

  return (
    <div>
      <div className="flex items-center gap-2">
        <span className={`w-2 h-2 rounded-full ${
          isConnected ? "bg-green-500" : "bg-yellow-500 animate-pulse"
        }`} />
        <span>{isConnected ? "Live" : "Reconnecting..."}</span>
      </div>

      {status && (
        <div>
          <p>[{status.phase}] {status.progress}% — {status.status}</p>
          {status.status === "COMPLETED" && (
            <pre>{JSON.stringify(status.result, null, 2)}</pre>
          )}
          {status.status === "FAILURE" && (
            <p className="text-red-600">{status.error}</p>
          )}
        </div>
      )}

      {error && <p className="text-red-600">{error.message}</p>}
    </div>
  )
}
```
