AWS AppSync Events
Recently, I got a requirement to notify the client applications running on-prem whenever a specific event occurs in the AWS cloud environment, which the client might be interested in as shown below. I considered several options but ultimately chose AWS AppSync Events.
Options Considered
Polling
Clients poll over HTTP for updates on topics they are interested in. This approach adds unnecessary traffic and requires scaling the application handling the polling requests. Additionally, maintaining metrics and logging is an overhead. I needed a serverless solution that provided these features out of the box.
AWS IoT Core
AWS IoT Core over MQTT protocol was an option, but for .NET clients, AWS STS access key and secret key authentication did not work. Certificate-based authentication was worked, but managing certificates for on-prem client applications became complex, especially since the on-prem environment was managed by an external client.
AWS API Gateway Web Sockets
With API Gateway, you need to manage your own connections. This means listening to $connect
and $disconnect
events and storing connection IDs in a database (DynamoDB or Redis ). Since $disconnect
events are not guaranteed for every connection, you are responsible for garbage collection.
Sending a message requires invoking the API and is limited to one connection at a time. If you need to send messages to all connected users, you must loop over all stored connections and send messages individually. You could offload this task to an SQS queue and a Lambda function, but it’s not ideal.
Why AWS AppSync Events?
Given these constraints, I finalized AWS AppSync Events, which met all my requirements and provided built-in metrics, logging, and a fully managed WebSocket connection service.
In this blog, I will walk you through the code to implement a client that integrates with AWS AppSync Events.
AWS AppSync Events Overview
AWS AppSync Events lets you create secure and performant serverless WebSocket APIs that can broadcast real-time event data to millions of subscribers without needing to manage connections or resource scaling. No API code is required to get started, allowing you to create production-ready real-time web and mobile experiences in minutes.
Use Cases
- Live chat and messaging
- Sports and score updates
- Real-time in-app alerts and notifications
- Live commenting and activity feeds
AWS AppSync Events Features
WebSocket and HTTP Support
- Clients can publish events over HTTP and subscribe to channels using WebSockets.
- Event APIs provide WebSocket endpoints that enable real-time pub/sub capabilities.
Channel Namespaces and Channels
- Events are published to channels, which are grouped under namespaces.
- Namespaces allow you to define authentication rules, authorization policies, and serverless functions that apply to all channels within that namespace.
Namespace Handlers
You can configure functions to run when events are published or when clients subscribe:
- OnPublish — Runs when an event is published to a channel, allowing you to transform, filter, or reject events.
- OnSubscribe — Runs when a client subscribes to a channel, allowing you to customize the behavior or reject subscription requests.
Flexible Authentication and Authorization
AWS AppSync Events supports multiple authentication mechanisms:
- API Key
- IAM
- Amazon Cognito
- OpenID Connect (OIDC)
- Lambda Authorizers
Authentication can be configured at both the API level and channel namespace level.
Channel Subscriptions
- Clients receive events for the channels they subscribe to.
- Wildcard Channel Subscriptions allow clients to subscribe to a group of related channels using wildcard syntax (e.g.,
namespace/channel/*
).
Scalable Event Broadcasting
- AWS AppSync Events automatically scales to handle a large number of concurrent connections and efficiently broadcasts events to all subscribed clients.
Integration with AWS Ecosystem
- AWS AppSync Events integrates with Amazon CloudWatch Logs, CloudWatch Metrics, and AWS WAF.
- Events can be published directly from Amazon EventBridge, AWS Lambda, and other AWS services.
- Amazon Cognito is directly supported as an authentication provider.
Cost Estimation
- 1000 clients connected for 24 hours & 30 days
- 1000 connections x 1,440 minutes per day x $0.08 per million minutes = 0.1152 per day => 30 * 0.1152 = 3.45 per month
Proof of Concept (PoC)
We will build a .NET Console and React Client Application to subscribe to a channel. These applications are hosted on-prem and are interested in specific event notifications published over AWS AppSync Events.
Flow
- A Lambda function publishes a message to a channel.
- AWS AppSync broadcasts the message to all subscribed clients.
- The on-prem applications receive real-time updates via WebSockets.
Implementation
Publisher Code (C#)
The following C# code demonstrates how to publish an event to AWS AppSync using HttpClient.
public class HttpClientHelper
{
private static readonly HttpClient _httpClient = new HttpClient();
public static async Task PublishEventAsync(string channel, string message)
{
var eventsList = new List<string>
{
JsonSerializer.Serialize(new Message { EventDetails = new List<EventDetail>()
{ new EventDetail() { Data = message } } })
};
var jsonContent = new StringContent(
JsonSerializer.Serialize(new EventData { channel = channel, events = eventsList }),
Encoding.UTF8, "application/json");
var request = new HttpRequestMessage(HttpMethod.Post, $"https://{Config.HttpDomain}/event")
{
Content = jsonContent
};
request.Headers.Host = Config.HttpDomain;
request.Headers.Add("x-api-key", Config.ApiKey);
var response = await _httpClient.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();
Console.WriteLine($"✅ Message published: {result}");
}
}
public class EventData
{
public string channel { get; set; }
public List<string> events { get; set; }
}
public class Message
{
public List<EventDetail> EventDetails { get; set; }
}
public class EventDetail
{
public string Id { get; set; } = Guid.NewGuid().ToString();
public string Name { get; set; } = Guid.NewGuid().ToString();
public string Data { get; set; }
}
Subscriber Code (C#)
The following C# code demonstrates how to subscribe to an AWS AppSync event channel using WebSockets.
public class WebSocketClient
{
private readonly Uri _url;
private readonly ClientWebSocket _webSocket;
private readonly string _authHeader;
public event Action<string>? OnMessageReceived; // Event for received messages
public class SumbscriptionMessage
{
public string type { get; set; }
public string id { get; set; }
public string channel { get; set; }
public Authorization authorization { get;set; }
}
public class Authorization
{
[JsonPropertyName("x-api-key")]
public string x_api_key { get; set; }
public string host { get; set; }
}
public WebSocketClient()
{
_url = new Uri($"wss://{Config.RealTimeDomain}/event/realtime");
_authHeader = GetAuthProtocol();
_webSocket = new ClientWebSocket();
_webSocket.Options.AddSubProtocol("aws-appsync-event-ws");
_webSocket.Options.AddSubProtocol(_authHeader);
}
private string GetAuthProtocol()
{
var authJson = $"{{\"x-api-key\":\"{Config.ApiKey}\", \"host\":\"{Config.HttpDomain}\"}}";
var authBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(authJson))
.Replace("+", "-")
.Replace("/", "_")
.TrimEnd('=');
return $"header-{authBase64}";
}
public async Task ConnectAsync()
{
try
{
await _webSocket.ConnectAsync(_url, CancellationToken.None);
Console.WriteLine("✅ WebSocket connected");
// Send connection initialization message
var initMessage = Encoding.UTF8.GetBytes("{\"type\": \"connection_init\"}");
await _webSocket.SendAsync(new ArraySegment<byte>(initMessage), WebSocketMessageType.Text, true, CancellationToken.None);
// Start listening for messages
_ = Task.Run(ReceiveMessagesAsync);
}
catch (Exception ex)
{
Console.WriteLine($"❌ WebSocket connection failed: {ex.Message}");
}
}
public async Task SubscribeAsync(string channel2)
{
if (_webSocket.State != WebSocketState.Open)
{
Console.WriteLine("❌ WebSocket is not open. Cannot subscribe.");
return;
}
// Construct the correct subscription payload
var message = new SumbscriptionMessage
{
type = "subscribe",
id = Guid.NewGuid().ToString(),
channel= channel2,
authorization = new Authorization()
{
x_api_key = Config.ApiKey,
host = Config.HttpDomain
}
};
string jsonMessage = JsonSerializer.Serialize(message);
byte[] messageBytes = Encoding.UTF8.GetBytes(jsonMessage);
Console.WriteLine($"📡 Sending subscription request: {jsonMessage}");
await _webSocket.SendAsync(new ArraySegment<byte>(messageBytes), WebSocketMessageType.Text, true, CancellationToken.None);
}
private async Task ReceiveMessagesAsync()
{
var buffer = new byte[4096];
while (_webSocket.State == WebSocketState.Open)
{
var result = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Close)
{
Console.WriteLine("🔴 WebSocket closed.");
break;
}
var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
Console.WriteLine($"📩 Received: {message}");
// Notify subscribers
OnMessageReceived?.Invoke(message);
}
}
}
Configuration
public static class Config
{
public static string RealTimeDomain = "abc.appsync-realtime-api.us-east-1.amazonaws.com"; // Example: abcdefg.appsync-api.us-east-1.amazonaws.com
public static string HttpDomain = "abc.appsync-api.us-east-1.amazonaws.com"; // Example: abcdefg.appsync-api.us-east-1.amazonaws.com
public static string ApiKey = "test-kay";
}
Conclusion
AWS AppSync Events provides a serverless, scalable, and fully managed WebSocket API service, making it an ideal choice for real-time event broadcasting.