Kalshi.Client.Websocket
1.0.0
dotnet add package Kalshi.Client.Websocket --version 1.0.0
NuGet\Install-Package Kalshi.Client.Websocket -Version 1.0.0
<PackageReference Include="Kalshi.Client.Websocket" Version="1.0.0" />
<PackageVersion Include="Kalshi.Client.Websocket" Version="1.0.0" />
<PackageReference Include="Kalshi.Client.Websocket" />
paket add Kalshi.Client.Websocket --version 1.0.0
#r "nuget: Kalshi.Client.Websocket, 1.0.0"
#:package Kalshi.Client.Websocket@1.0.0
#addin nuget:?package=Kalshi.Client.Websocket&version=1.0.0
#tool nuget:?package=Kalshi.Client.Websocket&version=1.0.0

Kalshi websocket API client
This is a C# implementation of the Kalshi websocket API v2:
Kalshi requires authenticated websocket connection headers for public and private websocket channels. The client defaults to Kalshi's dedicated Trade API websocket hosts:
- production:
wss://external-api-ws.kalshi.com/trade-api/ws/v2 - demo:
wss://external-api-ws.demo.kalshi.co/trade-api/ws/v2
The older shared hosts are exposed as KalshiValues.SharedTradeWebsocketApiUrl and KalshiValues.SharedDemoTradeWebsocketApiUrl.
License
Apache License 2.0
Features
- installation via NuGet (Kalshi.Client.Websocket)
- targets
netstandard2.0,netstandard2.1,net6.0,net7.0,net8.0,net9.0,net10.0 - built on Websocket.Client 5.5.0
- RSA-PSS authentication header helper for Kalshi websocket handshakes
- typed subscribe, unsubscribe, update-subscription, and list-subscriptions requests
- typed streams for public market data and private account channels
- decimal/integer/string-enum parsing for websocket values
- file communicator for replay/backtesting and data collection pipelines
- unit and integration tests, including replay data seeded from public Kalshi REST market/orderbook data
Usage
Connect and subscribe to public market data:
using var rsa = RSA.Create();
rsa.ImportFromPem(File.ReadAllText("kalshi-private-key.pem"));
var auth = KalshiAuthentication.FromRsaPrivateKey("api-key-id", rsa);
using var communicator = new KalshiWebsocketCommunicator(KalshiValues.TradeWebsocketApiUrl, auth);
using var client = new KalshiWebsocketClient(communicator);
client.Streams.OrderbookSnapshotStream.Subscribe(snapshot =>
{
Console.WriteLine($"Book {snapshot.Message.MarketTicker}: yes={snapshot.Message.Yes?.Length ?? 0}, no={snapshot.Message.No?.Length ?? 0}");
});
client.Streams.TickerStream.Subscribe(ticker =>
{
Console.WriteLine($"Ticker {ticker.Message.MarketTicker}: bid={ticker.Message.YesBid}, ask={ticker.Message.YesAsk}");
});
await communicator.Start();
client.Send(SubscribeRequest.Orderbook(1, "KXEXAMPLE-YES", sendInitialSnapshot: true));
client.Send(SubscribeRequest.Ticker(2, "KXEXAMPLE-YES"));
client.Send(SubscribeRequest.Trades(3, "KXEXAMPLE-YES"));
Update and unsubscribe:
client.Send(new UpdateSubscriptionRequest(
id: 4,
subscriptionId: 2,
action: KalshiSubscriptionAction.AddMarkets,
marketTickers: new[] { "KXEXAMPLE-NO" }));
client.Send(new UpdateSubscriptionRequest(
id: 5,
subscriptionIds: new[] { 2L },
action: KalshiSubscriptionAction.GetSnapshot,
marketTickers: new[] { "KXEXAMPLE-YES" }));
client.Send(new ListSubscriptionsRequest(6));
client.Send(new UnsubscribeRequest(7, new[] { 2L, 3L }));
Run the sample:
$env:KALSHI_API_KEY_ID = "your-key-id"
$env:KALSHI_PRIVATE_KEY_PATH = "C:\keys\kalshi-private-key.pem"
$env:KALSHI_MARKET_TICKER = "KXEXAMPLE-YES"
dotnet run --project test_integration/Kalshi.Client.Websocket.Sample
If the environment variables are not set, the sample replays the included public fixture.
Capture raw websocket messages for replay tests:
$env:KALSHI_API_KEY_ID = "your-key-id"
$env:KALSHI_PRIVATE_KEY_PATH = "C:\keys\kalshi-private-key.pem"
$env:KALSHI_MARKET_TICKER = "KXEXAMPLE-YES"
$env:KALSHI_CAPTURE_FILE = "artifacts\captures\kalshi-live.txt"
$env:KALSHI_CAPTURE_SECONDS = "30"
dotnet run --project test_integration/Kalshi.Client.Websocket.Sample --configuration Release
More examples:
API Coverage
Commands
| Command | Covered |
|---|---|
subscribe |
yes |
unsubscribe |
yes |
update_subscription |
yes |
list_subscriptions |
yes |
Public market data
| Type | Covered |
|---|---|
orderbook_snapshot |
yes |
orderbook_delta |
yes |
ticker |
yes |
trade |
yes |
market_lifecycle_v2 |
yes |
event_lifecycle |
yes |
multivariate_market_lifecycle |
yes |
multivariate_lookup |
yes |
Private/account data
| Type | Covered |
|---|---|
fill |
yes |
market_position |
yes |
user_order |
yes |
order_group_updates |
yes |
rfq_created |
yes |
rfq_deleted |
yes |
quote_created |
yes |
quote_accepted |
yes |
quote_executed |
yes |
Reconnecting
There is a built-in reconnection which invokes after 1 minute (default) of not receiving any messages from the server. It is possible to configure that timeout via communicator.ReconnectTimeout. There is also a stream ReconnectionHappened which sends information about the reconnection type.
You need to resubscribe after reconnection happens. Subscribe to communicator.ReconnectionHappened and send the same subscription request again from that handler.
Backtesting
The library is prepared for backtesting and data collection. The dependency between Client and Communicator is via abstraction IKalshiCommunicator. There are two communicator implementations:
KalshiWebsocketCommunicator- realtime communication with Kalshi websocket API v2KalshiFileCommunicator- simulated communication, raw data are loaded from files and streamed
Usage:
var communicator = new KalshiFileCommunicator
{
FileNames = new[] { "data/kalshi_public_replay.txt" },
Delimiter = ";;"
};
using var client = new KalshiWebsocketClient(communicator);
client.Streams.OrderbookSnapshotStream.Subscribe(snapshot =>
{
// do something with snapshots
});
await communicator.Start();
Multi-threading
Observables from Reactive Extensions are single threaded by default. Your code inside subscriptions is called synchronously as soon as a message comes from the websocket API. If your subscription handler takes a long time, it blocks the receiving method, buffers messages, and can eventually lose messages.
Handle messages on another thread when your processing is expensive:
client
.Streams
.OrderbookDeltaStream
.ObserveOn(TaskPoolScheduler.Default)
.Subscribe(delta => { /* process */ });
If you need parallel processing while preserving order for a stream, use synchronization:
private static readonly object Gate = new object();
client
.Streams
.OrderbookDeltaStream
.ObserveOn(TaskPoolScheduler.Default)
.Synchronize(Gate)
.Subscribe(delta => { /* process */ });
Pull Requests are welcome!
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 is compatible. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 is compatible. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Newtonsoft.Json (>= 13.0.4)
- System.Reactive (>= 6.1.0)
- Websocket.Client (>= 5.5.0)
-
.NETStandard 2.1
- Newtonsoft.Json (>= 13.0.4)
- System.Reactive (>= 6.1.0)
- Websocket.Client (>= 5.5.0)
-
net10.0
- Newtonsoft.Json (>= 13.0.4)
- System.Reactive (>= 6.1.0)
- Websocket.Client (>= 5.5.0)
-
net6.0
- Newtonsoft.Json (>= 13.0.4)
- System.Reactive (>= 6.1.0)
- Websocket.Client (>= 5.5.0)
-
net7.0
- Newtonsoft.Json (>= 13.0.4)
- System.Reactive (>= 6.1.0)
- Websocket.Client (>= 5.5.0)
-
net8.0
- Newtonsoft.Json (>= 13.0.4)
- System.Reactive (>= 6.1.0)
- Websocket.Client (>= 5.5.0)
-
net9.0
- Newtonsoft.Json (>= 13.0.4)
- System.Reactive (>= 6.1.0)
- Websocket.Client (>= 5.5.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 100 | 5/20/2026 |
Initial Kalshi websocket client with authenticated connection headers, typed subscription requests, typed market/private streams, file replay, examples, and tests.