Subscribing to events work like this, assuming you have a Subscription already:
1. Find the ObjectNode in the server that is the source of events you're interested in. Its EventNotifier attribute will be set.
2. Create a MonitoredItem, pointing at that EventNotifier attribute, in MonitoringMode.Reporting, optionally specifying an EventFilter. How you define an EventFilter is talked about in part 4 of the spec, section 7.17.3. It's almost like you're building a SQL query with a select and where clause that determines what kinds of events and what fields from those events are delivered.
3. Set an EventConsumer on the UaMonitoredItem you created. That will be the callback invoked when the server delivers events that passed the EventFilter.
You shouldn't need to worry about receiving empty PublishResponses or null NotificationMessages (neither of those things should happen, except an empty PublishResponse when its a KeepAlive). If you're using the OpcUaClient then you don't ever interact directly with PublishRequest/PublishResponse in the first place - the subscription manager takes care of that and you just receive callbacks on the items you create.