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 (
.p8file) 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:
- Run your app on a real device
- Note the FCM token printed to console
- Open Firebase Console
- Go to Cloud Messaging → Send your first message
- Enter:
- Notification title: "Test Push"
- Notification body: "Hello from Firebase!"
- Click Send test message
- 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 notificationscall_id— Unique identifier for the callroom_name— LiveKit room to joincaller_name— Display name shown in CallKit UI
Optional Fields
caller_email,caller_phone— For caller identificationcaller_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:
- Verify
GoogleService-Info.plistis in Xcode project - Verify
FirebaseApp.configure()is called indidFinishLaunchingWithOptions - Check that Push Notifications capability is enabled
- Test on real device
Issue: APNs token received but no FCM token
Causes:
- APNs
.p8key not uploaded to Firebase - Wrong Key ID or Team ID in Firebase
Solutions:
- Re-check Firebase Console → Project Settings → Cloud Messaging
- Verify APNs Authentication Key is uploaded
- Verify Key ID matches the one from Apple Developer
- 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_vspk_live_)
Solutions:
- Verify APNs key supports both Sandbox and Production
- Use
pk_live_key for production builds - Use
environment: .productionin SDK config
Issue: Push received but no CallKit UI
Causes:
- Payload format incorrect
- VoIP push not configured properly
- Background modes not enabled
Solutions:
- Verify payload includes
"type": "incoming_call" - Use VoIP push (PushKit) for calls, not standard push
- 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:
- Use VoIP push (high priority, bypasses battery management)
- Ensure app isn't force-quit by user
- 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
- 📖 Receiving Calls Guide — Full incoming call flow
- 🎯 CallKit Best Practices — Customize incoming call UI
- 🔧 Troubleshooting — Common issues
Need Help?
Updated 15 days ago
