> This page is for SDKs, version Legacy.
> For other versions, use one of these documentation indexes:
> - Current (default): https://buildwithfern.com/learn/sdks/current/llms.txt
> - Legacy: https://buildwithfern.com/learn/sdks/legacy/llms.txt

> For clean Markdown content of this page, append .md to this URL. For the complete documentation index, see https://buildwithfern.com/learn/llms.txt.

# WebSocket clients

> Generate typed WebSocket clients in your SDKs for bidirectional communication with connection management, typed messages, and automatic reconnection.

> **You're viewing the legacy SDK docs**
>
> These pages cover SDK generation configured with `generators.yml`. New projects use `sdk-config.yml`, so see [the Current version of this page](/learn/sdks/deep-dives/websocket-clients) for up-to-date guidance. To move an existing project, follow the [SDK Config migration guide](/learn/sdks/legacy/generators/migration-guides/sdk-config), which walks through `fern sdk migrate`, and review the [`sdk-config.yml` reference](/learn/sdks/reference/sdk-config).

> **Enterprise feature**
>
> This feature is available only for the [Enterprise plan](https://buildwithfern.com/pricing). To get started, reach out to [support@buildwithfern.com](mailto:support@buildwithfern.com).

Fern generates typed WebSocket clients from your [AsyncAPI channel definitions](/learn/api-definitions/asyncapi/channels/pubsub). The generated clients handle connection management, typed send/receive messages, and automatic reconnection.

Fern generates WebSocket clients for TypeScript, Python, Java, C#, and Rust. Go generates types only, and Ruby isn't yet supported.

## Enable WebSocket client generation

Add the config flag to the relevant generator in your `generators.yml`:

**`TypeScript`**

```yaml TypeScript
groups:
  ts-sdk:
    generators:
      - name: fernapi/fern-typescript-sdk
        version: ...
        config:
          generateWebSocketClients: true
```

**`Python`**

```yaml Python
groups:
  python-sdk:
    generators:
      - name: fernapi/fern-python-sdk
        version: ...
        config:
          should_generate_websocket_clients: true
```

**`C#`**

```yaml C#
groups:
  csharp-sdk:
    generators:
      - name: fernapi/fern-csharp-sdk
        version: ...
        config:
          experimental-enable-websockets: true
```

**`Rust`**

```yaml Rust
groups:
  rust-sdk:
    generators:
      - name: fernapi/fern-rust-sdk
        version: ...
        config:
          enableWebsockets: true
```

Java generates WebSocket clients by default with no additional configuration.

## Generated SDK behavior

Given an AsyncAPI channel definition like:

**`asyncapi.yml`**

```yaml title="asyncapi.yml"
asyncapi: 3.0.0
info:
  title: Plant Monitoring Service
  version: 1.0.0
channels:
  plantUpdates:
    address: /plants/{plant_id}/updates
    parameters:
      plant_id:
        description: Plant identifier
    messages:
      SensorReading:
        $ref: '#/components/messages/SensorReading'
      PlantStatus:
        $ref: '#/components/messages/PlantStatus'
operations:
  sendReading:
    action: send
    channel:
      $ref: '#/channels/plantUpdates'
    messages:
      - $ref: '#/channels/plantUpdates/messages/SensorReading'
  receivePlantStatus:
    action: receive
    channel:
      $ref: '#/channels/plantUpdates'
    messages:
      - $ref: '#/channels/plantUpdates/messages/PlantStatus'
components:
  messages:
    SensorReading:
      payload:
        type: object
        properties:
          temperature:
            type: number
          humidity:
            type: number
    PlantStatus:
      payload:
        type: object
        properties:
          health:
            type: string
          needsWater:
            type: boolean
```

Fern generates the following client code.

Send methods are named `send` + the operation name from the spec. The `sendReading` operation produces `sendSendReading` in TypeScript, Python, Java, and Rust. C# uses overloaded `Send()` methods instead.

Receive handling varies by language:

* **TypeScript** — a generic `socket.on("message", handler)` callback
* **Python** — typed responses from `ws.recv()`
* **Java** — per-message-type handlers like `onPlantStatus(handler)`
* **C#** — typed `Event<T>` properties like `PlantStatus.Subscribe(handler)`
* **Rust** — a typed enum from `ws.recv()`

#### TypeScript

The TypeScript SDK generates a typed socket class with `send` methods, event handlers, and a `ReconnectingWebSocket` that automatically retries dropped connections.

```typescript
const client = new PlantClient({ token: "..." });

const socket = await client.plantUpdates.createPlantUpdatesConnection({
  plantId: "plant-123",
});

socket.on("open", () => {
  console.log("Connected");
});

socket.on("message", (message) => {
  // message is typed as PlantStatus
  console.log(message.health);
});

socket.on("error", (error) => {
  console.error(error);
});

socket.sendSendReading({ temperature: 22.5, humidity: 65 });

socket.close();
```

Detach a handler with `socket.off(event, handler)`. By default, registering a second handler for the same event replaces the first; set [`websocketHandlerMode: accumulate`](/learn/sdks/legacy/generators/typescript/configuration#websockethandlermode) in `generators.yml` to fire every registered handler in order.

```typescript
const logStatus = (message) => console.log(message.health);

socket.on("message", logStatus);
socket.off("message", logStatus);
```

The connection call accepts `reconnectAttempts` (default `30`, `0` disables reconnection) and a `shouldReconnect` predicate that receives the `CloseEvent` when the server closes the connection and decides whether to reconnect. The default reconnects on every server close code except `1000`. Return `false` to treat a server close as terminal, for example after the client has sent an end-of-stream message:

```typescript
let streamEnded = false;

const socket = await client.plantUpdates.createPlantUpdatesConnection({
  plantId: "plant-123",
  reconnectAttempts: 5,
  shouldReconnect: (event) => !streamEnded && event.code !== 1000,
});

streamEnded = true;
socket.sendSendReading({ temperature: 22.5, humidity: 65 });
```

`shouldReconnect` applies only to server-initiated closes. Connection errors and connection timeouts still fire `close` handlers but always retry up to `reconnectAttempts`, regardless of the predicate. `shouldReconnect` also isn't consulted after `socket.close()` or when the `abortSignal` aborts, and a predicate that throws is treated as terminal. Calling `socket.reconnect()` after retries are exhausted starts a new connection attempt.

#### Python

The Python SDK generates both sync (`contextmanager`) and async (`asynccontextmanager`) WebSocket clients.

**Sync:**

```python
with client.plant_updates.create_plant_updates_connection(
    plant_id="plant-123",
) as ws:
    ws.send_send_reading(SensorReading(temperature=22.5, humidity=65))
    response = ws.recv()  # typed as PlantStatus
    print(response)
```

**Async:**

```python
async with client.plant_updates.create_plant_updates_connection(
    plant_id="plant-123",
) as ws:
    await ws.send_send_reading(SensorReading(temperature=22.5, humidity=65))
    response = await ws.recv()
    print(response)
```

Both clients also support iteration and event-based listening:

```python
from my_sdk.core.events import EventType

# Iteration
with client.plant_updates.create_plant_updates_connection(
    plant_id="plant-123",
) as ws:
    for message in ws:
        print(message)

# Event-based
with client.plant_updates.create_plant_updates_connection(
    plant_id="plant-123",
) as ws:
    ws.on(EventType.MESSAGE, lambda msg: print(msg))
    ws.start_listening()
```

#### Java

The Java SDK generates a `PlantUpdatesWebSocketClient` with `CompletableFuture`-based async connect/send and typed message handlers via `Consumer<T>` callbacks.

```java
PlantClient client = PlantClient.builder()
    .apiKey("...")
    .build();

PlantUpdatesWebSocketClient ws = client.plantUpdates().plantUpdatesWebSocket("plant-123");

ws.onConnected(() -> System.out.println("Connected"));
ws.onPlantStatus(status -> System.out.println(status.getHealth()));
ws.onError(error -> error.printStackTrace());

ws.connect().join();
ws.sendSendReading(SensorReading.builder()
    .temperature(22.5)
    .humidity(65)
    .build()).join();

ws.disconnect();
```

#### C\#

The C# SDK generates a `PlantUpdatesApi` class implementing `IAsyncDisposable` with `Event<T>` subscriptions for typed message handling. The WebSocket client is created through the root client's factory method, which requires `BaseUrl` and channel parameters in the `Options` object.

```csharp
var client = new PlantClient();

var api = client.CreatePlantUpdatesApi(new PlantUpdatesApi.Options
{
    BaseUrl = "wss://api.example.com",
    PlantId = "plant-123",
});

api.Connected.Subscribe(() => Console.WriteLine("Connected"));
api.PlantStatus.Subscribe(status => Console.WriteLine(status.Health));

await api.ConnectAsync();
await api.Send(new SensorReading { Temperature = 22.5, Humidity = 65 });

await api.CloseAsync();
```

#### Rust

The Rust SDK generates an async client using `tokio` with typed send/receive methods and channel-based message streaming.

```rust
let mut ws = client.plant_updates
    .create_plant_updates_connection(
        "plant-123",
        &PlantUpdatesConnectOptions::default(),
    )
    .await?;

ws.send_send_reading(&SensorReading {
    temperature: Some(22.5),
    humidity: Some(65.0),
}).await?;

if let Some(Ok(message)) = ws.recv().await {
    println!("{:?}", message);
}

ws.close().await?;
```

## Authentication

In TypeScript, Python, Java, and Rust, WebSocket clients automatically inherit the authentication configured on the root client. Headers — including bearer tokens, API keys, and custom headers — are sent with the WebSocket handshake request.

```typescript
const client = new PlantClient({
  token: "your-bearer-token",
});

// Auth headers are forwarded to the WebSocket handshake
const socket = await client.plantUpdates.createPlantUpdatesConnection({
  plantId: "plant-123",
});
```

In C#, the WebSocket client is constructed with an `Options` object that includes the connection URL and channel parameters. Configure authentication through the `HttpInvoker` option or by setting headers on the underlying client.

## Defining WebSocket channels

WebSocket clients are generated from channel definitions in your [AsyncAPI specification](/learn/api-definitions/asyncapi/overview). For defining channels, messages, and operations, see [Publish/subscribe operations](/learn/api-definitions/asyncapi/channels/pubsub).