Instead of subscribing to XmppXElementStreamObserver for handling packets we also can write handlers and inject them to the ChannelPipeline.

The following example is creating a handler that handles incoming software version requests XEP-0092: Software Version

using Matrix.Attributes;
using Matrix.Network.Handlers;
using Matrix.Xml;
using Matrix.Xmpp;
using Matrix.Xmpp.Client;    
using Matrix.Xmpp.Version;

/// <summary>
/// This handler automatically replies to incoming XMPP software version requests (XEP-0092: Software Version)
/// </summary>    
public class XmppVersionHandler : XmppStanzaHandler
    public XmppVersionHandler()
             setup a handler which handles elements
              * which are of type Iq
              * AND have a type of get
              * AND the query is of type Version
            el =>
                && el.Cast<Iq>().Type == IqType.Get
                && el.Cast<Iq>().Query.OfType<Version>(),

            async (context, xmppXElement) =>
                var iq = xmppXElement.Cast<Iq>();

                var resIq = new VersionIq();
                resIq.Id = iq.Id;
                resIq.To = iq.From;
                resIq.Type = IqType.Result;
                resIq.Version.Name = "Matrix-Client";
                resIq.Version.Os = "Windows";
                resIq.Version.Ver = "1.2.0";

                await SendAsync(resIq);

Now our handler then can be ingested to the channel pipeline. Here we do it in the constructor when we create our XmppClinet instance.

var pipelineInitializerAction = new Action<IChannelPipeline>(pipeline =>
    pipeline.AddBefore<XmppStanzaHandler>(new XmppVersionHandler());

var xmppClient = new XmppClient(pipelineInitializerAction);