- Handles multiple user communications with Twitch EventSub via websocket
- For more information on Twitch EventSub, refer to the Twitch EventSub Documentation.
Version 3.0.0 introduces Dependency Injection as the primary setup path and replaces static API classes with injectable singletons. The following breaking changes require action.
// v2
var client = new EventSubClient("your-client-id", logger);
// v3 — use DI (see Setup below), or supply options manually
var client = new EventSubClient(
Options.Create(new EventSubClientOptions { ClientId = "your-client-id" }),
logger,
twitchApi);If you called these classes directly:
// v2
await TwitchApi.SubscribeAsync(...);
await TwitchApiConduit.ConduitCreatorAsync(...);
// v3 — resolve from DI or construct with IHttpClientFactory
var api = new TwitchApi(httpClientFactory);
await api.SubscribeAsync(...);In practice, you should not need to call these directly — IEventSubClient covers all normal usage.
// v2
services.AddSingleton<IEventSubClient>(new EventSubClient("client-id", logger));
// v3
services.AddTwitchEventSub(options => options.ClientId = "your-client-id");// Program.cs / Startup.cs
services.AddTwitchEventSub(options =>
{
options.ClientId = "your-client-id";
});This registers:
- Two named
HttpClientinstances with standard resilience pipelines (retry, circuit breaker, timeout) and telemetry enrichment TwitchApiandTwitchApiConduitas singletonsIEventSubClientas a singleton- Logging if not already registered
Use the IServiceProvider overload to resolve IConfiguration or any other DI service when configuring options:
services.AddTwitchEventSub((sp, options) =>
{
var config = sp.GetRequiredService<IConfiguration>();
options.ClientId = config["Twitch:ClientId"]
?? Environment.GetEnvironmentVariable("TWITCH_CLIENT_ID")
?? throw new InvalidOperationException("Twitch ClientId is not configured.");
});- Client Id is the identifier of your Twitch application
- User Id is the identifier of a Twitch user
- AccessToken is a bearer token obtained for the user
IEventSubClient is the primary interface — inject it wherever you need it.
public async Task<bool> SetupAsync(string userId)
{
var listOfSubs = new List<SubscriptionType>
{
SubscriptionType.ChannelFollow
};
_listOfSubs = listOfSubs;
var resultAdd = await _eventSubClient.AddUserAsync(
userId,
GetApiToken(),
_listOfSubs,
allowRecovery: true).ConfigureAwait(false);
if (resultAdd)
{
SetupEvents(userId);
}
return resultAdd;
}private void SetupEvents(string userId)
{
var provider = _eventSubClient[userId];
if (provider == null)
{
_logger.LogError("EventSub Provider returned null for user {UserId}", userId);
return;
}
provider.OnRefreshTokenAsync -= EventSubClientOnRefreshTokenAsync;
provider.OnRefreshTokenAsync += EventSubClientOnRefreshTokenAsync;
provider.OnFollowEventAsync -= EventSubClientOnFollowEventAsync;
provider.OnFollowEventAsync += EventSubClientOnFollowEventAsync;
provider.OnUnexpectedConnectionTermination -= EventSubClientOnUnexpectedConnectionTermination;
provider.OnUnexpectedConnectionTermination += EventSubClientOnUnexpectedConnectionTermination;
#if DEBUG
provider.OnRawMessageAsync -= EventSubClientOnRawMessageAsync;
provider.OnRawMessageAsync += EventSubClientOnRawMessageAsync;
#endif
}await _eventSubClient.StartAsync(userId).ConfigureAwait(false);await _eventSubClient.StopAsync(userId).ConfigureAwait(false);- EventSub does not provide token refresh capabilities — you must implement your own.
- Subscribe to
OnRefreshTokenAsyncto be notified when a token refresh is needed.
provider.OnRefreshTokenAsync -= EventSubClientOnRefreshTokenAsync;
provider.OnRefreshTokenAsync += EventSubClientOnRefreshTokenAsync;private async Task EventSubClientOnRefreshTokenAsync(object sender, RefreshRequestArgs e)
{
_logger.LogInformation("EventSub requesting token refresh for user {UserId}", e.UserId);
_eventSubClient.UpdateUser(
e.UserId,
await GetNewAccessTokenAsync(),
_listOfSubs);
}Listen to IsConnected and OnUnexpectedConnectionTermination to detect failures and recover.
private async void RecoveryRoutineAsync(string userId)
{
try
{
if (_eventSubClient.IsConnected(userId))
{
_logger.LogDebug("EventSubClient is already connected, skipping recovery");
return;
}
var provider = _eventSubClient[userId];
if (provider != null)
{
provider.OnRefreshTokenAsync -= EventSubClientOnRefreshTokenAsync;
provider.OnFollowEventAsync -= EventSubClientOnFollowEventAsync;
provider.OnUnexpectedConnectionTermination -= EventSubClientOnUnexpectedConnectionTermination;
provider.OnRawMessageAsync -= EventSubClientOnRawMessageAsync;
}
var deleted = await _eventSubClient.DeleteUserAsync(userId);
if (!deleted)
{
_logger.LogWarning("EventSub user was not gracefully terminated during recovery");
}
await SetupAsync(userId).ConfigureAwait(false);
await _eventSubClient.StartAsync(userId).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.LogError(ex, "EventSub recovery routine failed");
}
}This project is available under the MIT license. See the LICENSE file for more info.
