Index: TFP-WebServer/WebServer/src/UrlHandlers/ApiHandler.cs
===================================================================
--- TFP-WebServer/WebServer/src/UrlHandlers/ApiHandler.cs	(revision 467)
+++ TFP-WebServer/WebServer/src/UrlHandlers/ApiHandler.cs	(revision 487)
@@ -30,6 +30,6 @@
 
 		private void apiFoundCallback (Type _type) {
-			ConstructorInfo ctor = _type.GetConstructor (BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, (Binder)null,
-				apiWithParentCtorTypes, (ParameterModifier[])null);
+			ConstructorInfo ctor = _type.GetConstructor (BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null,
+				apiWithParentCtorTypes, null);
 			if (ctor != null) {
 				AbsWebAPI apiInstance = (AbsWebAPI)ctor.Invoke (apiWithParentCtorArgs);
@@ -38,6 +38,6 @@
 			}
 
-			ctor = _type.GetConstructor (BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, (Binder)null,
-				apiEmptyCtorTypes, (ParameterModifier[])null);
+			ctor = _type.GetConstructor (BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null,
+				apiEmptyCtorTypes, null);
 			if (ctor != null) {
 				AbsWebAPI apiInstance = (AbsWebAPI)ctor.Invoke (apiEmptyCtorArgs);
Index: TFP-WebServer/WebServer/src/UrlHandlers/SessionHandler.cs
===================================================================
--- TFP-WebServer/WebServer/src/UrlHandlers/SessionHandler.cs	(revision 467)
+++ TFP-WebServer/WebServer/src/UrlHandlers/SessionHandler.cs	(revision 487)
@@ -20,5 +20,4 @@
 		private const string userPassLoginUrl = "login";
 		public const string userPassLoginName = "User/pass";
-		private const string userPassErrorPage = "UserPassLoginFailed";
 
 		public SessionHandler () : base (null) {
Index: TFP-WebServer/WebServer/src/UrlHandlers/SseHandler.cs
===================================================================
--- TFP-WebServer/WebServer/src/UrlHandlers/SseHandler.cs	(revision 467)
+++ TFP-WebServer/WebServer/src/UrlHandlers/SseHandler.cs	(revision 487)
@@ -20,4 +20,6 @@
 		private static readonly Type[] ctorTypes = { typeof (SseHandler) };
 		private static readonly object[] ctorParams = new object[1];
+
+		private readonly List<SseClient> clients = new List<SseClient>();
 
 		public SseHandler (string _moduleName = null) : base (_moduleName) {
@@ -57,13 +59,57 @@
 
 		public override void HandleRequest (RequestContext _context) {
-			string eventName = _context.RequestPath.Remove (0, urlBasePath.Length);
-
-			if (!events.TryGetValue (eventName, out AbsEvent eventInstance)) {
-				Log.Warning ($"[Web] [SSE] In {nameof (SseHandler)}.HandleRequest(): No handler found for event \"{eventName}\"");
-				_context.Response.StatusCode = (int)HttpStatusCode.NotFound;
+			string eventNames = _context.QueryParameters ["events"];
+			if (string.IsNullOrEmpty (eventNames)) {
+				Log.Warning ($"[Web] [SSE] In {nameof (SseHandler)}.HandleRequest(): No 'events' query parameter given");
+				_context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
 				return;
 			}
 
-			if (!IsAuthorizedForEvent (eventName, _context.PermissionLevel)) {
+			SseClient client;
+			try {
+				client = new SseClient(this, _context.Response);
+			} catch (Exception e) {
+				Log.Error ($"[Web] [SSE] In {nameof (SseHandler)}.HandleRequest(): Could not create client:");
+				Log.Exception (e);
+				_context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
+				return;
+			}
+
+			int eventsFound = 0;
+			int eventsAuthorized = 0;
+			int eventsRegistered = 0;
+			foreach (string eventName in eventNames.Split (',', StringSplitOptions.RemoveEmptyEntries)) {
+				if (!events.TryGetValue (eventName, out AbsEvent eventInstance)) {
+					Log.Warning ($"[Web] [SSE] In {nameof (SseHandler)}.HandleRequest(): No handler found for event \"{eventName}\"");
+					continue;
+				}
+
+				eventsFound++;
+				
+				if (!IsAuthorizedForEvent (eventName, _context.PermissionLevel)) {
+					continue;
+				}
+
+				eventsAuthorized++;
+
+				try
+				{
+					eventInstance.AddListener (client);
+				} catch (Exception e) {
+					Log.Error ($"[Web] [SSE] In {nameof (SseHandler)}.HandleRequest(): Handler {eventInstance.Name} threw an exception:");
+					Log.Exception (e);
+					_context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
+				}
+
+				eventsRegistered++;
+			}
+
+			if (eventsFound == 0) {
+				_context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
+				_context.Response.Close ();
+				return;
+			}
+
+			if (eventsAuthorized == 0) {
 				_context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
 				if (_context.Connection != null) {
@@ -71,20 +117,9 @@
 				}
 
+				_context.Response.Close ();
 				return;
 			}
 
-			try {
-				eventInstance.AddListener (_context.Response);
-
-				// Keep the request open
-				_context.Response.SendChunked = true;
-
-				_context.Response.AddHeader ("Content-Type", "text/event-stream");
-				_context.Response.OutputStream.Flush ();
-			} catch (Exception e) {
-				Log.Error ($"[Web] [SSE] In {nameof (SseHandler)}.HandleRequest(): Handler {eventInstance.Name} threw an exception:");
-				Log.Exception (e);
-				_context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
-			}
+			clients.Add (client);
 		}
 
@@ -105,4 +140,8 @@
 					}
 				}
+
+				for (int index = clients.Count - 1; index >= 0; index--) {
+					clients[index].HandleKeepAlive ();
+				}
 			}
 		}
@@ -111,4 +150,17 @@
 			evSendRequest.Set ();
 		}
+
+		public void ClientClosed (SseClient _client) {
+			foreach ((string eventName, AbsEvent eventHandler) in events) {
+				try {
+					eventHandler.ClientClosed (_client);
+				} catch (Exception e) {
+					Log.Error($"[Web] [SSE] '{eventName}': Error closing client");
+					Log.Exception(e);
+				}
+			}
+
+			clients.Remove (_client);
+		}
 	}
 }
