Introduction
This document provides a guide on how to set up push notifications for incoming calls on the Telnyx Voice SDK Flutter Plugin. It is important to understand how push notifications work in general in relation to Telnyx services. When you connect to the TelnyxClient, you establish a socket connection that can receive and handle incoming calls. However, if your application is in the background or terminated, the socket connection closes and can no longer receive invitations. To address this limitation, the Telnyx Voice SDK uses push notifications to inform the device of incoming calls. When you log in to the TelnyxClient and provide an APNS or FCM token, the SDK sends a push notification to your device whenever an incoming call is received. Note that this notification only indicates that an invitation is on the way. Once you respond to a push notification—for example, by tapping “Accept”—you must handle the logic to launch and reconnect to the TelnyxClient. After reconnection, the socket connection is re-established, and the backend will send the actual invitation to your device. In this scenario, it may be helpful to store the fact that the push notification was accepted so that when the invitation arrives (as soon as the socket is connected), you can automatically accept the call. The following sections provide more detail on how to implement these steps.Handling Foreground and Terminated Calls
When the app is in the foreground you do not need to use push notifications to receive calls, however it still might be beneficial to use CallKit to show native UI for the calls. When the app is terminated you will need to use push notifications to receive calls as described below.Android
The Android platform makes use of Firebase Cloud Messaging in order to deliver push notifications. To receive notifications when receiving calls on your Android mobile device you will have to enable Firebase Cloud Messaging within your application. The Demo app uses the FlutterCallkitIncoming plugin to show incoming calls. To show a notification when receiving a call, you can follow the steps below:- Listen for Background Push Notifications, Implement the
FirebaseMessaging.onBackgroundMessagemethod in yourmainmethod
- Optionally Add the
metadatato CallKitParamsextrafield
- Handle the push notification in the
_firebaseMessagingBackgroundHandlermethod
-
Create a High-Importance Notification Channel (Android 8.0+)
For Android 8.0 (API level 26) and higher, notifications must be assigned to a channel. To ensure incoming call notifications are treated with high priority (e.g., shown as heads-up notifications), you need to create a dedicated channel with maximum importance.
First, add the
flutter_local_notificationspackage to yourpubspec.yaml:Gradle Setup (for Android): Theflutter_local_notificationsplugin may require Java 8+ features. To enable support for these, you need to configure Core Library Desugaring in your Android project. In yourandroid/app/build.gradlefile, ensure the following configurations are present:For projects using Kotlin DSL (build.gradle.kts):(Note: Adjust Java versions anddesugar_jdk_libsversion as needed. Whileflutter_local_notificationsmight show Java 11, Java 8 withdesugar_jdk_libs:2.0.4is often sufficient. For Java 11, usedesugar_jdk_libs:2.1.4or newer.) Then, create the channel, typically during your app’s initialization (e.g., in yourAndroidPushNotificationHandler.initializemethod):Finally, tell the Firebase SDK to use this channel by default for incoming FCM notifications by adding the following meta-data inside the<application>tag in yourandroid/app/src/main/AndroidManifest.xml:(Note: The ID ‘telnyx_call_channel’ must match the ID used in the Dart code.) -
Use the
TelnyxClient.getPushMetaData()method to retrieve the metadata when the app comes to the foreground. This data is only available on 1st access and becomesnullafterward.
- To Handle push calls on foreground, Listen for Call Events and invoke the
handlePushNotificationmethod
Best Practices for Push Notifications on Android
- Request for Notification Permissions for android 13+ devices to show push notifications. More information can be found here
- Push Notifications only work in foreground for apps that are run in
debugmode (You will not receive push notifications when you terminate the app while running in debug mode). - On Foreground calls, you can use the
FirebaseMessaging.onMessage.listenmethod to listen for incoming calls and show a notification.
- To handle push notifications on the background, use the
FirebaseMessaging.onBackgroundMessagemethod to listen for incoming calls and show a notification and make sure to set theTelnyxClient.setPushMetaDatawhen user answers the call.
-
When you call the
telnyxClient.handlePushNotificationit connects to thetelnyxClient, make sure not to call thetelnyxClient.connect()method after this. e.g an Edge case might be if you calltelnyxClient.connect()on Widgetinitmethod it will always call theconnectmethod -
Early Answer/Decline : Users may answer/decline the call too early before a socket connection is established. To handle this situation,
assert if the
IncomingInviteParamsis not null and only accept/decline if this is available.
iOS
The iOS Platform makes use of the Apple Push Notification Service (APNS) and Pushkit in order to deliver and receive push notifications For a detailed tutorial, please visit our official Push Notification DocsNative Swift Code Changes
For a full example please view the Demo Application Example- In AppDelegate.swift setup the ability to receive VOIP push notifications by adding these lines to your application function
Info.plist file to enable push notifications
AppDelegate.swift file in the application function as demonstrated above
- Implement the CallkitIncomingAppDelegate within AppDelegate so that you can action on calls that are received. Callin action.fulfill() will allows us to listen to the events and act on them in our dart code.
- Register / Invalidate the push device token for iOS within AppDelegate.swift class
- For foreground calls to work, you need to register with callkit on the restorationHandler delegate function. You can also choose to register with callkit using iOS official documentation on CallKit.
- Listen for incoming calls in AppDelegate.swift class and grab the relevant metadata from the push payload to pass to showCallkitIncoming (eg. the callerName, callerNumber, callID, etc)
Dart / Flutter Code Changes
- Listen for Call Events and invoke the
handlePushNotificationmethod
- Sending an invitation on iOS when using Callkit. Because of the native changes we made in AppDelegate.swift related to WebRTC audio handling, we now need to start a callkit call whenever we send out an invitation so that we can have an active audio session. This simply means starting a callkit call whenever we send out an invitation. Your call() method could look something like:
Handling Late Notifications
If notifications arrive very late due to no internet connectivity, It is good to always flag it as a missed call. You can do that using the code snippet below :Handling Background Calls
Background calls are handled slightly differently than terminated calls as the application is not disconnected and in a fresh state. The application is in the background and the socket connection is still active - meaning we can receive an invite on the socket but not be notified about it. We can get around this by listening to application lifecycle events and checking if the application is in the background, and if so disconnecting from the socket manually. This way we can listen to the push notification and reconnect to the socket when the user accepts the call. You can do this by either manually creating a lifecycle state listener in dart using the WidgetsBindingObserver or using a library like flutter_fgbg to listen to the application lifecycle events. An implementation could look like this:Best Practices for Push Notifications on iOS
- Push Notifications only work in foreground for apps that are run in
debugmode (You will not receive push notifications when you terminate the app while running in debug mode). Make sure you are inreleasemode. Preferably test using Testfight or Appstore. To test if push notifications are working, disconnect the telnyx client (while app is in foreground) and make a call to the device. You should receive a push notification.
New Push Notification Features
Simplified Push Decline Method
The Telnyx Flutter Voice SDK now includes a significantly simplified method for declining push notifications, eliminating the complex workflow previously required.Previous Method (Legacy)
Previously, declining a push notification required:- Logging back into the socket
- Listening for events
- Waiting for an INVITE message
- Manually sending a bye message to decline the call
- Handling bye responses and cleanup
New Simplified Method
The SDK now handles push call decline automatically when you set the decline flag. The process is streamlined to:- Set the decline flag when the user declines the push notification:
- Call handlePushNotification as usual:
What Happens Internally
WhenisDecline: true is set, the SDK automatically:
- Connects to the Telnyx socket
- Sends a login message with
decline_push: trueparameter - The backend handles the call termination
- Automatically disconnects from the socket
- No need to wait for INVITE messages or handle bye responses
Benefits
- Simplified Implementation: Reduces code complexity significantly
- Automatic Cleanup: SDK handles all socket management and cleanup
- Reliable: Eliminates potential race conditions and edge cases
- Consistent: Works the same way across Android and iOS platforms
10-Second Answer Timeout Mechanism
The SDK now includes an automatic timeout mechanism to handle cases where an INVITE message is missing after accepting a VoIP push notification.The Problem
Sometimes after accepting a push notification, the INVITE message may not arrive due to:- Network connectivity issues
- Server-side problems
- Message delivery failures
- Backend processing delays
The Solution
The SDK now implements a 10-second timeout mechanism that:- Starts automatically when a push notification is accepted (
isAnswer: true) - Monitors for incoming INVITE messages
- Automatically terminates the call if no INVITE arrives within 10 seconds
- Sends a bye message with
ORIGINATOR_CANCELtermination reason (SIP code 487)
How It Works
Normal Flow (INVITE Received):- User accepts push notification
- SDK starts 10-second timer
- INVITE message arrives on socket
- Timer is cancelled
- Call proceeds normally
- User accepts push notification
- SDK starts 10-second timer
- No INVITE message arrives within 10 seconds
- Timer expires
- SDK automatically sends bye message with
ORIGINATOR_CANCEL - Call is terminated gracefully
Implementation Details
The timeout mechanism is completely automatic and requires no additional code. It activates when:TelnyxClient.setPushMetaData()is called withisAnswer: truehandlePushNotification()is called- The SDK connects and waits for an INVITE
Handling Timeout Events
Your existing call event handlers will receive the termination event:Benefits
- Prevents Indefinite Waiting: Ensures calls don’t hang indefinitely
- Automatic Recovery: No manual intervention required
- Clear Termination Reason: Provides specific reason code for debugging
- Consistent Behavior: Works across all platforms and scenarios
- User Experience: Prevents users from waiting for calls that will never connect
Configuration
The timeout duration is fixed at 10 seconds and is not currently configurable. This duration was chosen to balance between:- Allowing sufficient time for normal INVITE delivery
- Preventing excessive wait times for users
- Accommodating network latency variations
Migration Guide
For Existing Decline Implementations
If you have existing push decline logic, you can simplify it: Before:For Timeout Handling
No migration is required for the timeout feature - it works automatically with existing code. However, you may want to add specific handling forORIGINATOR_CANCEL termination reasons in your bye message handlers.
Testing the New Features
Testing Push Decline
- Receive a push notification while app is terminated
- Decline the call from the notification
- Verify the call is declined without complex socket operations
- Check logs to confirm
decline_push: trueparameter is sent
Testing Answer Timeout
- Accept a push notification
- Simulate network issues or server problems that prevent INVITE delivery
- Wait 10 seconds
- Verify the call is automatically terminated with
ORIGINATOR_CANCEL - Check that your app handles the termination gracefully
Troubleshooting
Push Decline Issues
- Ensure you’re setting
isDecline: trueinsetPushMetaData - Verify
handlePushNotificationis called after setting the metadata - Check network connectivity for the decline message to be sent
Timeout Issues
- The timeout only applies to accepted push notifications (
isAnswer: true) - Normal incoming calls (not from push) are not affected by this timeout
- If INVITE arrives after timeout, it will be ignored as the call is already terminated