Livekit.Server.Sdk.Dotnet
1.2.6
dotnet add package Livekit.Server.Sdk.Dotnet --version 1.2.6
NuGet\Install-Package Livekit.Server.Sdk.Dotnet -Version 1.2.6
<PackageReference Include="Livekit.Server.Sdk.Dotnet" Version="1.2.6" />
<PackageVersion Include="Livekit.Server.Sdk.Dotnet" Version="1.2.6" />
<PackageReference Include="Livekit.Server.Sdk.Dotnet" />
paket add Livekit.Server.Sdk.Dotnet --version 1.2.6
#r "nuget: Livekit.Server.Sdk.Dotnet, 1.2.6"
#:package Livekit.Server.Sdk.Dotnet@1.2.6
#addin nuget:?package=Livekit.Server.Sdk.Dotnet&version=1.2.6
#tool nuget:?package=Livekit.Server.Sdk.Dotnet&version=1.2.6
Livekit.Server.Sdk.Dotnet
.NET APIs to manage LiveKit Access Tokens, Webhook and Server APIs. Use it with a .NET backend. It is build with netstandard2.0 as target framework, so the SDK can be used in all versions of .NET.
This SDK does not provide real-time communication capabilities. If you want to add real-time audio, video and data features to your .NET server application, please check Livekit.Rtc.Dotnet instead.
Installation
dotnet add package Livekit.Server.Sdk.Dotnet
Usage
Creating Access Tokens
using Livekit.Server.Sdk.Dotnet;
var token = new AccessToken("yourkey", "yoursecret")
.WithIdentity("participant-identity")
.WithName("participant-name")
.WithGrants(new VideoGrants{ RoomJoin = true, Room = "room-name" })
.WithAttributes(new Dictionary<string, string> { { "mykey", "myvalue" } })
.WithTtl(TimeSpan.FromHours(1));
var jwt = token.ToJwt();
By default, a token expires after 6 hours. You may override this by calling method WithTtl in the AccessToken object, just as shown in the example above.
It's possible to customize the permissions of each participant. See more details at access tokens guide.
Room Service
RoomServiceClient is a Twirp-based client that provides management APIs to LiveKit. You can connect it to your LiveKit endpoint. See service apis for a list of available APIs.
using Livekit.Server.Sdk.Dotnet;
RoomServiceClient client = new RoomServiceClient(
"https://my.livekit.instance",
"yourkey",
"yoursecret"
);
// List rooms
var listRoomsRequest = new ListRoomsRequest();
var listRoomsResponse = await client.ListRooms(listRoomsRequest);
Room[] rooms = listRoomsResponse.Rooms.ToArray();
// List participants
var listParticipantsRequest = new ListParticipantsRequest { Room = "room-name" };
var listParticipantsResponse = await client.ListParticipants(listParticipantsRequest);
ParticipantInfo[] participants = listParticipantsResponse.Participants.ToArray();
// Mute published track
var mutePublishedTrackRequest = new MuteRoomTrackRequest
{
Room = "room-name",
Identity = "participant-identity",
TrackSid = "track-sid",
Muted = true,
};
await client.MutePublishedTrack(mutePublishedTrackRequest);
// Remove participant
var removeParticipantRequest = new RoomParticipantIdentity
{
Room = "room-name",
Identity = "participant-identity",
};
await client.RemoveParticipant(removeParticipantRequest);
// Delete room
var deleteRoomRequest = new DeleteRoomRequest { Room = "room-name" };
await client.DeleteRoom(deleteRoomRequest);
// Send data to room
var sendDataRequest = new SendDataRequest
{
Room = "room-name",
Data = ByteString.CopyFromUtf8("test-data"),
Kind = DataPacket.Types.Kind.Reliable,
};
await client.SendData(sendDataRequest);
Egress Service
EgressServiceClient is a .NET client to EgressService. Refer to docs for more usage examples.
using Livekit.Server.Sdk.Dotnet;
EgressServiceClient client = new EgressServiceClient(
"https://my.livekit.instance",
"yourkey",
"yoursecret"
);
// Starting a room composite to S3
var s3request = new RoomCompositeEgressRequest { RoomName = "room-name" };
s3request.FileOutputs.Add(
new EncodedFileOutput
{
FileType = EncodedFileType.Mp4,
Filepath = "my-recording.mp4",
S3 = new S3Upload
{
AccessKey = "aws-access-key",
Secret = "aws-access-secret",
Region = "bucket-region",
Bucket = "my-bucket",
},
}
);
EgressInfo s3Egress = await client.StartRoomCompositeEgress(s3request);
// Starting a track composite to RTMP
var rtmpRequest = new TrackCompositeEgressRequest
{
RoomName = "room-name",
AudioTrackId = "TR_XXXXXXXXXXXX",
VideoTrackId = "TR_XXXXXXXXXXXX",
StreamOutputs =
{
new StreamOutput
{
Protocol = StreamProtocol.Rtmp,
Urls = { "rtmp://url1", "rtmps://url2" },
},
},
};
EgressInfo rtmpEgress = await client.StartTrackCompositeEgress(rtmpRequest);
// Starting any type of egress with the unified StartEgress API.
// The source and outputs of the request determine the egress type.
var request = new StartEgressRequest
{
RoomName = "room-name",
Template = new TemplateSource { Layout = "grid" },
Outputs =
{
new Output
{
File = new FileOutput { FileType = EncodedFileType.Mp4, Filepath = "my-recording.mp4" },
},
},
};
EgressInfo egress = await client.StartEgress(request);
Ingress Service
IngressServiceClient is a .NET client to IngressService. Refer to docs for more usage examples.
using Livekit.Server.Sdk.Dotnet;
IngressServiceClient client = new IngressServiceClient(
"https://my.livekit.instance",
"yourkey",
"yoursecret"
);
// Publish an ingress participant from URL
var urlRequest = new CreateIngressRequest
{
RoomName = "room-name",
ParticipantIdentity = "ingress-participant",
ParticipantMetadata = "ingress-metadata",
ParticipantName = "ingress-name",
InputType = IngressInput.UrlInput,
Url = "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
};
IngressInfo ingress = await client.CreateIngress(urlRequest);
// Publish an ingress participant from RTMP
var rtmpRequest = new CreateIngressRequest
{
RoomName = "room-name",
ParticipantIdentity = "ingress-participant",
ParticipantMetadata = "ingress-metadata",
ParticipantName = "ingress-name",
InputType = IngressInput.RtmpInput,
Video = new IngressVideoOptions
{
Preset = IngressVideoEncodingPreset.H2641080P30Fps3LayersHighMotion,
},
};
ingress = await client.CreateIngress(rtmpRequest);
// List ingresses
var response = await client.ListIngress(new ListIngressRequest());
IngressInfo[] ingresses = response.Items.ToArray();
// Delete an ingress
await client.DeleteIngress(new DeleteIngressRequest { IngressId = ingress.IngressId });
SIP Service
SipServiceClient is a .NET client to SipService. Refer to docs for more usage examples.
using Livekit.Server.Sdk.Dotnet;
SipServiceClient client = new SipServiceClient(
"https://my.livekit.instance",
"yourkey",
"yoursecret"
);
// Create SIP inbound trunk
var inRequest = new CreateSIPInboundTrunkRequest
{
Trunk = new SIPInboundTrunkInfo
{
Name = "Demo inbound trunk",
Numbers = { "+1234567890" },
AllowedNumbers = { "+11111111", "+22222222" },
AllowedAddresses = { "1.1.1.0/24" },
},
};
SIPInboundTrunkInfo inboundTrunk = await client.CreateSIPInboundTrunk(inRequest);
// Create SIP outbound trunk
var outRequest = new CreateSIPOutboundTrunkRequest
{
Trunk = new SIPOutboundTrunkInfo
{
Name = "Demo outbound trunk",
Address = "my-test-trunk.com",
Numbers = { "+1234567890" },
AuthUsername = "username",
AuthPassword = "password",
},
};
SIPOutboundTrunkInfo outboundTrunk = await client.CreateSIPOutboundTrunk(outRequest);
// Create dispatch rule
var ruleRequest = new CreateSIPDispatchRuleRequest
{
Name = "Demo dispatch rule",
Metadata = "Demo dispatch rule metadata",
Rule = new SIPDispatchRule
{
DispatchRuleDirect = new SIPDispatchRuleDirect { RoomName = "room-name", Pin = "1234" },
},
};
SIPDispatchRuleInfo dispatchRule = await client.CreateSIPDispatchRule(ruleRequest);
Agent Dispatch Service
AgentDispatchServiceClient is a .NET client to AgentDispatchService. Refer to docs for more usage examples.
using Livekit.Server.Sdk.Dotnet;
AgentDispatchServiceClient agentDispatchClient = new AgentDispatchServiceClient(
"https://my.livekit.instance",
"yourkey",
"yoursecret"
);
// Dispatch an agent
var dispatchAgentRequest = new CreateAgentDispatchRequest
{
AgentName = "agent-name",
Room = "room-name",
Metadata = "my-job-metadata",
};
AgentDispatch agentDispatch = await agentDispatchClient.CreateDispatch(dispatchAgentRequest);
// Get an agent dispatch by its ID (null if it does not exist)
AgentDispatch foundAgentDispatch = await agentDispatchClient.GetDispatch(agentDispatch.Id, "room-name");
// List agent dispatches
var listAgentDispatchesRequest = new ListAgentDispatchRequest { Room = "room-name" };
ListAgentDispatchResponse agentDispatches = await agentDispatchClient.ListDispatch(listAgentDispatchesRequest);
// Delete an agent dispatch
var deleteAgentDispatchRequest = new DeleteAgentDispatchRequest
{
DispatchId = agentDispatch.Id,
Room = "room-name",
};
AgentDispatch deletedAgentDispatch = await agentDispatchClient.DeleteDispatch(deleteAgentDispatchRequest);
Connector Service
ConnectorServiceClient is a .NET client to the Connector service, which connects WhatsApp and Twilio calls to LiveKit rooms. It is only available in LiveKit Cloud. Refer to docs for more usage examples.
using Livekit.Server.Sdk.Dotnet;
ConnectorServiceClient connectorClient = new ConnectorServiceClient(
"https://my.livekit.instance",
"yourkey",
"yoursecret"
);
// Initiate an outbound WhatsApp call
var dialRequest = new DialWhatsAppCallRequest
{
WhatsappPhoneNumberId = "whatsapp-phone-number-id",
WhatsappToPhoneNumber = "+1234567890",
WhatsappApiKey = "whatsapp-api-key",
WhatsappCloudApiVersion = "23.0",
RoomName = "room-name",
};
DialWhatsAppCallResponse dialResponse = await connectorClient.DialWhatsAppCall(dialRequest);
// Disconnect the WhatsApp call
await connectorClient.DisconnectWhatsAppCall(
new DisconnectWhatsAppCallRequest
{
WhatsappCallId = dialResponse.WhatsappCallId,
WhatsappApiKey = "whatsapp-api-key",
}
);
// Connect a Twilio call to a room. Returns the WebSocket URL for the Twilio media stream
var twilioRequest = new ConnectTwilioCallRequest
{
TwilioCallDirection = ConnectTwilioCallRequest.Types.TwilioCallDirection.Inbound,
RoomName = "room-name",
};
ConnectTwilioCallResponse twilioResponse = await connectorClient.ConnectTwilioCall(twilioRequest);
Receiving Webhooks
The .NET SDK also provides helper functions to decode and verify webhook callbacks. While verification is optional, it ensures the authenticity of the message. See webhooks guide for details.
LiveKit POSTs to webhook endpoints with Content-Type: application/webhook+json. Please ensure your server is able to receive POST body with that MIME.
using Livekit.Server.Sdk.Dotnet;
var webhookReceiver = new WebhookReceiver("yourkey", "yoursecret");
// In order to use the validator, WebhookReceiver must have access to the raw POSTed string
// This example uses "ASP.NET Core Web SDK" to handle the webhook request from LiveKit Server
// (https://learn.microsoft.com/en-us/aspnet/core/razor-pages/web-sdk?view=aspnetcore-8.0)
app.MapPost("/webhook-endpoint", async (HttpRequest request) => {
var body = new StreamReader(request.Body);
string postData = await body.ReadToEndAsync();
var authHeader = request.Headers["Authorization"].First();
WebhookEvent webhookEvent = webhookReceiver.Receive(postData, authHeader);
// Here you have available the WebhookEvent object
Console.Out.WriteLine(webhookEvent);
return Results.Ok();
});
Environment Variables
You may store credentials in environment variables. If api-key or api-secret is not passed in when creating an AccessToken, TokenVerifier, WebhookReceiver, RoomServiceClient (or any other service client) the values in the following env vars will be used:
LIVEKIT_API_KEYLIVEKIT_API_SECRET
var token = new AccessToken().WithIdentity("identity").WithGrants(new VideoGrants { RoomJoin = true, Room = "room" });
var roomService = new RoomServiceClient("https://your-livekit-server");
Using a custom HttpClient
You can provide a custom HttpClient when creating any of the service clients. This allows you to customize settings such as timeouts, headers, or proxy configurations.
The SDK never modifies the HttpClient you pass: it does not set its BaseAddress or its default headers. The LiveKit host, the bearer token signed for each call and the SDK user agent are set on each request instead. The same instance (for example one obtained from IHttpClientFactory) can therefore be shared between several service clients, even for different LiveKit servers, and with the rest of your application, and it may already have sent requests.
using System;
using System.Net;
using System.Net.Http;
using Livekit.Server.Sdk.Dotnet;
// Custom proxy
var handler = new HttpClientHandler
{
Proxy = new WebProxy("http://myproxy.example.com:8080"),
UseProxy = true
};
// Custom timeout and custom header
var httpClient = new HttpClient(handler)
{
Timeout = TimeSpan.FromSeconds(10)
};
httpClient.DefaultRequestHeaders.Add("X-Custom-Header", "my-value");
// Pass your custom HttpClient to the service client
var roomService = new RoomServiceClient(
"https://your-livekit-server",
"your-api-key",
"your-api-secret",
httpClient
);
Handling errors
Errors returned by the LiveKit server are raised as TwirpException (a subclass of the generated Twirp.Exception) with the Twirp error code, the HTTP status and the meta map of the error:
try
{
await roomService.DeleteRoom(new DeleteRoomRequest { Room = "missing" });
}
catch (TwirpException e)
{
Console.WriteLine($"{e.Type} ({e.Status}): {e.Message}");
if (e.Meta.TryGetValue("sip_status_code", out var sipStatus)) { /* SIP calls */ }
}
Example app
At LivekitApi.Example you can find a sample application using livekit-server-sdk-dotnet.
For developers of the SDK
Clone repository
Make sure to clone with submodule option:
git clone --recurse-submodules https://github.com/pabloFuente/livekit-server-sdk-dotnet.git
Compile
Pre-requisites:
- .NET 10.0 SDK (
global.jsonpins it; the package itself targetsnetstandard2.0) - protoc 36.2 (the version that reproduces the committed generated code)
go install github.com/seanpfeifer/twirp-gen/cmd/protoc-gen-twirpcs@v1.1.0
Generate proto files:
./generate_proto.sh
Build the SDK:
dotnet build
Run tests
Run all tests:
dotnet test LivekitApi.Tests
Run unit tests:
dotnet test LivekitApi.Tests --filter "Category=Unit"
Run integration tests (they automatically launch necessary services as Docker containers with Testcontainers):
dotnet test LivekitApi.Tests --filter "Category=Integration"
Perform release
- Create a commit like this for the new version.
- Make sure to run script
./build_local.shto properly format all files with csharpier. Commit any changes in the repo after the script finishes. - Create a new release in GitHub with:
- Release title
api-X.Y.Z - A new tag
api-X.Y.Z - Description (change
A.B.Cwith the proper version of livekit/protocol):Update livekit/protocol to [vA.B.C](https://github.com/livekit/protocol/releases/tag/%40livekit%2Fprotocol%40A.B.C)
- Release title
The api- prefix in the tag is mandatory for the publish workflow to identify that it is a release for package Livekit.Server.Sdk.Dotnet specifically.
After creating the release, workflow publish.yml will automatically publish the new version to NuGet and will perform the necessary post-release tasks.
GitHub Actions
- Tests are automatically run in GitHub Actions after each commit, thanks to this workflow.
- A new version is published to NuGet after a new release with a tag with prefix
api-is created in GitHub, thanks to this workflow.
Install as a local NuGet package
It is necessary the NuGet tool.
First of all, configure the local NuGet repository in the NuGet configuration file. This is by default %appdata%\NuGet\NuGet.Config in Windows and ~/.nuget/NuGet/NuGet.Config Mac/Linux. Simply add line <add key="Local" value="/home/YOUR_USER/.private_nuget" /> if it does not already exist:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
<add key="Local" value="/home/YOUR_USER/.private_nuget" />
</packageSources>
</configuration>
In this way we are configuring our local NuGet repository in the ~/.private_nuget directory.
Then pack and install the package in the local repository. Run these commands at the root of the project livekit-server-sdk-dotnet:
dotnet pack -c Debug -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg
# Delete existing previous version just in case
nuget delete Livekit.Server.Sdk.Dotnet 1.0.0 -Source ~/.private_nuget/ -np
# Install the package in the local NuGet repository
nuget add "$PWD"/bin/Debug/Livekit.Server.Sdk.Dotnet.1.0.0.nupkg -Source ~/.private_nuget/
Once the NuGet package is installed in the local repository, you can add it to any local dotnet project with:
# Run this command at the root of the project where you want to add the NuGet package
dotnet add package Livekit.Server.Sdk.Dotnet
You can reset the local repository at any time with these commands:
dotnet nuget locals all --clear
dotnet restore
Note: all this process is automated in the
build_local.shscript. Tested in Unix systems.
AI coding agents
CLAUDE.md briefs AI coding agents on this repository. For this package the relevant tool is the /sync-protocol Claude Code skill (SKILL.md), which carries out the livekit/protocol upgrade end to end:
Start from a clean working tree with the tools listed in Compile installed, and Docker running if the integration tests should run too.
Open Claude Code at the repository root and run the skill with the target version, or without one to take the latest
@livekit/protocolrelease:/sync-protocol 1.54.0The skill moves the submodule, reads the upstream changelog and the proto diff, regenerates
LivekitApi/protowith./generate_proto.sh, fixes the build and compares the proto RPCs with the ones the*ServiceClientclasses wrap. It then stops at a checkpoint listing the relevant upstream changes, the proto files it added togenerate_proto.sh(and the candidates it left out), the new RPCs it proposes to wrap with the client each one belongs to, and the breaking changes found. Strike what you do not want and confirm.It wraps the confirmed RPCs with the grants the Node and Python SDKs use, adds the unit tests (route and grants), an integration test when the OSS server supports the RPC, the README section and the changelog bullets under
## Unreleased. Breaking changes forced by the protocol are recorded inCompatibilitySuppressions.xmland explained in the changelog underBreaking changes coming from livekit/protocol.It builds and checks the API reference, then ends with the test results, the open decisions and the suggested commits, and never commits. Review the diff, commit as
Update livekit/protocol to vX.Y.Z(the submodule pointer),Regenerate proto C# files(generate_proto.shandLivekitApi/proto) andAdapt LivekitApi to livekit/protocol vX.Y.Z(everything else), and follow Perform release when ready.
The sync-protocol workflow runs the same skill unattended: every day it looks for a GA release of livekit/protocol newer than the submodule and, when it finds one, opens a pull request with those three commits and the skill's report as description. Instead of stopping at the checkpoint, it wraps the new RPCs that the Node or Python SDK wraps and lists the rest as open decisions. Its header lists the secrets and repository settings it needs, and it can be started by hand from the Actions tab to choose the Claude model and effort level.
The skill's scripts also work on their own, from the repository root:
# .proto files in the submodule vs. the ones generate_proto.sh compiles
python3 .claude/skills/sync-protocol/scripts/proto_inventory.py
# proto RPCs vs. generated Twirp routes vs. RPCs wrapped by a *ServiceClient
python3 .claude/skills/sync-protocol/scripts/api_coverage.py
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. 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 was computed. 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 was computed. 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 was computed. 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 was computed. 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 was computed. |
| .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
- Google.Protobuf (>= 3.36.2)
- Newtonsoft.Json (>= 13.0.4)
- System.IdentityModel.Tokens.Jwt (>= 8.23.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.2.6 | 158 | 11/5/2026 |
| 1.2.5 | 2,228 | 11/2/2026 |
| 1.2.4 | 120 | 11/1/2026 |
| 1.2.3 | 46,002 | 8/27/2026 |
| 1.2.2 | 128,299 | 5/24/2026 |
| 1.2.1 | 45,937 | 3/29/2026 |
| 1.2.0 | 86,889 | 1/17/2026 |
| 1.1.0 | 15,535 | 11/15/2025 |
| 1.0.14 | 1,765 | 11/8/2025 |
| 1.0.13 | 4,906 | 10/12/2025 |
| 1.0.12 | 3,647 | 9/18/2025 |
| 1.0.11 | 20,321 | 8/18/2025 |
| 1.0.10 | 9,675 | 6/16/2025 |
| 1.0.9 | 8,625 | 5/27/2025 |
| 1.0.8 | 7,959 | 5/21/2025 |
| 1.0.7 | 21,505 | 2/25/2025 |
| 1.0.6 | 616 | 2/17/2025 |
| 1.0.5 | 3,482 | 1/28/2025 |
| 1.0.4 | 736 | 1/26/2025 |
| 1.0.3 | 3,563 | 12/3/2024 |
# Changelog (Release Notes)
## 1.2.6
- Update livekit/protocol to [v1.53.2](https://github.com/livekit/protocol/releases/tag/%40livekit%2Fprotocol%401.53.2) [PR #147](https://github.com/pabloFuente/livekit-server-sdk-dotnet/pull/147)
- New `Dialtone` property (enum `SIPDialtone`) on `CreateSIPParticipantRequest` and `TransferSIPParticipantRequest`, to choose the dial tone that `SipServiceClient.CreateSIPParticipant` and `TransferSIPParticipant` play while the call is placed or transferred: `Disabled`, `Etsi` (the tone `PlayDialtone` played), `Auto` (chosen by the country of the called number), `Us`, `Uk`, `Jp` or `Au`. `PlayDialtone` is now `[Obsolete]` on both requests, so code that sets it gets warning `CS0612` (an error with `TreatWarningsAsErrors`). It still works: when `Dialtone` is not set, `PlayDialtone = true` means `Dialtone = SIPDialtone.Etsi`. [PR #147](https://github.com/pabloFuente/livekit-server-sdk-dotnet/pull/147)
- New `SIPMediaConfig.DropAudioDuringDtmf`, settable on the `Media` of SIP trunks, dispatch rules and `CreateSIPParticipantRequest`, to stop sending audio while DTMF digits are sent. [PR #147](https://github.com/pabloFuente/livekit-server-sdk-dotnet/pull/147)
- New `AgentDispatchState.Trigger` (enum `AgentDispatchTrigger`) in the `AgentDispatch.State` returned by `AgentDispatchServiceClient`, telling what created the dispatch: a participant joining (`AdtJoin`), `RoomServiceClient.CreateRoom` (`AdtRoomApi`) or `AgentDispatchServiceClient.CreateDispatch` (`AdtDispatchApi`). New timestamps in Unix nanoseconds on the agent jobs listed in that state and sent in the `agent_job_*` webhooks: `Job.CreatedAt`, `JobState.AcceptedAt` and `JobState.AssignedAt`. [PR #147](https://github.com/pabloFuente/livekit-server-sdk-dotnet/pull/147)
- Breaking changes coming from livekit/protocol ([PR #147](https://github.com/pabloFuente/livekit-server-sdk-dotnet/pull/147)):
- `NodeStats.ForwardSchedulingDelay` and its constant `NodeStats.ForwardSchedulingDelayFieldNumber` have been removed. The field number now carries `NodeStats.ForwardProcessingPerWrite`, the mean forwarding work per write to a subscriber in nanoseconds, which measures something else. `NodeStats` describes LiveKit server nodes and is not returned by any API of this SDK.
- XML documentation comments for every public member, with parameter and return value descriptions on every method, for IntelliSense and for the new [API reference](https://pablofuente.github.io/livekit-server-sdk-dotnet/) generated with DocFX. [commit f5660c2](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/f5660c2f0225936951568c0c3ab403175521063a)
- Fix wrong or incomplete XML documentation: `SipServiceClient.TransferSIPParticipant` said it moved the participant to another room, while it transfers the call to a phone number or SIP URI; `TokenVerifier.Verify`, `WebhookReceiver.Receive` and the constructors of `AccessToken`, `TokenVerifier` and `WebhookReceiver` now list the exceptions they actually throw. `AccessToken`, `RoomServiceClient` and `WebhookReceiver` gain usage examples, and the API reference groups the clients, tokens and webhooks in its sidebar and no longer lists the protobuf plumbing members of the generated messages.
## 1.2.5
- Fix concurrent calls on the same service client, or on service clients sharing one `HttpClient`, sometimes being sent with another call's token and failing with a `Twirp.Exception` (`Permission_Denied` or `Unauthenticated`): the bearer token was written to the shared `HttpClient.DefaultRequestHeaders` right before each request. The host, the token and the user agent now travel with each request, so a caller-supplied `HttpClient` is never modified anymore: it can be shared between service clients (even for different servers) and with the rest of the application, and it may already have sent requests, which used to make the constructor throw `InvalidOperationException`. See [Using a custom HttpClient](https://github.com/pabloFuente/livekit-server-sdk-dotnet/blob/main/LivekitApi/README.md#using-a-custom-httpclient). [commit e68fa9a](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/e68fa9a914eed9095decfb635075d9081e25b5a6)
- Fix `new AccessToken()`, `new TokenVerifier()` and `new WebhookReceiver()` throwing `ArgumentException: apiKey and apiSecret must be set` even with `LIVEKIT_API_KEY` and `LIVEKIT_API_SECRET` set: the constructors validated their arguments instead of the resolved values. The service clients now honor the same variables when `apiKey` and `apiSecret` are omitted, as the README already stated. See [Environment Variables](https://github.com/pabloFuente/livekit-server-sdk-dotnet/blob/main/LivekitApi/README.md#environment-variables). [commit e68fa9a](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/e68fa9a914eed9095decfb635075d9081e25b5a6)
- Fix tokens built on a thread whose culture uses a decimal comma (`es-ES`, `de-DE`, `fr-FR`...) carrying a malformed `exp` or `nbf` claim (formatted as `1762000000,123`) when `WithExpiration`, `WithNotBefore` or a fractional `WithTtl` was used, and under Turkish cultures `WithKind(Kind.Ingress)` and the `IngressAdmin` grant being written with a dotless `ı` and ignored by the server. Time claims are now whole seconds, as in the other LiveKit SDKs, and all conversions are culture-invariant. [commit e68fa9a](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/e68fa9a914eed9095decfb635075d9081e25b5a6)
- Fix `AccessToken.ToJwt()` throwing `NullReferenceException` when `WithName`, `WithMetadata` or `WithSha256` received `null`. The optional claims (`name`, `metadata`, `sha256`, `kind`, `roomConfig`) are now only written when set, as the other LiveKit SDKs do. [commit e68fa9a](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/e68fa9a914eed9095decfb635075d9081e25b5a6)
- Fix `WebhookReceiver.Receive` failing with `InvalidProtocolBufferException: Unknown field` for every webhook as soon as the server sends a field the compiled protocol does not know (for example after a LiveKit upgrade): the `ignoreUnknownFields` parameter had no effect. It is honored now (unknown fields are ignored by default). New overloads: `Receive(byte[] body, ...)` to hash the raw bytes the server hashed, and `WebhookReceiver(apiKey, apiSecret, TimeSpan? leeway)` to set the clock tolerance. [commit e68fa9a](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/e68fa9a914eed9095decfb635075d9081e25b5a6)
- Fix `TokenVerifier.Verify` (and so `WebhookReceiver`) throwing `JsonSerializationException` for tokens whose `roomConfig` contains an enum (an egress preset, a file type...): the claim is written with the protobuf JSON formatter and was read back with Newtonsoft. `Verify` also no longer throws for tokens carrying a JSON array claim, and only accepts HS256 signatures, like the Python SDK. [commit e68fa9a](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/e68fa9a914eed9095decfb635075d9081e25b5a6)
- Errors returned by the server are now raised as `TwirpException`, a subclass of the generated `Twirp.Exception` (existing `catch` blocks keep working; a test asserting the exact type with xUnit's `Assert.ThrowsAsync<Twirp.Exception>` needs `Assert.ThrowsAnyAsync`) that parses the Twirp error properly (messages with quotes used to be truncated) and exposes the HTTP `Status` and the `Meta` map, for example `sip_status_code` for SIP calls. See [Handling errors](https://github.com/pabloFuente/livekit-server-sdk-dotnet/blob/main/LivekitApi/README.md#handling-errors). [commit e68fa9a](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/e68fa9a914eed9095decfb635075d9081e25b5a6)
- Service clients accept `ws://` and `wss://` hosts (the usual `LIVEKIT_URL` value) and convert them to `http`/`https`, as the Node and Python SDKs do. Clients created without an `HttpClient` now share one connection pool instead of each opening its own, and the user agent is `livekit-server-sdk-dotnet/<version>`, appended after the user agent of a caller-supplied `HttpClient` instead of replacing it. [commit e68fa9a](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/e68fa9a914eed9095decfb635075d9081e25b5a6)
- New grants, reaching parity with the Node and Python SDKs: `VideoGrants.CanManageAgentSession`, `AccessToken.WithInferenceGrants(InferenceGrants)` and `AccessToken.WithObservabilityGrants(ObservabilityGrants)` (read back by `TokenVerifier.Verify` as `ClaimsModel.Inference` and `ClaimsModel.Observability`). Empty grant values (`room: ""`, `canPublishSources: []`...) are no longer written to the token, as in the other SDKs. New `AccessToken.WithExpiration(DateTimeOffset)` and `WithNotBefore(DateTimeOffset)` overloads, since a `DateTime` of kind `Unspecified` is read as local time. [commit e68fa9a](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/e68fa9a914eed9095decfb635075d9081e25b5a6)
- All service client methods now await with `ConfigureAwait(false)`, so callers that block on the returned task from a thread with a `SynchronizationContext` (UI threads, classic ASP.NET) no longer deadlock. [commit e68fa9a](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/e68fa9a914eed9095decfb635075d9081e25b5a6)
## 1.2.4
- Update livekit/protocol to [v1.53.1](https://github.com/livekit/protocol/releases/tag/%40livekit%2Fprotocol%401.53.1) ([commit 032d6c0](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/032d6c0ebe81b49ad44f6b421b5af409b4dd68e7), [commit 1c623d5](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/1c623d53d701aae5520d24dfb24077bb8ac57ee2))
- New `ConnectorServiceClient` to connect [WhatsApp and Twilio](https://docs.livekit.io/telephony/connectors/) calls to LiveKit rooms (LiveKit Cloud only), with methods `DialWhatsAppCall`, `AcceptWhatsAppCall`, `ConnectWhatsAppCall`, `DisconnectWhatsAppCall` and `ConnectTwilioCall`. See [Connector Service](https://github.com/pabloFuente/livekit-server-sdk-dotnet/blob/main/LivekitApi/README.md#connector-service). [commit 40e46b7](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/40e46b72e6bae652fc3c27c6ed167c1c0a5ab131)
- New method `EgressServiceClient.StartEgress` to start any type of egress with the unified egress API. [commit 40e46b7](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/40e46b72e6bae652fc3c27c6ed167c1c0a5ab131)
- New method `AgentDispatchServiceClient.GetDispatch` to get an agent dispatch by its ID. [commit 40e46b7](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/40e46b72e6bae652fc3c27c6ed167c1c0a5ab131)
- Fix base URLs with a subpath (e.g. `https://my.server.com/livekit`) losing the subpath when they did not end with `/`: all requests were sent to `https://my.server.com/twirp/...`. `BaseService` now appends the trailing slash itself, so the subpath is kept either way. [commit 51235a3](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/51235a3e8388be968db145d058aff62be6d79146)
- Breaking changes coming from livekit/protocol ([commit 1c623d5](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/1c623d53d701aae5520d24dfb24077bb8ac57ee2)):
- `SipServiceClient.TransferSIPParticipant` now returns a `TransferSIPParticipantResponse` with the outcome of the transfer (`TransferId`, `Status`, `Reason` and `SipStatus`), instead of `Google.Protobuf.WellKnownTypes.Empty`.
- Type `UpdateEgressRequest` has been removed. It was not used by any API.
- The value of enum member `DataTrackExtensionID.DteiParticipantSid` changed from `1` to `255`.
## 1.2.3
- Update livekit/protocol to [v1.50.4](https://github.com/livekit/protocol/releases/tag/%40livekit%2Fprotocol%401.50.4) ([commit c68108a](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/c68108aba7b95a11581c936d5cd8eb565e4dfd73), [commit ca1447c](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/ca1447c851f5aa5cc46391ba58dac91288c61a2c))
## 1.2.2
- Update livekit/protocol to [v1.46.0](https://github.com/livekit/protocol/releases/tag/%40livekit%2Fprotocol%401.46.0) ([commit 57b9b63](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/57b9b63e6cead508502189e831a7d812c6cc2bbe), [commit 017e93a](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/017e93a64f81382ddc1d8459d5997938e44007df))
## 1.2.1
- Update livekit/protocol to [v1.45.1](https://github.com/livekit/protocol/releases/tag/%40livekit%2Fprotocol%401.45.1) ([commit b6f0054](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/b6f00545ef4f9c87f4b6e1915f140d7e0f6e512d))
## 1.2.0
- Update livekit/protocol to [v1.44.0](https://github.com/livekit/protocol/releases/tag/%40livekit%2Fprotocol%401.44.0) ([commit 4c00774](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/4c00774c1d5abf129ff7b3b6549661a1847e4926), [commit 4234473](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/4234473916df326ff80426251845472a1e6fc652))
- Support for [Phone Number APIs](https://docs.livekit.io/reference/telephony/phone-numbers-api/), [custom Token Sources](https://docs.livekit.io/frontends/authentication/tokens/custom/#use-a-custom-tokensource) and **Connectors for WhatsApp and Twilio**. [commit 4234473](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/4234473916df326ff80426251845472a1e6fc652)
## 1.1.0
- Allow configuring a custom HttpClient (of package System.Net.Http) when creating any service client. This allows setting custom headers, proxy configuration, timeouts, or other HttpClient settings. See [Using a custom HttpClient](https://github.com/pabloFuente/livekit-server-sdk-dotnet/blob/d522cedc64d6b0ec873a9fbecd6189a512ae0a10/README.md#using-a-custom-httpclient). [commit bbb94fe](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/bbb94fe3b59ffa2c6ccd091c35056b9557a77cd3)
## 1.0.14
- Update livekit/protocol to [v1.43.0](https://github.com/livekit/protocol/releases/tag/%40livekit%2Fprotocol%401.43.0) ([commit e7925de](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/e7925de93cd757d34755168b88c8dd2f2c2f3657), [commit 7d2f28d](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/7d2f28df868a28ffda1a28644143b1b538a15c75))
- Allow subpaths in the base URL when connecting to LiveKit server API. Before, all petitions were routed to the root path /twirp (e.g., https://my-livekit-server.com/twirp). Now, subpaths are also supported (e.g., https://my-livekit-server.com/my-subpath/twirp). [PR #32](https://github.com/pabloFuente/livekit-server-sdk-dotnet/pull/32)
## 1.0.13
- Update livekit/protocol to [v1.42.0](https://github.com/livekit/protocol/releases/tag/%40livekit%2Fprotocol%401.42.0) ([commit c2800f1](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/c2800f17c3facb17e3a6ed3796bfe93dd2520e66), [commit 2682f5f](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/2682f5f841b3d65ddd6688576981ed8479a8fac3))
## 1.0.12
- Update livekit/protocol to [v1.41.0](https://github.com/livekit/protocol/releases/tag/%40livekit%2Fprotocol%401.41.0) ([commit f7e94d3](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/f7e94d315faad9aa1cc05f0749cc8e535956b0da), [commit 9a70b07](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/9a70b07ba3070603f009bf537cf6052c4c9818c8))
## 1.0.11
- Update livekit/protocol to [v1.40.0](https://github.com/livekit/protocol/releases/tag/%40livekit%2Fprotocol%401.40.0) ([commit 1ef7e55](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/1ef7e55d3f82cedb03dfec63924f9ed5cf3881e4), [commit d33976b](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/d33976b6b87cd13dc3305e802274865a7598b2ed))
## 1.0.10
- Update livekit/protocol to [v1.39.2](https://github.com/livekit/protocol/releases/tag/%40livekit%2Fprotocol%401.39.2) ([commit 6569eda](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/6569edadb797295c191a53d35f05452e05a1eafb), [commit 95040f7](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/95040f79d00ef195d642ea2cd99caa9b1d5e6858))
## 1.0.9
- Update livekit/protocol to [v1.39.0](https://github.com/livekit/protocol/releases/tag/%40livekit%2Fprotocol%401.39.0) ([commit 8f8636f](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/8f8636f6108295b83b2f26e8817d15843f513235), [commit 45dc9ec](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/45dc9eccf09b9ccc4b4594680e280b68285872c0))
## 1.0.8
- Update livekit/protocol to [v1.38.0](https://github.com/livekit/protocol/releases/tag/%40livekit%2Fprotocol%401.38.0) ([commit 8155ad6](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/8155ad6eb7acca5b5c3a7741a7e9f8a8a70e5d71), [commit 65703e4](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/65703e4c2c436c05dd7f930f1d790497af0a5e88), [commit a75a2e8](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/a75a2e850a46fa184711054243d0f0d49d236d1f))
## 1.0.7
- Update livekit/protocol to [v1.34.0](https://github.com/livekit/protocol/releases/tag/%40livekit%2Fprotocol%401.34.0) ([commit 002330b](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/002330b035ef01625efcd7844a2fd717802018b8), [commit a8d1f2d](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/a8d1f2d864d8b950b91fba51d5c3700d90f5a34a))
## 1.0.6
- Update livekit/protocol to [v1.33.0](https://github.com/livekit/protocol/releases/tag/%40livekit%2Fprotocol%401.33.0) ([commit 6ffd848](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/6ffd8483367a3ae42f89723ab7ea0882bb14dc0c), [commit 605659d](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/605659dc63a77d8a386a33359c53b7d9e67050d8))
## 1.0.5
- Update livekit/protocol to [v1.32.1](https://github.com/livekit/protocol/releases/tag/%40livekit%2Fprotocol%401.32.1) ([commit 59c8b30](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/59c8b30b9ff1985096764a39771c1102ea279cb4), [commit 7d66404](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/7d66404cfa99245c1e2223ccab00b0a009948837))
## 1.0.4
- Update livekit/protocol to [v1.32.0](https://github.com/livekit/protocol/releases/tag/%40livekit%2Fprotocol%401.32.0) ([commit 09284cd](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/09284cd1ede94c00001604690632761f46c24e9b), [commit 7f06890](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/7f06890f95befddf601bdd419d7a9a2413d9a9a0))
## 1.0.3
- Update livekit/protocol to [v1.29.3](https://github.com/livekit/protocol/releases/tag/%40livekit%2Fprotocol%401.29.3) ([commit c7483b7](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/c7483b7a940079a0c5aae9b59912b1ad4ef94598), [commit 66083a6](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/66083a623b30e59450005a5b01e539f850084a90), [commit f59cbcb](https://github.com/pabloFuente/livekit-server-sdk-dotnet/commit/f59cbcb363a287c3472769446e3ce77573ff3b94))