SIPIS-less push calls ============================== This article describes deployment requirements for SIPIS-less incoming push calls. For deployments with SIPIS, refer to `SIPIS documentation `_. Quick Start ----------- Use this checklist for first integration: #. Configure SDK accounts with ``sipisDisabled`` and ``pushNotificationsEnabled``. #. Configure `Push Token Reporter API `_. #. Implement account-removal reporting so backend can remove stale push tuples after user log out. #. Persist tuple data (``AppId``, ``DeviceToken``, ``Selector``) for each registered device/account. #. On incoming call, load all tuples for the callee and send ``NotifyIncomingCall`` to each tuple. #. Send SIP INVITE to all registered contacts. #. Ensure pairing identifier ``Id`` is present in SIP signaling via ``X-Push-ID`` header when ``Id`` is not the SIP Call-ID. #. If call is canceled or answered elsewhere, send ``NotifyIncomingCallMissed`` or ``NotifyIncomingCallAnsweredElsewhere``. Prerequisites and configuration ------------------------------- Enable the following preference keys: ``sipisDisabled`` Disables SDK communication with SIPIS. ``pushNotificationsEnabled`` Enables push notification integration. Configure SDK SIP accounts to use the `Push Token Reporter API `_. Store the tuple reported by each device: ``AppId`` App/platform/environment identifier. ``DeviceToken`` Push target identifier for the device. ``Selector`` SIP account selector on the device. If one SIP account has multiple device registrations, persist all reported tuples. Optional account XML property: ``secondsToBeRegistered`` Overrides how long the SDK tries to stay registered and wait for INVITE after incoming call push. Default is platform-specific in direct push flow (for example Android uses 5 seconds, iOS uses 3 seconds). ``secondsToBeRegistered`` and Push Token Reporter configuration can both be provisioned via `External provisioning API `_. End-to-end sequence (backend + client) --------------------------------------- Backend flow: #. Collect and persist ``AppId``, ``DeviceToken``, ``Selector`` tuples reported by the `Push Token Reporter API `_. #. On incoming call, retrieve all tuples for the callee. #. Build incoming-call push payload with pairing identifier ``Id``. #. Send incoming-call push to every tuple. #. Send SIP INVITE to all registered contacts. #. If a new contact REGISTERs while the call is pending, send INVITE to that contact, and continue sending INVITE to any additional contacts that REGISTER before the call is established. #. If a contact re-REGISTERs and no provisional response was seen for that contact INVITE, send INVITE again. Client flow: #. App is in background or not running. #. App receives VoIP push. #. SDK starts and creates internal call representation. #. App shows call UI / CallKit. #. SDK starts SIP account registration and waits for SIP INVITE. #. If SIP INVITE arrives, call continues as standard incoming call. #. If SIP INVITE does not arrive, SDK terminates call after timeout (Android default: 5 seconds, iOS default: 3 seconds; can be overridden by ``secondsToBeRegistered``). Handling user log out --------------------- When a user logs out or removes an account from the app, backend tuple data must be cleaned up. If stale tuples are kept, backend may continue sending intrusive call pushes to devices that no longer have the account configured. Repeated delivery failures or irrelevant pushes can also lead to pushes being blocked by the app. Recommended approaches: #. Use `Account Removal Reporter `_ to report account removal and remove the related tuple (``AppId``, ``DeviceToken``, ``Selector``) on your backend. #. Or implement equivalent custom server ping logic in your app and remove the same tuple data on log out. Account Removal Reporter is triggered on account log out/removal, but not on app uninstall or OS-level app data cleanup. Keep backend cleanup logic resilient to those cases as well. Incoming call payload contract ------------------------------ Use ``verb = NotifyIncomingCall``. .. list-table:: Incoming call payload :header-rows: 1 * - Field - Required - Source - Notes * - ``verb`` - Yes - Backend logic - Must be ``NotifyIncomingCall``. * - ``AppId`` - Yes - Push Token Reporter - Use ``pushappid_incoming_call``. * - ``DeviceToken`` - Yes - Push Token Reporter - Device push token. * - ``Selector`` - Yes - Push Token Reporter - Account selector on device. * - ``Id`` - Yes - PBX / backend - Use PBX call identifier when available; required for push-SIP pairing. * - ``UserDisplayName`` - No - PBX / backend - Caller display label shown in call UI (recommended). * - ``UserName`` - No - PBX / backend - Used with ``Domain`` to compose caller URI (recommended). * - ``Domain`` - No - PBX / backend - Caller SIP domain (recommended). * - ``Media`` - No - Backend logic - Use ``audio`` for audio-only calls. * - ``Badge`` - No - Backend logic - APNS-related; commonly ``0`` for incoming call push. * - ``Sound`` - No - Backend logic - Optional; include when required by your push template. Commonly ``default``. * - ``Timestamp`` - Yes - PBX / backend - Unix timestamp in seconds. Missed/answered-elsewhere payload contract ------------------------------------------- Use: - ``verb = NotifyIncomingCallMissed`` for missed call push. - ``verb = NotifyIncomingCallAnsweredElsewhere`` for answered-elsewhere push. .. list-table:: Missed / answered-elsewhere payload :header-rows: 1 * - Field - Required - Source - Notes * - ``verb`` - Yes - Backend logic - Must be one of the values above. * - ``AppId`` - Yes - Push Token Reporter - Use ``pushappid_other``. * - ``DeviceToken`` - Yes - Push Token Reporter - Device push token. * - ``Selector`` - Yes - Push Token Reporter - Account selector on device. * - ``Id`` - Yes - PBX / backend - Must match call identifier used for the incoming call push. * - ``UserDisplayName`` - No - PBX / backend - Caller display label (recommended). * - ``UserName`` - No - PBX / backend - Used with ``Domain`` to compose caller URI (recommended). * - ``Domain`` - No - PBX / backend - Caller SIP domain (recommended). * - ``Badge`` - No - Backend logic - APNS-related; commonly ``1`` for missed calls. * - ``Sound`` - No - Backend logic - Optional; include when required by your push template. Commonly ``default``. * - ``Timestamp`` - Yes - PBX / backend - Unix timestamp in seconds. Pairing and cancellation logic ------------------------------ Push-SIP pairing prevents duplicate incoming calls in app UI. Pairing requirements: #. Incoming push must include ``Id``. #. If PBX Call-ID is known at push time, use that as ``Id``. #. If PBX Call-ID is not known at push time, use generated ``Id`` and include that same value in SIP INVITE header ``X-Push-ID``. The ``Id`` value is typically controlled by the PBX side. Even when the format is not under your control, presence and consistency are mandatory: - same ``Id`` must be used across related push events, - and SIP signaling must carry matching identifier via ``X-Push-ID`` for pairing. SDK can be notified about canceled incoming call by: #. SIP CANCEL. #. Missed/answered-elsewhere push. #. SIP SIMPLE message with content type ``application/x-acro-missed-call`` and header ``X-Push-ID`` set to the related call identifier. Validated cURL/Python examples ------------------------------ The following examples are production-safe templates with placeholders. Incoming call push (cURL): .. code-block:: bash curl --header "Content-Type: application/json" \ --request POST \ --data '{ "DeviceToken": "", "Selector": "", "AppId": "", "verb": "NotifyIncomingCall", "Id": "", "UserDisplayName": "", "UserName": "", "Domain": "", "Media": "audio", "Timestamp": "", "Badge": "1", "Sound": "default" }' \ https://pnm.cloudsoftphone.com/pnm2/send Incoming call push (Python): .. code-block:: python #!/usr/bin/env python3 import requests payload = { "verb": "NotifyIncomingCall", "DeviceToken": "", "AppId": "", "Selector": "", "UserDisplayName": "", "UserName": "", "Domain": "", "Id": "", "Media": "audio", "Timestamp": "", "Badge": "1", "Sound": "default" } response = requests.post("https://pnm.cloudsoftphone.com/pnm2/send", json=payload, timeout=10) print(response.text)