iOS Quickstart
Install the SDK, register a device, and send your first push.
#Prerequisites
- Xcode 15 or later
- A paid Apple Developer account (required for APNs)
- A physical iOS 16+ device — the Simulator has no APNs and can never receive a real push
- Your Pushlane Tenant ID, Ingest URL, and write-key — copy them from Settings → Install SDK in the dashboard
#1. Install the SDK (Swift Package Manager)
In Xcode, go to File → Add Package Dependencies…, paste the URL below, and set the dependency rule to Up to Next Major Version.
https://github.com/Big-Terence/pushlane-ios-sdkWhen the package resolves, add the products to the right targets:
.package(url: "https://github.com/Big-Terence/pushlane-ios-sdk", from: "0.1.0")
// Then add the products you need to each target:
// App target → PushlaneCore, PushlanePush, PushlaneInApp
// NSE target → PushlaneNotificationService#2. Enable Push Notifications in Xcode
This step is easy to miss and is the most common reason a device never produces a token.
- Select your app target in Xcode.
- Go to Signing & Capabilities.
- Click + Capability and search for Push Notifications.
- Double-click to add it.
UIApplication.registerForRemoteNotifications() silently fails on a real device with no valid aps-environment entitlement. No entitlement = no device token = the Verify step never turns green, regardless of your APNs key.Optional: if you plan to send silent pushes (content-available: 1), also add the Background Modes capability and tick Remote notifications.
#3. Configure the SDK
Call the four setup functions in didFinishLaunchingWithOptions — order matters. Identify before tracking so the first event is always attributed to a user.
import PushlaneCore
import PushlanePush
import PushlaneInApp
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
Pushlane.configure(
apiBase: URL(string: "https://<your-ingest-url>")!,
tenantId: "<your-tenant-id>",
publishableKey: "lpk_live_…", // from Settings → Install SDK
appGroup: "group.com.yourapp.pushlane" // shared with your NSE target
)
Pushlane.identify(currentUser.id) // your stable external user id (no label — positional)
PushlaneInApp.start() // emits app_open on cold launch; tracks IAM sessions
PushlanePush.register() // sets UNDelegate and requests permission BEFORE return
return true
}
// Forward the APNs device token — the environment is auto-detected (sandbox vs prod)
func application(
_ app: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken token: Data
) {
PushlanePush.didRegister(deviceToken: token)
}
func application(
_ app: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error
) {
PushlanePush.didFailToRegister(error: error)
}SwiftUI app? Keep an @UIApplicationDelegateAdaptor to receive the device-token callbacks, then call the same four functions in your App.init():
import SwiftUI
import PushlaneCore
import PushlanePush
import PushlaneInApp
@main
struct MyApp: App {
// Keep an AppDelegate adapter to receive the device-token callbacks.
@UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
init() {
Pushlane.configure(
apiBase: URL(string: "https://<your-ingest-url>")!,
tenantId: "<your-tenant-id>",
publishableKey: "lpk_live_…",
appGroup: "group.com.yourapp.pushlane"
)
Pushlane.identify(currentUser.id)
PushlaneInApp.start()
PushlanePush.register()
}
var body: some Scene { WindowGroup { ContentView() } }
}
// AppDelegate.swift — handles the token callbacks
class AppDelegate: NSObject, UIApplicationDelegate {
func application(
_ app: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken token: Data
) {
PushlanePush.didRegister(deviceToken: token)
}
func application(
_ app: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error
) {
PushlanePush.didFailToRegister(error: error)
}
}Pushlane.identify takes a positional argument — no label. Pushlane.identify(currentUser.id) compiles. Pushlane.identify(externalId: currentUser.id) does not.#4. Add the Notification Service Extension
The NSE runs in a separate process and does two things: it downloads rich media (image_url / loop_media) and emits a received event the moment a push lands — so you get a true open-rate (opened ÷ received).
Create a new target: in Xcode, File → New → Target… → choose Notification Service Extension. Give it a suffixed bundle id (e.g.com.yourapp.ios.NotificationService).
// NotificationService.swift — inside your NSE target
import PushlaneNotificationService
class NotificationService: PushlaneNotificationService {
// Use the SAME App Group id you passed to Pushlane.configure(appGroup:)
override var pushlaneAppGroup: String? { "group.com.yourapp.pushlane" }
}Then wire the App Group so the NSE can read the ingest config:
- Add the App Groups capability to both the app target and the NSE target.
- Use the same group id (e.g.
group.com.yourapp.pushlane) in both. - Pass that id as
appGroup:inPushlane.configure(app) and aspushlaneAppGroupin the NSE subclass.
received. The received event is best-effort and never blocks the notification from showing.#5. Track events
Push is only as good as the events that trigger it. The SDK auto-emits five events; track the rest with Pushlane.track. Use snake_case names and typed property values — the backend coerces against your event catalogue, it never infers types from the first value.
// Properties are typed (String / Int / Double / Bool / array).
// The backend coerces against your event catalogue — never infers from the first value.
Pushlane.track("lesson_finished", ["lesson_id": "intro-1", "duration_sec": 42])
Pushlane.track("paywall_viewed", ["placement": "home", "paywall_id": "fall_sale"])
Pushlane.track("subscription_cancelled", ["product_id": "monthly_pro", "reason": "price"])Auto-emitted events (no call needed):
| Event | Source | When |
|---|---|---|
app_open | PushlaneInApp.start() | Cold launch — emitted once per process |
session_started | PushlaneInApp (automatic) | Foreground return after ≥30 s in background |
opened | PushlanePush (automatic) | User tapped a Pushlane push notification |
received | PushlaneNotificationService (NSE) | Push landed on device — even if never opened |
push_registration_failed | PushlanePush (automatic) | APNs refused to register the device |
Pushlane.track drops the event and logs a warning if called before Pushlane.identify. Always identify the user before tracking. On logout, call Pushlane.reset():// On logout: detach this device from the user so stale tokens aren't stored.
Pushlane.reset()#6. Verify the integration
Build and run your app on a physical device. In the Pushlane dashboard, go to Settings → Install SDK (or open the Quickstart) and watch the live verification panel — it polls your backend every few seconds and lights up as real events and device registrations arrive.
| Check | What it means |
|---|---|
| SDK detected | At least one event has reached the ingest worker |
| Events instrumented | Distinct event names from your app appear in the catalogue |
| Device connected | A device token was registered — you can receive a push |
#Next step
The SDK is wired. To send real pushes to a device you need to upload your Apple push key (.p8) to Pushlane — that is the only remaining step before a buzz on the phone.