Coming from the earlier ringing flow? The Chat SDK’s calling methods (
CometChat.initiateCall, acceptCall, rejectCall, endCall, CometChatCallDelegate) are superseded by the Calls SDK and no longer used; a coming Chat SDK release marks them deprecated. This page describes the replacement. See Migrating from legacy ringing for a method-by-method mapping.How ringing works
The caller creates the call with the Calls SDK. The CometChat backend sends a VoIP push to the callee. The Push SDK reports the call to CallKit, which rings the device, and when the callee answers, your call screen joins the session. The caller’s app learns that the callee is ringing and has answered from the Calls SDK, without joining the session first. What each side sees:On iOS the caller is not told when, or why, the callee ended the call:
onIncomingCallEnded is not reported for your own outgoing call. A decline or ring-out closes the caller’s prepared session (onConnectionClosed) only when the callee’s device had prepared it. A busy decline never does (the callee is in another call and never joins the room), and neither does a decline made before the callee’s device connected. So while the call rings, read its status from Call Logs every few seconds and end the screen on rejected, busy or unanswered, as the ring timer does when nothing arrives.Prerequisites
- Calls SDK set up and the user logged in.
- Push notifications configured with the Push Notifications SDK 2.0 Core and Calls products. The whole push side of ringing is on the Push ringing page; this page assumes it is done.
NSMicrophoneUsageDescriptionandNSCameraUsageDescriptionin yourInfo.plist. Add the camera string even if your app only places audio calls: the Calls SDK links the camera.- The Push Notifications capability and Background Modes → Voice over IP on your app target. Without them PushKit delivers no VoIP push and a killed app cannot ring.
- A physical device. PushKit and CallKit do not work in the Simulator.
Start a call
CometChatCalls.initiateCall creates the call on the backend, which rings the callee, and prepares the caller’s media session. The caller does not join yet.
InitiatedCall gives you sessionId, messageId (the id of the call message in the conversation), conversationId, sender, receiver, receiverType, type, status, initiatedAt (in seconds) and session, the CallSession being prepared.
initiateCall fails with SESSION_ALREADY_ACTIVE while the user is joining or in another call; leave that call first.
There is no client-side ring timeout. The callee’s device stops at the ring window the push carries (
ringTimeout), or the Push SDK’s default of 45 seconds, and ends a 1:1 call as unanswered; that closes the caller’s prepared session only when the callee’s device had prepared it. If the callee’s device is never reached, nothing ends the caller’s ringing until its prepared session expires after 5 minutes, so run your own timer on the caller and end the call as unanswered after the same window, as shown under Ring timeout.Show “Ringing…” and detect the answer
The caller stays on the session thatinitiateCall prepared and listens to it. Do not call joinSession yet: joining only when the callee has joined keeps the caller’s media off until there is someone to talk to, and it is what makes the two events below arrive.
Register on CallSession.shared. It is the session initiateCall is preparing (the same object call.session and CometChatCalls.getActiveSession() return), and registering on it works before the prepare has finished.
The SDK holds these listeners weakly and does not remove them for you: remove them when the screen goes away, and when the call screen takes over.
Listen for calls starting and ending
CometChatCalls.addIncomingCallListener reports calls ringing on this device and their end. The Push SDK feeds it from the VoIP push, including in a launch the push started, provided you register the listener in application(_:didFinishLaunchingWithOptions:): events are delivered only to listeners registered at that moment and are not replayed. On iOS it reports incoming calls only; your own outgoing call ends through the prepared session, as described above.
It fires once per call. Treat any other reason as “the ring is over”. Answering on this device is not reported, and neither is a decline or ring-out on this device: those reach the Push SDK delegate’s
callDidEnd(_:reason:).
CometChatIncomingCall carries sessionId, callerUid, callerName, callerAvatarUrl, callType (audio / video), receiverType, receiverUid, conversationId, initiatedAt (milliseconds), isVideo and isPeerCall.
Answer a call
CallKit shows the Answer button. When the user answers, the Push SDK callspresentCall(_:) on your CometChatPushNotificationsCallsDelegate. It fires immediately in every app state, the phone locked included (for End & Accept, once the previous call has left): present your call screen and join at once, so audio connects before the user unlocks. The Push SDK ends an answered call with .failed if the session has not joined within 20 seconds. If your UI may come up later than that, for example on an answer from the lock screen after a push started the app, don’t wait for a window to join: create your call screen, call joinSession with its container view, and present it once a window exists. There is no separate “accept” call to make.
- Swift
- Objective-C
IncomingCall carries sessionId, callerUid, callerName, callerAvatarUrl, callType, receiverType, receiverUid, conversationId, initiatedAtMs, ringTimeoutMs, isVideo and isPeerCall.
After presentCall, the Push SDK asks for the microphone (and the camera for a video call) if the user has not decided yet. On a lock-screen answer the prompt waits until the app is active; a denied microphone joins muted. To ask earlier yourself, turn this off with requestCallPermissions(false) on the push configuration.
Join the session
Both sides join the same way: the callee from the screenpresentCall opened, the caller from onParticipantJoined. joinSession takes over the session that was prepared earlier, so it connects almost at once. On a prepared session the settings passed to joinSession are ignored: the ones frozen when the session was prepared apply. That is the caller’s options.sessionSettings (if the prepare failed, joinSession joins directly with the settings it is given, so build those the same way, setPeerCall(true) included for a 1:1 call); the callee’s session is prepared by the Push SDK with its own settings (the session type, the camera off for an audio call, the speaker, and peer call for a 1:1 call), so layout or hidden-button settings passed by the callee have no effect.
Decline a call
Declining is handled by the Push SDK: CallKit’s Decline button sendsendCall(sessionID:status:) with rejected, and with busy instead when the user is already in another call. The decline goes through a queue that is replayed when the app next launches, if that is within 90 seconds, in case iOS suspends the app before it is sent. A group call’s decline is local: nothing is sent. On the caller, the prepared session closes (onConnectionClosed) only if the callee’s device had prepared it, and never for busy; so read the status while it rings.
You only write decline code when you offer a Decline button of your own, for example on an in-app banner (Swift only):
CometChatCalls.endCall(sessionID: sessionId, status: EndCallStatus.rejected, onSuccess:onError:).
Cancel a call
The caller ends a call that is still ringing withendCall and the cancelled status. The SDK tears the caller’s prepared session down first (your onConnectionClosed fires), then tells the server. The callee’s ring stops and a missed call is left in Recents.
onParticipantJoined that arrives after you cancelled: the cancel wins. If the screen goes away before initiateCall has returned, cancel the call from its onSuccess, or the callee rings for a call nobody is waiting on.
Ring timeout
The callee’s Push SDK ends an unanswered 1:1 call asunanswered when its ring window closes (45 seconds unless the push carries a ringTimeout), which closes the caller’s prepared session if the callee’s device had prepared it. To close the caller’s screen at the same moment even when the callee’s device was never reached, run your own timer from initiateCall and end the call with the same status. The Push SDK’s default window is RingConstants.defaultRingTimeoutMs (45 000 ms), public in CometChatPushNotificationsCalls, so the caller’s timer can use the same value:
End a call
Hang up by leaving the session. The room tells the other side, which receivesonParticipantLeft and then onSessionLeft or onConnectionClosed. No status needs to be sent and the call is logged by the backend. In a peer call (setPeerCall(true)), the session ends for both as soon as either leaves.
onSessionLeft.
CallSession.leaveSession() is deprecated in 5.1; use leave(). Leave only the session your screen owns: after answering a second call over a live one, the first screen’s clean-up must not leave the new session.Outgoing calls and CallKit
CallKit owns the Calls SDK’s audio only while it has a call: an answered incoming call, or an outgoing call reported to it, until the last one ends. The Push SDK reports an outgoing call to CallKit by itself once the Calls SDK prepares or joins its session, so outgoing calls get CallKit-owned audio too: they keep their audio through a cellular call, appear in Recents and can be ended from the lock screen. A session CallKit has no call for uses WebRTC’s normal audio handling. When the Calls product is linked, don’t setCometChatAudioSession.callKitAudioEnabled yourself; the Push SDK manages it. To show the callee’s name and the call type from the start, report it yourself right after initiateCall succeeds (Swift only):
reportCallConnected and reportCallEnded.
Second call while in a call
Handled by CallKit and the Push SDK, the same way a phone does it. A call that arrives while the user is in another call rings with CallKit’s call-waiting buttons. End & Accept ends the live call and answers the new one, sopresentCall(_:) fires again with the new session: open a fresh screen for it. Decline sends busy and keeps the live call. A call that arrives while another one is still ringing replaces it.
Group calls
To ring a group, callinitiateCall with the group’s GUID as receiver and options.receiverType = CallReceiverType.group, and leave setPeerCall off so the call outlives any one member leaving. Every member’s device rings through the push. You can also start a group call without ringing: share a session id (for example in a custom message) and have each member join it with joinSession. When a group call rings, declining it or letting it ring out sends nothing: the call carries on for the other members, and the decline is local to that device.
Logout
Log out of the Calls SDK when the user signs out (CometChatCalls.logout). On that logout the Push SDK stops a call still ringing for the old user and tries to unregister the push tokens. A Chat SDK logout does the same when the Push SDK’s Chat product is linked.
Unregistering needs the user’s auth token, which may already be gone when the logout is reported, so call CometChatPushNotifications.shared.unregisterTokens and wait for it before logging out of either SDK. End or cancel any call of your own before logging out: endCall needs a logged-in user.
Call status values
Related
- Push ringing: dependencies, capabilities,
PNConfiguration, PushKit and CallKit, the delegate, outgoing calls on CallKit, readiness checks and what happens when the app is killed. - Migrating from legacy ringing.
- Events, Actions, Call Logs.