> This page is for SDKs, version Current (default).
> 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.

> **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#, Rust, Go, Ruby, PHP, and Swift.

## Enable WebSocket client generation

Set `generation.webSockets: true` in `sdk-config.yml`. Set it in the top-level `generation` block to enable WebSocket clients for every target, or in a target's `generation` block for one language. A target's value overrides the top-level value:

**`sdk-config.yml`**

```yaml title="sdk-config.yml"
generation:
  webSockets: true
```

Java generates WebSocket clients by default with no additional configuration; set `webSockets: false` to turn them off. Every other language generates them only when `webSockets` is `true`. With WebSocket generation off, the SDK still includes the message types.

## 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 message name from the spec. The `SensorReading` message produces `sendSensorReading` in TypeScript, Java, Go, PHP, and Swift, and `send_sensor_reading` in Python, Ruby, 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()`
* **Go** — per-message-type handlers like `OnPlantStatus(handler)`, then `Start()`
* **Ruby** — `recv`, `each`, or `on(...)` handlers with `start_listening`
* **PHP** — iterate the socket with `foreach`, or call `receive()`
* **Swift** — `for await event in socket.events`, with `.message(.plantStatus(...))`

#### 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.connect({
  plant_id: "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);
});

await socket.waitForOpen();
socket.sendSensorReading({ 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.connect({
  plant_id: "plant-123",
  reconnectAttempts: 5,
  shouldReconnect: (event) => !streamEnded && event.code !== 1000,
});

streamEnded = true;
await socket.waitForOpen();
socket.sendSensorReading({ 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.connect()` 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.connect(
    plant_id="plant-123",
) as ws:
    ws.send_sensor_reading(SensorReading(temperature=22.5, humidity=65))
    response = ws.recv()  # typed as PlantStatus
    print(response)
```

**Async:**

```python
async with client.plant_updates.connect(
    plant_id="plant-123",
) as ws:
    await ws.send_sensor_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.connect(
    plant_id="plant-123",
) as ws:
    for message in ws:
        print(message)

# Event-based
with client.plant_updates.connect(
    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()
    .token("...")
    .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.sendSensorReading(SensorReading.builder()
    .temperature(22.5)
    .humidity(65.0)
    .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. Reconnection is off by default; set `IsReconnectionEnabled = true` in `Options` to turn it on.

```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
    .connect("plant-123")
    .await?;

ws.send_sensor_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. In TypeScript, the handshake carries the root client's credentials when the auth scheme is declared under `api.auth` in `sdk-config.yml`.

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

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

In C#, the WebSocket client created by the root client's factory method sends the root client's credentials and `ClientOptions.AdditionalHeaders` on the handshake and on every reconnect. Per connection, `Options.AdditionalHeaders` replaces a header of the same name, and a `null` value removes it.

## 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).