Skip to main content
Integrate Apple Push Notifications into your iOS app to deliver instant updates about messages, mentions, and community activities, keeping users engaged even when your app isn’t active.
Push notifications require proper APNs certificate setup and valid entitlements. Production certificates work with App Store and TestFlight builds only.

Prerequisites

Before implementing push notifications, ensure you have:

Apple Developer Account

Active Apple Developer Program membership

Xcode Project

iOS project with proper Bundle ID configured

social.plus SDK

social.plus iOS SDK properly integrated

Push Capability

Push Notifications capability enabled in Xcode

Step 1: Generate APNs Certificate

Create Certificate in Apple Developer Console

  1. Navigate to Certificates
  2. Select Certificate Type
    • Choose Apple Push Notification service SSL (Sandbox & Production)
    • This universal certificate works for both development and production
Universal Certificate: The Sandbox & Production certificate eliminates the need for separate development and production certificates, simplifying your workflow.
  1. Configure Certificate
    • Select your app’s Bundle ID from the dropdown
    • Follow Apple’s certificate creation wizard
    • Download the generated .cer file

Convert Certificate to .p12 Format

  1. Install Certificate
    • Double-click the downloaded .cer file
    • Keychain Access will open automatically
    • The certificate will be installed in your Login keychain
  2. Export as .p12
    • In Keychain Access, navigate to Login → My Certificates
    • Locate your Apple Push Services certificate
    • Right-click and select Export “Apple Push Services…”
    • Choose .p12 format and set a secure password
    • Save the file (you’ll need both the file and password for console upload)
Certificate Security: Store your .p12 file and password securely. When you create a new certificate, the previous one is automatically revoked.

Step 2: Upload to social.plus Console

  1. Access Console
  2. Upload Certificate
    • Click Upload Certificate in the iOS section
    • Select your .p12 file
    • Enter the password you set during export
    • Click Save to complete the setup
Certificate Validation: The console will validate your certificate upon upload. If there are issues, check that the Bundle ID matches your app configuration.

Step 3: iOS App Configuration

Enable Push Notifications Capability

In Xcode, enable the Push Notifications capability:
  1. Select your app target
  2. Go to Signing & Capabilities
  3. Click + Capability
  4. Add Push Notifications

Request Permission and Handle Registration

import UIKit
import UserNotifications
import AmitySDK

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
        // Configure notification center
        UNUserNotificationCenter.current().delegate = self
        
        // Request notification permissions
        requestNotificationPermissions()
        
        return true
    }
    
    private func requestNotificationPermissions() {
        UNUserNotificationCenter.current().requestAuthorization(
            options: [.alert, .badge, .sound, .provisional]
        ) { granted, error in
            if let error = error {
                print("Push notification permission error: \(error.localizedDescription)")
                return
            }
            
            if granted {
                print("Push notifications authorized")
                DispatchQueue.main.async {
                    UIApplication.shared.registerForRemoteNotifications()
                }
            } else {
                print("Push notifications denied")
            }
        }
    }
}

Handle Device Token Registration

extension AppDelegate {
    
    func application(_ application: UIApplication, 
                    didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        
        // Convert token to string
        let tokenString = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
        print("Device token: \(tokenString)")
        
        // Register with social.plus SDK
        registerDeviceWithAmitySDK(token: tokenString)
    }
    
    func application(_ application: UIApplication, 
                    didFailToRegisterForRemoteNotificationsWithError error: Error) {
        print("Failed to register for remote notifications: \(error.localizedDescription)")
        
        // Handle registration failure
        handleRegistrationFailure(error: error)
    }
    
    private func registerDeviceWithAmitySDK(token: String) {
        AmityManager.shared.client?.registerDevice(
            withToken: token,
            completion: { [weak self] success, error in
                if success {
                    print("Successfully registered device with Amity SDK")
                    self?.onDeviceRegistrationSuccess(token: token)
                } else {
                    print("Failed to register device: \(error?.localizedDescription ?? "Unknown error")")
                    self?.handleRegistrationFailure(error: error)
                }
            }
        )
    }
    
    private func onDeviceRegistrationSuccess(token: String) {
        // Store token locally for reference
        UserDefaults.standard.set(token, forKey: "device_token")
        
        // Update UI or perform additional setup
        NotificationCenter.default.post(
            name: .deviceTokenRegistered,
            object: nil,
            userInfo: ["token": token]
        )
    }
    
    private func handleRegistrationFailure(error: Error?) {
        // Implement retry logic
        DispatchQueue.main.asyncAfter(deadline: .now() + 30) {
            UIApplication.shared.registerForRemoteNotifications()
        }
    }
}

// MARK: - Notification Names
extension Notification.Name {
    static let deviceTokenRegistered = Notification.Name("deviceTokenRegistered")
}

Best Practices

Strategic Permission Requests: Don’t ask for notification permissions immediately on app launch. Request them at meaningful moments.
// Good: Request after user joins a channel
func didJoinChannel() {
    NotificationPermissionManager.shared.requestPermissionIfNeeded { granted in
        if granted {
            print("User enabled notifications for channel updates")
        }
    }
}

// Bad: Request immediately on app launch
func application(_ application: UIApplication, didFinishLaunchingWithOptions...) -> Bool {
    // Don't do this immediately
    // UNUserNotificationCenter.current().requestAuthorization(...)
    return true
}
Robust Token Management: Implement comprehensive error handling for token registration failures.
class TokenRetryManager {
    private var retryCount = 0
    private let maxRetries = 3
    private let retryDelay: TimeInterval = 30
    
    func registerWithRetry(token: String) {
        AmityManager.shared.client?.registerDevice(withToken: token) { [weak self] success, error in
            if success {
                self?.retryCount = 0
                print("Token registered successfully")
            } else if let self = self, self.retryCount < self.maxRetries {
                self.retryCount += 1
                DispatchQueue.main.asyncAfter(deadline: .now() + self.retryDelay) {
                    self.registerWithRetry(token: token)
                }
            } else {
                print("Failed to register token after \(self?.maxRetries ?? 0) attempts")
            }
        }
    }
}
Notification State Tracking: Keep track of notification-related state across app lifecycle.
class NotificationStateManager {
    static let shared = NotificationStateManager()
    
    private(set) var isRegistered = false
    private(set) var deviceToken: String?
    private(set) var authorizationStatus: UNAuthorizationStatus = .notDetermined
    
    func updateRegistrationState(isRegistered: Bool, token: String?) {
        self.isRegistered = isRegistered
        self.deviceToken = token
        
        // Notify observers
        NotificationCenter.default.post(
            name: .notificationStateChanged,
            object: self
        )
    }
    
    func updateAuthorizationStatus(_ status: UNAuthorizationStatus) {
        self.authorizationStatus = status
    }
}

extension Notification.Name {
    static let notificationStateChanged = Notification.Name("notificationStateChanged")
}
Comprehensive Testing: Test push notifications across different scenarios and device states.
#if DEBUG
class NotificationTestingHelper {
    static func logNotificationStatus() {
        UNUserNotificationCenter.current().getNotificationSettings { settings in
            print("Authorization Status: \(settings.authorizationStatus.rawValue)")
            print("Alert Setting: \(settings.alertSetting.rawValue)")
            print("Badge Setting: \(settings.badgeSetting.rawValue)")
            print("Sound Setting: \(settings.soundSetting.rawValue)")
        }
    }
    
    static func simulateNotification() {
        let content = UNMutableNotificationContent()
        content.title = "Test Notification"
        content.body = "This is a test notification"
        content.sound = .default
        
        let request = UNNotificationRequest(
            identifier: "test",
            content: content,
            trigger: UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
        )
        
        UNUserNotificationCenter.current().add(request)
    }
}
#endif

Testing & Debugging

Debug Notification Status

class NotificationDebugger {
    
    static func printDebugInfo() {
        print("=== Notification Debug Info ===")
        
        // Check authorization status
        UNUserNotificationCenter.current().getNotificationSettings { settings in
            print("Authorization Status: \(settings.authorizationStatus)")
            print("Alert Setting: \(settings.alertSetting)")
            print("Badge Setting: \(settings.badgeSetting)")
            print("Sound Setting: \(settings.soundSetting)")
        }
        
        // Check device token
        if let token = UserDefaults.standard.string(forKey: "device_token") {
            print("Stored Device Token: \(token)")
        } else {
            print("No device token stored")
        }
        
        // Check badge count
        print("Current Badge Count: \(UIApplication.shared.applicationIconBadgeNumber)")
    }
    
    static func testLocalNotification() {
        let content = UNMutableNotificationContent()
        content.title = "Test Notification"
        content.body = "This is a test notification from your app"
        content.sound = .default
        content.badge = 1
        
        let request = UNNotificationRequest(
            identifier: UUID().uuidString,
            content: content,
            trigger: UNTimeIntervalNotificationTrigger(timeInterval: 2, repeats: false)
        )
        
        UNUserNotificationCenter.current().add(request) { error in
            if let error = error {
                print("Failed to schedule test notification: \(error)")
            } else {
                print("Test notification scheduled")
            }
        }
    }
}

Troubleshooting

Notifications Not Received:
  1. Certificate Issues: Verify .p12 certificate is valid and uploaded correctly
  2. Build Configuration: Use TestFlight or App Store builds (not debug builds)
  3. Permissions: Check notification permissions in iOS Settings
  4. Token Registration: Ensure device token is successfully registered with social.plus SDK
  5. Bundle ID Mismatch: Verify certificate Bundle ID matches your app
// Debug token registration
func debugTokenRegistration() {
    print("App Bundle ID: \(Bundle.main.bundleIdentifier ?? "Unknown")")
    
    if let token = UserDefaults.standard.string(forKey: "device_token") {
        print("Current Token: \(token)")
    }
    
    // Re-register token
    UIApplication.shared.registerForRemoteNotifications()
}
Certificate Validation Issues:
  1. Expired Certificate: Check certificate expiration date
  2. Wrong Environment: Ensure using Sandbox & Production certificate
  3. Password Incorrect: Verify .p12 password in console
  4. Bundle ID Mismatch: Certificate must match exact Bundle ID
// Validate certificate setup
func validateCertificateSetup() {
    print("Bundle ID: \(Bundle.main.bundleIdentifier ?? "Not found")")
    print("Push capability enabled: \(Bundle.main.object(forInfoDictionaryKey: "UIBackgroundModes") != nil)")
}
Platform-Specific Problems:
  1. Simulator Limitations: Push notifications don’t work in iOS Simulator
  2. Debug Build Restrictions: Debug builds can’t receive production push notifications
  3. Background App Refresh: Check if disabled in iOS Settings
  4. Do Not Disturb Mode: May suppress notification display
// Check background refresh status
func checkBackgroundRefreshStatus() {
    let status = UIApplication.shared.backgroundRefreshStatus
    switch status {
    case .available:
        print("Background refresh available")
    case .denied:
        print("Background refresh denied")
    case .restricted:
        print("Background refresh restricted")
    @unknown default:
        print("Unknown background refresh status")
    }
}
Production Requirements: Push notifications only work with production builds distributed through App Store or TestFlight. Debug builds from Xcode cannot receive push notifications.