We've implemented that by having a singleton on the .NET side that listens to commands from the browser. The first, possibly naive approach, was to simply call our code whenever we got a message:
However, this didn't work. While all the data was transferred correctly, whenever we accessed certain features of the Revit API, we got a SEHException from unmanaged code in Revit. A quick Google search yielded this bit of info: "SEHExceptions are always indications of a bug in Revit.". Ok, we thought, but that's super basic stuff we're accessing, there's probably not an undiscovered bug with Revit here.
Luckily, debugging this gave us a bit more info: The API is working correctly when accessed from the main thread, but throws when accessed by another one. So even though we weren't doing any UI operations, this seemed to be the root cause of our issue. Since you also can't invoke something on the main thread in Revit, with Application.Current.Dispatcher not being set, we fell back to using the UIApplication.Idling event and a stack to invoke our methods on the main thread:
This cost us a few hours, so I hope it's helpful to others!