APNs Push
iOS SDK

APNs Push

Register APNs device tokens with Gleam and route Gleam notification payloads back to the Portal.

What Your App Owns

The host app owns notification permission, APNs capability setup, UNUserNotificationCenterDelegate wiring, and foreground presentation policy.

Gleam owns project-scoped device token registration, unregistering stale tokens, and converting Gleam payloads into Portal URLs.

Before registering tokens

  • Enable Push Notifications in the app target.
  • Request notification authorization from the user.
  • Configure APNs credentials in Gleam dashboard Settings -> Feature -> iOS Push (APNs).
  • Make sure the SDK session is authenticated.

Configure APNs In The Gleam Dashboard

Open the Gleam dashboard, select the project, then go to Settings -> Feature -> iOS Push (APNs). Only project owners can save these credentials.

The iOS app never receives the APNs private key. The dashboard values let the Gleam backend create a token-based APNs connection, find compatible device tokens, and send notifications for this project.

Dashboard fieldWhere to get itWhat Gleam uses it for
APNs Key IDApple Developer -> Certificates, Identifiers & Profiles -> Keys -> your APNs Auth Key detail page. Copy the 10-character Key ID.Identifies the signing key in the APNs provider token.
Apple Team IDApple Developer -> Membership details -> Team ID.Identifies the Apple Developer team that owns the APNs key.
Bundle IDXcode -> Target -> General -> Bundle Identifier, or Info.plist CFBundleIdentifier. It must match the app that registers device tokens.Used as the APNs topic and to filter registered device tokens by app bundle.
.p8 private key PEMApple Developer -> Keys -> create an Apple Push Notifications service key -> Download. Apple only shows this download once.Signs the backend APNs provider token. Do not put this file in the iOS app.
APNs environmentChoose the environment that matches how the app is signed and distributed.Selects the APNs host: sandbox for development delivery, production for distribution delivery.

Do not ship APNs credentials in the app

The iOS app only registers APNs device tokens with Gleam. APNs Key ID, Team ID, Bundle ID, private key, and environment are backend configuration values in the Gleam dashboard.

Register Device Tokens

After the dashboard is configured and the user has granted notification permission, forward the APNs device token to Gleam when iOS calls didRegisterForRemoteNotificationsWithDeviceToken.

The SDK sends the token with the current Gleam session and the app bundle ID. Gleam stores it project-scoped so replies and announcements can target the right user and device.

Register APNs token

SwiftUI apps still receive APNs callbacks through UIApplicationDelegate; install the delegate with UIApplicationDelegateAdaptor.

import SwiftUI
import UIKit
import UserNotifications
import GleamSDK

@main
struct MyApp: App {
  @UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate

  var body: some Scene {
    WindowGroup {
      ContentView()
    }
  }
}

final class AppDelegate: NSObject, UIApplicationDelegate {
  func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
  ) -> Bool {
    UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, _ in
      guard granted else { return }
      DispatchQueue.main.async {
        application.registerForRemoteNotifications()
      }
    }
    return true
  }

  func application(
    _ application: UIApplication,
    didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
  ) {
    Task {
      do {
        try await Gleam.shared.registerDeviceToken(deviceToken)
      } catch {
        print("Gleam push registration failed: \(error)")
      }
    }
  }
}

Route Notification Payloads

When a user taps a Gleam notification, ask the SDK to turn the payload into a Portal URL, then present that URL through your existing Portal flow.

Handle tapped notification

The delegate callback is the same in SwiftUI and UIKit. If you keep the Portal in-app, replace openURL with your existing authenticated Portal presenter.

import SwiftUI
import UIKit
import UserNotifications
import GleamSDK

@MainActor
final class GleamPushRouter: ObservableObject {
  @Published var portalURL: URL?
}

@main
struct MyApp: App {
  @UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
  @StateObject private var pushRouter = GleamPushRouter()

  var body: some Scene {
    WindowGroup {
      RootView()
        .environmentObject(pushRouter)
        .onAppear { AppDelegate.pushRouter = pushRouter }
    }
  }
}

struct RootView: View {
  @EnvironmentObject private var pushRouter: GleamPushRouter
  @Environment(\.openURL) private var openURL

  var body: some View {
    ContentView()
      .onChange(of: pushRouter.portalURL) { _, url in
        guard let url else { return }
        openURL(url)
        pushRouter.portalURL = nil
      }
  }
}

final class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {
  static weak var pushRouter: GleamPushRouter?

  func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
  ) -> Bool {
    UNUserNotificationCenter.current().delegate = self
    return true
  }

  func userNotificationCenter(
    _ center: UNUserNotificationCenter,
    didReceive response: UNNotificationResponse
  ) async {
    guard let url = try? Gleam.shared.url(
      forRemoteNotification: response.notification.request.content.userInfo
    ) else {
      return
    }

    await MainActor.run {
      Self.pushRouter?.portalURL = url
    }
  }
}

Unregister On Logout

When the current app user changes, unregister the previous token before clearing the SDK session. This prevents the old user from receiving future project notifications on this device.

Logout cleanup

try await Gleam.shared.unregisterCurrentDeviceToken()
try Gleam.shared.clearSession()