Pinqponq.LiveKit.Server
1.0.2
Prefix Reserved
dotnet add package Pinqponq.LiveKit.Server --version 1.0.2
NuGet\Install-Package Pinqponq.LiveKit.Server -Version 1.0.2
<PackageReference Include="Pinqponq.LiveKit.Server" Version="1.0.2" />
<PackageVersion Include="Pinqponq.LiveKit.Server" Version="1.0.2" />
<PackageReference Include="Pinqponq.LiveKit.Server" />
paket add Pinqponq.LiveKit.Server --version 1.0.2
#r "nuget: Pinqponq.LiveKit.Server, 1.0.2"
#:package Pinqponq.LiveKit.Server@1.0.2
#addin nuget:?package=Pinqponq.LiveKit.Server&version=1.0.2
#tool nuget:?package=Pinqponq.LiveKit.Server&version=1.0.2
Pinqponq.LiveKit.Server
A LiveKit server API client for .NET: it issues the access
tokens your clients join rooms with, calls LiveKit's RoomService over its Twirp JSON
API, and verifies the webhooks LiveKit sends back. Requests are plain HttpClient
calls with System.Text.Json — no generated Protobuf/gRPC client and no Protobuf
toolchain.
Install
dotnet add package Pinqponq.LiveKit.Server
Requirements
- .NET 8.0, 9.0, or 10.0
- A LiveKit server — self-hosted or LiveKit Cloud — and its API key/secret pair
Quick start
using Pinqponq.LiveKit.Server;
using Pinqponq.LiveKit.Server.Auth;
using Pinqponq.LiveKit.Server.DependencyInjection;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddPinqponqLiveKitServer(options => options.Url = "wss://my-project.livekit.cloud")
.AddStaticCredentials(
builder.Configuration["LiveKit:ApiKey"]!,
builder.Configuration["LiveKit:ApiSecret"]!);
var app = builder.Build();
app.MapPost("/livekit/token", async (AccessTokenIssuer issuer, CancellationToken cancellationToken) =>
{
var token = await issuer.CreateToken(
new AccessTokenOptions
{
Identity = "user-123",
Name = "Jane Doe",
VideoGrant = new VideoGrant { RoomJoin = true, Room = "my-room" },
},
cancellationToken);
return Results.Ok(new { token = token.Value, expiresAt = token.ExpiresAt });
});
app.Run();
Managing rooms and receiving webhooks from injected services:
using Pinqponq.LiveKit.Server.Models;
using Pinqponq.LiveKit.Server.Rooms;
using Pinqponq.LiveKit.Server.Webhooks;
public sealed class MeetingService(RoomServiceClient roomServiceClient, WebhookReceiver webhookReceiver)
{
public Task<IReadOnlyList<ParticipantInfo>> ListParticipantsAsync(string roomName, CancellationToken cancellationToken) =>
roomServiceClient.ListParticipants(roomName, cancellationToken);
public async Task HandleWebhookAsync(string rawBody, string? authorizationHeader, CancellationToken cancellationToken)
{
// Throws LiveKitWebhookValidationException unless LiveKit signed exactly this body.
var webhookEvent = await webhookReceiver.Receive(rawBody, authorizationHeader, cancellationToken);
if (webhookEvent.EventType == WebhookEventType.ParticipantJoined)
{
// webhookEvent.Room, webhookEvent.Participant, ...
}
}
}
Configuration
AddPinqponqLiveKitServer(Action<LiveKitServerOptions> configureOptions) registers
RoomServiceClient (as a typed HttpClient), AccessTokenIssuer and
WebhookReceiver, and returns a LiveKitServerBuilder for the credentials:
| Option | Default | Notes |
|---|---|---|
Url |
"" (required) |
LiveKit server address. Accepts http, https, ws and wss, with an optional sub path; ws/wss are sent as http/https. |
Options are validated on startup via ValidateOnStart(), so a missing or
non-absolute Url fails fast instead of at the first RoomService call.
Credentials are registered on the returned builder — exactly one of:
AddStaticCredentials(apiKey, apiSecret)— a fixed key/secret pair.AddCredentialsProvider<TCredentialsProvider>(lifetime)— your ownILiveKitCredentialsProvider, e.g. one that reads a secret store (default lifetime:Singleton).
Main types
| Type | Kind | Purpose |
|---|---|---|
AddPinqponqLiveKitServer(Action<LiveKitServerOptions>) |
DI extension (IServiceCollection) |
Registers the three services below and returns a LiveKitServerBuilder. |
LiveKitServerBuilder |
Builder | AddStaticCredentials(...) / AddCredentialsProvider<T>(...) register the ILiveKitCredentialsProvider. |
AccessTokenIssuer |
Service | CreateToken(AccessTokenOptions, cancellationToken) → LiveKitAccessToken (Value, ExpiresAt). |
AccessTokenOptions |
Options | Identity, Name, Metadata, Attributes, VideoGrant, Ttl (default AccessTokenOptions.DefaultTtl, 6 hours). |
VideoGrant |
Model | The token's video claim: room join/create/list/admin, publish/subscribe permissions, CanPublishSources, and the rest of LiveKit's grant fields. |
RoomServiceClient |
Service | ListRooms, GetRoom, ListParticipants, RemoveParticipant, DeleteRoom against LiveKit's RoomService. |
WebhookReceiver |
Service | Receive(rawBody, authorizationHeader, cancellationToken) → WebhookEvent, after verifying LiveKit's signature. |
WebhookEvent |
Model | Id, Event / EventType, CreatedAt, Room, Participant, Track. |
ILiveKitCredentialsProvider |
Interface | GetCredentials(cancellationToken) → LiveKitCredentials. Implement it to source the key/secret yourself. |
LiveKitApiException |
Exception | A RoomService call returned a non-success status: StatusCode, ErrorCode (Twirp code), IsNotFound. |
LiveKitWebhookValidationException |
Exception | A webhook is not authentic — see Notes / behavior. |
Notes / behavior
- Each RoomService call carries its own least-privilege token.
RoomServiceClientsigns a 10-minute token per call holding only the grant that call needs —roomListforListRooms/GetRoom,roomAdminlimited to the target room forListParticipants/RemoveParticipant, androomCreateforDeleteRoom. - Token options are checked before signing.
CreateTokenthrowsArgumentOutOfRangeExceptionfor a non-positiveTtl, andArgumentExceptionwhen aRoomJoingrant has noIdentity, or aRoomJoin/RoomAdmingrant has noRoom. Tokens are HS256-signed with the API secret. - Unset publish/subscribe permissions mean "granted". On
VideoGrant,CanPublish,CanSubscribeandCanPublishDataare nullable on purpose: LiveKit treats an unset permission as granted and only an explicitfalsedenies it. Do not default them tofalsewhen building a grant. - Webhook verification fails closed.
ReceivethrowsLiveKitWebhookValidationExceptionwhen theAuthorizationheader is missing, the token's signature, issuer (your API key) or lifetime is invalid, or the body's SHA-256 does not match the checksum in the token. Pass the request body exactly as received — the checksum covers those bytes, so a re-serialized body will not verify. - Webhooks can arrive more than once. LiveKit retries failed deliveries; use
WebhookEvent.Idto discard duplicates. Egress, ingress and agent job payloads are not modeled — read them from the raw body if you need them. - API errors keep LiveKit's code.
LiveKitApiException.ErrorCodeholds the Twirp error code (not_found,permission_denied, …) and isnullwhen the response was not a Twirp error at all, e.g. an HTML page from a proxy.GetRoomreturnsnullfor a room that isn't open rather than throwing. - Credentials are read on every call. The
ILiveKitCredentialsProvideris called each time a token is signed or a webhook is verified, so a provider backed by a remote secret store should cache internally.LiveKitCredentials.ToString()omits the secret, so it is safe to log.
Related packages
Pinqponq.Identity— authenticates your own users; issue a LiveKit access token only after that check passes.Pinqponq.ErrorHandling— mapLiveKitWebhookValidationExceptionandLiveKitApiExceptionto standard error responses through itsStatusMappingResolver.
Samples
Try this package in the browser via Pinqponq.Playground — see samples/README.md.
Repository
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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. |
-
net10.0
- Microsoft.Extensions.Configuration (>= 10.0.10)
- Microsoft.Extensions.DependencyInjection (>= 10.0.10)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.10)
- Microsoft.Extensions.Http (>= 10.0.10)
- Microsoft.Extensions.Logging (>= 10.0.10)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.10)
- Microsoft.Extensions.Options (>= 10.0.10)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.10)
-
net8.0
- Microsoft.Bcl.Memory (>= 10.0.12)
- Microsoft.Extensions.Configuration (>= 8.0.0)
- Microsoft.Extensions.DependencyInjection (>= 8.0.1)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Http (>= 8.0.1)
- Microsoft.Extensions.Logging (>= 8.0.1)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.3)
- Microsoft.Extensions.Options (>= 8.0.2)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 8.0.0)
-
net9.0
- Microsoft.Extensions.Configuration (>= 9.0.18)
- Microsoft.Extensions.DependencyInjection (>= 9.0.18)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.18)
- Microsoft.Extensions.Http (>= 9.0.18)
- Microsoft.Extensions.Logging (>= 9.0.18)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.18)
- Microsoft.Extensions.Options (>= 9.0.18)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 9.0.18)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.