Skip to content
FeedbackDashboard
Generate SDKs
Configure

Configure WebSocket methods in your SDKs

Generate SDK methods for WebSocket endpoints that support full-duplex, bi-directional communication with typed client and server events.

WebSockets provide full-duplex, bi-directional communication between client and server over a persistent connection, enabling real-time data exchange after an initial HTTP handshake. Unlike SSE streaming, WebSockets allow both the client and server to send messages at any time.

OpenAPI can define the initial HTTP handshake endpoint and the message schemas, but cannot express which schemas are used for client and server messages in a WebSocket connection. Your Stainless config bridges that gap by specifying which schemas correspond to each direction of communication, enabling Stainless to generate fully typed code.

To define a WebSocket method, add a method with type: websocket to your Stainless config:

resources:
feed:
methods:
connect:
type: websocket
path: /feed
client_event_schema_uri: "#/components/schemas/ClientEvent"
server_event_schema_uri: "#/components/schemas/ServerEvent"

The client_event_schema_uri and server_event_schema_uri reference schemas defined in your OpenAPI spec. The server_event_schema_uri must be a discriminated union so the SDK can distinguish between different incoming event types at runtime. The client_event_schema_uri can be any schema, but using a discriminated union is recommended so that clients get typed event variants.

Here is an example of a client event schema defined as a discriminated union in OpenAPI:

components:
schemas:
ClientEvent:
oneOf:
- $ref: "#/components/schemas/SubscribeEvent"
- $ref: "#/components/schemas/SendMessageEvent"
discriminator:
propertyName: type
mapping:
subscribe: "#/components/schemas/SubscribeEvent"
send_message: "#/components/schemas/SendMessageEvent"
SubscribeEvent:
type: object
required: [type, channel]
properties:
type:
type: string
enum: [subscribe]
channel:
type: string
SendMessageEvent:
type: object
required: [type, channel, content]
properties:
type:
type: string
enum: [send_message]
channel:
type: string
content:
type: string

The discriminator field with propertyName tells Stainless (and OpenAPI tooling) which property distinguishes the union members. Each member schema must include that property with a unique constant value.

PropertyRequiredDescription
typeYesMust be websocket.
pathYesThe path to the WebSocket handshake endpoint. Supports path parameters, e.g. /feed/{channel_id}.
client_event_schema_uriYesA $ref-style URI pointing to the schema for messages the client can send. Must be a discriminated union.
server_event_schema_uriYesA $ref-style URI pointing to the schema for messages the server can send. Must be a discriminated union.
query_params_schema_uriNoA $ref-style URI pointing to the schema for query parameters used when initializing the connection.
event_name_separatorNoPython only. Separator character in event names (e.g. . for room.send_message). When set, generates typed nested helper methods in addition to the universal send method. See Event helper methods.

If your WebSocket endpoint accepts query parameters during the handshake, specify them with query_params_schema_uri:

openapi.yml
components:
schemas:
FeedConnectParams:
type: object
required: [token]
properties:
token:
type: string
description: Session token used to authenticate the connection.
channel:
type: string
description: Channel to subscribe to on connect.
stainless.yml
resources:
feed:
methods:
connect:
type: websocket
path: /feed
client_event_schema_uri: "#/components/schemas/ClientEvent"
server_event_schema_uri: "#/components/schemas/ServerEvent"
query_params_schema_uri: "#/components/schemas/FeedConnectParams"

The properties of the referenced schema become typed parameters on the generated method.

import { FeedWS } from 'acme/resources/feed/ws';
const ws = new FeedWS(client, {
// Required
token: 'user-session-token',
// Optional
channel: 'general',
});

Every connection exposes a send method that accepts any message matching your client_event_schema_uri schema:

connection.send({"type": "room.send_message", "channel": "general", "content": "hello"})

In Python, if your client event names share a consistent separator, set event_name_separator to generate typed helper methods that mirror your event hierarchy. For example, with events named like room.send_message:

resources:
chat:
methods:
connect:
type: websocket
path: /chat
client_event_schema_uri: "#/components/schemas/ClientEvent"
server_event_schema_uri: "#/components/schemas/ServerEvent"
event_name_separator: "."

The SDK generates a room namespace on the connection object:

# Equivalent to connection.send({"type": "room.send_message", ...})
connection.room.send_message(channel="general", content="hello")

The send method remains available regardless of whether event_name_separator is set.

The generated SDKs support reconnection with exponential backoff for recoverable connection failures. To enable reconnection, you must provide an onReconnecting (TypeScript) or on_reconnecting (Python) handler. This handler is required because the SDK cannot guarantee that reconnecting is safe in the general case — your server may require a new session ID, updated authentication tokens, or other state that must be refreshed before re-establishing the connection.

Pass a reconnect option with an onReconnecting handler when creating the WebSocket:

import { FeedWS } from 'acme/resources/feed/ws';
const ws = new FeedWS(client, { token: 'user-session-token' }, {
reconnect: {
maxRetries: 5,
initialDelay: 500,
maxDelay: 8000,
onReconnecting(event) {
console.log(`Reconnecting (attempt ${event.attempt}/${event.maxAttempts})...`);
// Optionally return { parameters: { ... } } to override query parameters
},
},
});

If reconnection is always safe for your API (for example, because the server handles session resumption transparently), you can provide a minimal handler:

const ws = new FeedWS(client, { token: 'user-session-token' }, {
reconnect: {
onReconnecting() {},
},
});

If reconnection is always safe for every consumer of your SDK, you can use custom code to add a default reconnection handler so that end users do not need to provide one themselves.

The SDK automatically reconnects on these close codes:

  • 1001 (Going Away)
  • 1005 (No Status)
  • 1006 (Abnormal Closure)
  • 1011 (Internal Error)
  • 1012 (Service Restart)
  • 1013 (Try Again Later)
  • 1015 (TLS Handshake Failure)

Messages sent during reconnection are held in an in-memory send queue (default 1 MB) and flushed once the new connection is established.

The example code below shows how your users can use WebSockets after you configure them and provide new builds of your SDKs.

The TypeScript SDK generates separate entry points for Node.js and browser environments.

Node.js (requires the ws package):

Event-based API:

import { FeedWS } from 'acme/resources/feed/ws';
const ws = new FeedWS(client, { token: 'user-session-token' });
// Listen for specific server event types
ws.on('message_received', (event) => {
console.log(`${event.channel}: ${event.content}`);
});
ws.on('error', (err) => {
console.error('WebSocket error:', err);
});
// Send typed client events
ws.send({ type: 'subscribe', channel: 'general' });
ws.send({ type: 'send_message', channel: 'general', content: 'hello' });
// Close when done
ws.close();

Async iterator (alternative to the event-based API):

for await (const event of ws) {
switch (event.type) {
case 'message':
console.log('Received:', event.message);
break;
case 'close':
console.log('Connection closed');
break;
}
}

The iterator yields lifecycle and message events until the connection closes. To shut down cleanly, call ws.close() from an event handler or external signal — the iterator will then complete.

Browser (uses the native WebSocket API):

import { FeedWS } from 'acme/resources/feed/ws-browser';
const ws = new FeedWS(client, { token: 'user-session-token' });

You can customize the pip extras group name for the WebSocket dependency:

targets:
python:
options:
websockets_extra_name: realtime

This changes the install command from pip install acme[websockets] to pip install acme[realtime].

  • Both client and server event schemas must be discriminated unions — the SDK uses the discriminator to route and type-check events
  • Use event_name_separator when event names have hierarchical structure (e.g. room.send_message) to generate ergonomic nested helper methods
  • Only one WebSocket method is allowed per resource — use subresources if you need multiple WebSocket endpoints
  • Define connection parameters in a separate schema when your handshake endpoint requires authentication tokens or session identifiers
  • Browser WebSocket clients (TypeScript only) support unauthenticated, query-parameter, and Sec-WebSocket-Protocol auth — Bearer tokens and other custom headers are not available in browser WebSocket connections