Table of Contents

Events

The SDK raises events for call, line and quality changes. Consuming them correctly means knowing which thread each fires on and what a handler is allowed to do.

Threading contract

Handlers run on SDK-internal threads. A handler must not block and must not throw. A blocking handler stalls the pipeline it runs on; a throwing handler is logged and swallowed but may drop the notification.

Event Source Thread
ICall.StateChanged call Signaling thread, serialized
ICall.HoldStateChanged call Signaling thread, serialized
ICall.DtmfReceived call Two possible threads: SIP INFO handling or the RFC-4733 media path
ICall.TransferRequested call Signaling thread — handled synchronously (accept/reject inline)
ICall.QualitySnapshotChanged call Media/RTCP thread
IPhoneLine.StateChanged line Signaling thread, serialized
IPhoneLine.IncomingCall line Signaling thread
VoipClient.IncomingCall / CallStateChanged client Signaling thread

Because DtmfReceived can arrive from two different threads, make its handler thread-safe or marshal onto your own queue.

Do / don't

  • Do subscribe before placing/accepting a call — events fire live to the handlers present at the transition and are not guaranteed to be replayed to late subscribers.
  • Do marshal to your UI/synchronization context, enqueue work, or set flags.
  • Do keep handlers short and non-blocking.
  • Don't run await-heavy work inline on the event thread — offload it.
  • Don't throw; catch inside your handler.

Convenience subscription

client.OnIncomingCall(handler) returns an IDisposable; dispose it to unsubscribe. This is the recommended way to handle inbound calls without wiring raw events.

The same contract is documented on the types themselves (ICall, IPhoneLine, VoipClient) via XML docs, and expanded under Production → Threading.