diff --git a/contents/docs/libraries/android/index.mdx b/contents/docs/libraries/android/index.mdx index ad9e496ce99d..4f4ad09c3587 100644 --- a/contents/docs/libraries/android/index.mdx +++ b/contents/docs/libraries/android/index.mdx @@ -492,6 +492,48 @@ config.addBeforeSend { event -> } ``` +# Push notifications + +The Android SDK can register a device for [Workflows](/docs/workflows) push notifications and capture when a user opens one. Sends go out through the FCM channel you connect in [Workflows > Channels](/docs/workflows/configure-channels?tab=Push), so the Firebase project on that channel must match your app. + +## Automatic registration and open tracking (default) + +Both behaviors are on by default. When `firebase-messaging` is on your classpath, the SDK registers this device's FCM token with PostHog, and it auto-captures a `$push_notification_opened` event when a user opens the app from a notification tray tap. + +```kotlin +val config = PostHogAndroidConfig(apiKey = "").apply { + capturePushNotificationSubscriptions = true // register the FCM token with PostHog + capturePushNotificationOpened = true // capture `$push_notification_opened` +} +PostHogAndroid.setup(context, config) +``` + +Firebase delivers rotated tokens through `onNewToken`, which the SDK can't observe on its own — forward it so the registered token stays current: + +```kotlin +class MyFirebaseMessagingService : FirebaseMessagingService() { + override fun onNewToken(token: String) { + PostHog.registerPushNotificationToken(token, appId = "your-firebase-project-id") + } +} +``` + +## Manual registration + +```kotlin +// Register a device token (appId = your Firebase project id): +PostHog.registerPushNotificationToken(token, appId = "your-firebase-project-id") + +// Unregister when a user signs out: +PostHog.unregisterPushNotificationToken() +``` + +Calling `PostHog.reset()` on logout unregisters the token for the signed-out user and re-registers it under the new anonymous id. + +## Identity verification + +Identity verification (a signed token that proves *who* a device belongs to when registering) is **not yet available in the Android SDK** — it currently ships only in iOS. If your push channel is set to **Required**, Android device registration will be rejected until Android support lands, so keep the channel on **Disabled** or **Optional** for Android users in the meantime. + # FAQ ## What Android API level is required? diff --git a/contents/docs/libraries/ios/push-notifications.mdx b/contents/docs/libraries/ios/push-notifications.mdx new file mode 100644 index 000000000000..9b6617f822d1 --- /dev/null +++ b/contents/docs/libraries/ios/push-notifications.mdx @@ -0,0 +1,110 @@ +--- +title: iOS push notifications +sidebarTitle: Push notifications +sidebar: Docs +showTitle: true +github: 'https://github.com/PostHog/posthog-ios' +platformLogo: ios +--- + +The iOS SDK can register a device for [Workflows](/docs/workflows) push notifications and capture when a user opens one. Sends go out through the FCM or APNs channel you connect in [Workflows > Channels](/docs/workflows/configure-channels?tab=Push). + + + +The SDK registers the device token with PostHog and captures opens. You still set up remote notifications and request notification permission in your app as usual — the SDK doesn't do that for you. + + + +## Requirements + +- Enable the **Push Notifications** capability for your app and register for remote notifications (`registerForRemoteNotifications()`), which requires requesting notification permission from the user. +- A connected **FCM** or **APNs** channel in PostHog whose Firebase project / APNs topic (bundle id) matches your app. + +## Automatic registration and open tracking (default) + +By default the SDK registers the device token and captures opens for you, by hooking into the system callbacks: + +```swift +let config = PostHogConfig(apiKey: "") +// Both default to true — shown here for clarity: +config.capturePushNotificationSubscriptions = true // register the device token with PostHog +config.capturePushNotificationOpened = true // capture `$push_notification_opened` on tap +PostHogSDK.shared.setup(config) +``` + +With these enabled you only need to register for remote notifications; the SDK picks up the token and open events automatically. Locally-scheduled notifications are ignored — capture those manually (below). + +## Manual registration + +If you manage the notification lifecycle yourself, turn the automatic flags off and call the SDK directly. + +Register the device token from `application(_:didRegisterForRemoteNotificationsWithDeviceToken:)`: + +```swift +func application(_ application: UIApplication, + didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + let token = deviceToken.map { String(format: "%02x", $0) }.joined() + PostHogSDK.shared.registerPushNotificationToken(token) + // Or, to route to a specific channel by app id (Firebase project id / APNs bundle id): + // PostHogSDK.shared.registerPushNotificationToken(token, appId: "com.example.app") +} +``` + +Unregister the token when a user signs out so it isn't left bound to them: + +```swift +PostHogSDK.shared.unregisterPushNotificationToken() +``` + +Calling `PostHogSDK.shared.reset()` on logout also unregisters the token for the signed-out user. + +## Capturing opens + +When automatic capture is on, tapping a remote push emits a `$push_notification_opened` event. To capture opens yourself (or to capture a locally-scheduled notification), call: + +```swift +func userNotificationCenter(_ center: UNUserNotificationCenter, + didReceive response: UNNotificationResponse, + withCompletionHandler completionHandler: @escaping () -> Void) { + PostHogSDK.shared.capturePushNotificationOpened(response: response) + completionHandler() +} +``` + +There's also a fully manual variant if you don't have a `UNNotificationResponse`: + +```swift +PostHogSDK.shared.capturePushNotificationOpened(title: "...", body: "...", payload: [:], action: "...") +``` + +## Identity verification + + + +The `pushIdentityProvider` hook ships in [posthog-ios #735](https://github.com/PostHog/posthog-ios/pull/735) — confirm your SDK version includes it before relying on this. Identity verification is not yet available in the Android SDK. + + + +A device token says *where* to deliver a notification, not *who* the device belongs to. If you turn on identity verification for your push channel ([Optional or Required](/docs/workflows/configure-channels?tab=Push)), your app must attach a short-lived token that proves the logged-in user's identity when it registers a device. + +Your **backend** mints that token — the SDK never signs one itself. Sign an HS256 JWT with your project's secret API key, with the user's `distinct_id` as `sub`, `aud` set to `posthog:push_identity`, and a short `exp`. Then supply it through `pushIdentityProvider`: + +```swift +config.pushIdentityProvider = { distinctId, appId, completion in + // Fetch a freshly-minted token from your backend for this user, then: + completion(token) // or completion(nil) to send without one +} +``` + +Notes: + +- The SDK calls your provider when it needs a token and caches the result in memory per `(distinctId, appId)`, re-requesting on identity change and when a request is rejected. Always call `completion` exactly once. +- If your provider doesn't respond in time, the SDK falls back to sending without a token. On a channel set to **Required** that request is rejected, so make sure your provider reliably returns a token before switching the channel to Required. + +## Troubleshooting + +| Issue | Check | +| --- | --- | +| Token never registers | Confirm you call `registerForRemoteNotifications()` and the user granted notification permission. | +| Push doesn't arrive | Confirm the channel's APNs environment (Production/Sandbox) matches your build, and the bundle id matches. | +| Registration rejected on a Required channel | Your `pushIdentityProvider` isn't returning a valid token in time — see [Identity verification](#identity-verification). | diff --git a/src/navs/index.js b/src/navs/index.js index d8a68b6d29e1..83252599b6a4 100644 --- a/src/navs/index.js +++ b/src/navs/index.js @@ -2540,6 +2540,10 @@ export const docsMenu = { name: 'Configuration', url: '/docs/libraries/ios/configuration', }, + { + name: 'Push notifications', + url: '/docs/libraries/ios/push-notifications', + }, ], }, {