So it actually turned out to have nothing to do with cross site access, and everything to do with Mono incompatibilities.
After I enabled exception logging for SignalR (https://stackoverflow.com/a/16577890/299262) I got the following exception instead of just a blank http 500 error:
System.InvalidOperationException: The connection id is in the incorrect format.
at Microsoft.AspNet.SignalR.PersistentConnection.GetConnectionId (Microsoft.AspNet.SignalR.Hosting.HostContext context, System.String connectionToken) [0x00000] in <filename unknown>:0
at Microsoft.AspNet.SignalR.PersistentConnection.ProcessRequest (Microsoft.AspNet.SignalR.Hosting.HostContext context) [0x00000] in <filename unknown>:0
at Microsoft.AspNet.SignalR.Hubs.HubDispatcher.ProcessRequest (Microsoft.AspNet.SignalR.Hosting.HostContext context) [0x00000] in <filename unknown>:0
at Microsoft.AspNet.SignalR.Owin.CallHandler.Invoke (IDictionary`2 environment) [0x00000] in <filename unknown>:0
at Microsoft.AspNet.SignalR.Owin.Handlers.HubDispatcherHandler.Invoke (IDictionary`2 environment) [0x00000] in <filename unknown>:0
at Microsoft.AspNet.SignalR.Owin.Handlers.PersistentConnectionHandler.Invoke (IDictionary`2 environment) [0x00000] in <filename unknown>:0
at Microsoft.Owin.Diagnostics.ShowExceptionsMiddleware.Invoke (IDictionary`2 environment) [0x00000] in <filename unknown>
That led me to find this ticket on the SignalR repo issues list: https://github.com/SignalR/SignalR/issues/1914
So in the end, the fix was to comment out the following line in HostDependencyResolverExtensions.cs:
resolver.InitializePerformanceCounters(instanceName, hostShutdownToken);