Push Notification

Push Notifications Guide

Complete guide to setting up push notifications for incoming calls in your Kasookoo iOS SDK integration.


Overview

Push notifications are essential for receiving incoming calls when your app is in the background or closed. The Kasookoo SDK uses Firebase Cloud Messaging (FCM) to deliver push notifications, which then trigger CallKit to show the native iOS incoming call screen.

Flow Diagram

Caller's Device                Kasookoo Backend              Your Device
                                                        
   ┌─────┐                       ┌─────┐                    ┌─────┐
   │ App │──── start call ──────→│Server│                    │ App │
   └─────┘                       └──┬──┘                    └─────┘
                                    │                          ↑
                                    │  trigger push            │
                                    ▼                          │
                              ┌──────────┐                     │
                              │ Firebase │                     │
                              │   FCM    │                     │
                              └────┬─────┘                     │
                                   │                           │
                                   ▼                           │
                              ┌──────────┐                     │
                              │  Apple   │                     │
                              │   APNs   │                     │
                              └────┬─────┘                     │
                                   │                           │
                                   └─── delivered ─────────────┘
                                                       │
                                                       ▼
                                              ┌───────────────┐
                                              │ CallKit shows │
                                              │ incoming call │
                                              └───────────────┘

Prerequisites

Before starting, ensure you have:

  • ✅ Paid Apple Developer account ($99/year) — Required for push notifications
  • ✅ Firebase project with iOS app added
  • ✅ APNs Authentication Key (.p8 file) generated and uploaded to Firebase
  • ✅ GoogleService-Info.plist added to your Xcode project
  • ✅ Real iOS device for testing (push doesn't work on simulator)

See Installation Guide for setup details.


Two Approaches to Push Notifications

The SDK supports both standard and VoIP push notifications:

1. Standard Push (Notification Center)

Behavior: Notification appears in iOS Notification Center
Use case: Messages, low-priority alerts
Trigger: User taps the notification → App launches

2. VoIP Push (CallKit)

Behavior: iOS shows native incoming call screen (like Phone app)
Use case: Incoming voice calls
Trigger: Push arrives → CallKit shows full-screen UI

For incoming calls, use VoIP Push for the best user experience.


Setup: Standard Push Notifications

Step 1: Configure Capabilities

In Xcode, ensure these capabilities are added to your target:

  • ✅ Push Notifications
  • ✅ Background Modes → Voice over IP + Audio

Step 2: Initialize Firebase in AppDelegate

import UIKit
import FirebaseCore
import FirebaseMessaging
import UserNotifications
import KasookooSDK

class AppDelegate: NSObject, UIApplicationDelegate {
    
    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
    ) -> Bool {
        // Configure Firebase
        FirebaseApp.configure()
        
        // Set delegates
        Messaging.messaging().delegate = self
        UNUserNotificationCenter.current().delegate = self
        
        // Request push permissions
        requestNotificationPermissions(application: application)
        
        return true
    }
    
    private func requestNotificationPermissions(application: UIApplication) {
        UNUserNotificationCenter.current().requestAuthorization(
            options: [.alert, .badge, .sound]
        ) { granted, error in
            if granted {
                print("✅ Push permissions granted")
                DispatchQueue.main.async {
                    application.registerForRemoteNotifications()
                }
            } else {
                print("❌ Push permissions denied")
            }
        }
    }
    
    // Called when APNs token received
    func application(
        _ application: UIApplication,
        didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
    ) {
        Messaging.messaging().apnsToken = deviceToken
    }
    
    // Called if APNs registration fails
    func application(
        _ application: UIApplication,
        didFailToRegisterForRemoteNotificationsWithError error: Error
    ) {
        print("❌ Failed to register for remote notifications: \(error)")
    }
}

Step 3: Receive FCM Token

Implement MessagingDelegate to receive the FCM token:

extension AppDelegate: MessagingDelegate {
    
    func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
        guard let fcmToken = fcmToken else {
            print("❌ No FCM token received")
            return
        }
        
        print("✅ FCM token: \(fcmToken)")
        
        // Register with Kasookoo SDK
        Task {
            do {
                try await KasookooSDK.shared.notifications.registerDevice(
                    fcmToken: fcmToken,
                    userType: .customer  // or .agent, .driver, etc.
                )
                print("✅ Device registered with Kasookoo")
            } catch {
                print("❌ Device registration failed: \(error)")
            }
        }
    }
}

Step 4: Handle Incoming Notifications

extension AppDelegate: UNUserNotificationCenterDelegate {
    
    // Called when notification arrives while app is in foreground
    func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        willPresent notification: UNNotification,
        withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
    ) {
        let userInfo = notification.request.content.userInfo
        
        // Forward to SDK
        Task {
            try await KasookooSDK.shared.notifications.handlePush(payload: userInfo)
        }
        
        // Show banner + sound while app is foreground
        completionHandler([.banner, .sound, .badge])
    }
    
    // Called when user taps a notification
    func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        didReceive response: UNNotificationResponse,
        withCompletionHandler completionHandler: @escaping () -> Void
    ) {
        let userInfo = response.notification.request.content.userInfo
        
        Task {
            try await KasookooSDK.shared.notifications.handlePush(payload: userInfo)
        }
        
        completionHandler()
    }
}

Setup: VoIP Push for Incoming Calls (Recommended)

For incoming calls, use PushKit which provides better UX (CallKit native UI).

Step 1: Add PushKit Capability

VoIP push uses the same Background Modes → Voice over IP capability.

Step 2: Set Up PushKit in AppDelegate

import PushKit

class AppDelegate: NSObject, UIApplicationDelegate {
    
    var voipRegistry: PKPushRegistry?
    
    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
    ) -> Bool {
        FirebaseApp.configure()
        
        // Set up VoIP push
        setupVoIPPush()
        
        return true
    }
    
    private func setupVoIPPush() {
        voipRegistry = PKPushRegistry(queue: DispatchQueue.main)
        voipRegistry?.delegate = self
        voipRegistry?.desiredPushTypes = [.voIP]
    }
}

// MARK: - PushKit Delegate

extension AppDelegate: PKPushRegistryDelegate {
    
    func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
        guard type == .voIP else { return }
        
        // VoIP push token
        let token = pushCredentials.token.map { String(format: "%02x", $0) }.joined()
        print("✅ VoIP push token: \(token)")
        
        // Register with backend
        Task {
            try await KasookooSDK.shared.notifications.registerDevice(
                voipToken: token,
                userType: .customer
            )
        }
    }
    
    func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) async {
        guard type == .voIP else { return }
        
        // Forward to SDK — this will trigger CallKit native UI
        try? await KasookooSDK.shared.notifications.handleVoIPPush(payload: payload.dictionaryPayload)
    }
}

Step 3: Configure CallKit Provider

The SDK handles CallKit internally. To customize:

let callKitConfig = CallKitConfig(
    appDisplayName: "Your App Name",
    ringtoneSound: "ringtone.caf",  // Optional custom ringtone
    supportsVideo: false,
    supportsHolding: true,
    iconTemplate: UIImage(named: "callkit-icon")
)

KasookooSDK.shared.configure(callKit: callKitConfig)

Testing Push Notifications

Method 1: Firebase Console (Manual Test)

Test that push notifications reach your device without setting up backend trigger:

  1. Run your app on a real device
  2. Note the FCM token printed to console
  3. Open Firebase Console
  4. Go to Cloud Messaging → Send your first message
  5. Enter:
    • Notification title: "Test Push"
    • Notification body: "Hello from Firebase!"
  6. Click Send test message
  7. Paste your FCM token → Test

✅ Notification should appear on your device.

Method 2: APNs Direct Test (Production)

For more advanced testing, use Apple's APNs directly via tools like:

This verifies APNs delivery without going through Firebase.

Method 3: Real Backend Test

Once everything is set up, your backend can trigger pushes:

curl -X POST https://your-backend.com/test-push \
  -H "Content-Type: application/json" \
  -d '{
    "device_token": "YOUR_FCM_TOKEN",
    "type": "incoming_call",
    "caller_name": "Alice"
  }'

Push Payload Structure

The Kasookoo SDK expects this payload format for incoming calls:

{
  "data": {
    "type": "incoming_call",
    "call_id": "call_abc123",
    "room_name": "demo_room_xyz",
    "caller_name": "Alice Smith",
    "caller_email": "[email protected]",
    "caller_phone": "+1234567890",
    "caller_type": "agent",
    "callee_email": "[email protected]",
    "is_video": false,
    "is_call_recording": false
  },
  "apns": {
    "payload": {
      "aps": {
        "content-available": 1,
        "alert": {
          "title": "Incoming call",
          "body": "Alice is calling..."
        },
        "sound": "ringtone.caf",
        "badge": 1
      }
    }
  }
}

Required Fields

  • type — Must be "incoming_call" for call notifications
  • call_id — Unique identifier for the call
  • room_name — LiveKit room to join
  • caller_name — Display name shown in CallKit UI

Optional Fields

  • caller_email, caller_phone — For caller identification
  • caller_type — Role of the caller (customer, agent, driver, bot)
  • is_video — Whether this is a video call (default: false)
  • is_call_recording — Whether call should be recorded

Common Issues & Troubleshooting

Issue: No FCM token received

Causes:

  • Firebase not configured properly
  • Push capability not enabled in Apple Developer
  • Running on simulator (use real device)

Solutions:

  1. Verify GoogleService-Info.plist is in Xcode project
  2. Verify FirebaseApp.configure() is called in didFinishLaunchingWithOptions
  3. Check that Push Notifications capability is enabled
  4. Test on real device

Issue: APNs token received but no FCM token

Causes:

  • APNs .p8 key not uploaded to Firebase
  • Wrong Key ID or Team ID in Firebase

Solutions:

  1. Re-check Firebase Console → Project Settings → Cloud Messaging
  2. Verify APNs Authentication Key is uploaded
  3. Verify Key ID matches the one from Apple Developer
  4. Verify Team ID matches your Apple Developer Team ID

Issue: Push works in development but not production

Causes:

  • APNs key only configured for sandbox
  • Wrong publishable key (pk_test_ vs pk_live_)

Solutions:

  1. Verify APNs key supports both Sandbox and Production
  2. Use pk_live_ key for production builds
  3. Use environment: .production in SDK config

Issue: Push received but no CallKit UI

Causes:

  • Payload format incorrect
  • VoIP push not configured properly
  • Background modes not enabled

Solutions:

  1. Verify payload includes "type": "incoming_call"
  2. Use VoIP push (PushKit) for calls, not standard push
  3. Verify Background Modes → Voice over IP is enabled

Issue: Push delivers slowly or sporadically

Causes:

  • iOS aggressive battery management
  • App was force-quit by user (iOS limits pushes)
  • Low power mode

Solutions:

  1. Use VoIP push (high priority, bypasses battery management)
  2. Ensure app isn't force-quit by user
  3. Test on charged device

Best Practices

1. Always Request Permission Once

Don't repeatedly ask for push permissions:

// Check current status before requesting
UNUserNotificationCenter.current().getNotificationSettings { settings in
    if settings.authorizationStatus == .notDetermined {
        // Request permission
    }
}

2. Refresh FCM Token Regularly

FCM tokens can change. Always re-register on app launch:

func application(_ application: UIApplication, didFinishLaunchingWithOptions...) -> Bool {
    // Get current FCM token and re-register
    Messaging.messaging().token { token, error in
        if let token = token {
            // Re-register with backend
        }
    }
}

3. Handle Token Updates

When FCM token refreshes:

func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
    // Always re-register with backend when token changes
    Task {
        try await sdk.notifications.registerDevice(fcmToken: fcmToken!, userType: .customer)
    }
}

4. Test Different App States

Test push delivery in:

  • ✅ App in foreground
  • ✅ App in background
  • ✅ App killed (force-quit)
  • ✅ Phone locked
  • ✅ Low power mode
  • ✅ Do Not Disturb mode

5. Provide Fallback UI

If push fails or isn't permitted, provide alternative ways to know about calls:

  • In-app notification banner
  • Poll-based call check (less ideal but works)
  • Webhook callbacks for important events

Privacy & Compliance

App Tracking Transparency

If your app uses analytics with notifications, you may need:

<key>NSUserTrackingUsageDescription</key>
<string>This identifier will be used to improve call quality.</string>

GDPR Compliance

  • Get explicit consent for push notifications
  • Document what data is sent in push payloads
  • Provide opt-out mechanism
  • Honor users' deletion requests

Push Payload Security

⚠️ Push payloads can be intercepted at the OS level. Don't include:

  • Passwords
  • API keys
  • Personal medical information
  • Financial details

Use call IDs and reference data, not sensitive payload content.


Next Steps


Need Help?



Did this page help you?