feat: add push notification support for PostHog Workflows - #667
Conversation
* feat: add push notification token registration and open capture * feat(android): auto-register push tokens and capture notification opens * fix: stop push token registration after opt-out and guard cold-start crash * feat: unregister push token on reset and add unregisterPushNotificationToken Claude-Session: https://claude.ai/code/session_013yuqz2SN7ZfLr3kwQN2evU * refactor: align push notification API with iOS and gate on isOptedOut Claude-Session: https://claude.ai/code/session_01DLdpQV47JafPqUqGMzcQBk * fix: halt in-session push subscription retries after a non-retryable failure * docs: clarify reset moves push token independently of capture flag * fix: skip push notification open capture on activity restore * fix: bail push retry when the pending record was cleared mid-unregister
) * feat: attach optional identity token to push subscription requests * chore: drop changeset until release * fix: do not cache identity token minted after opt-out * fix: harden async push identity token mint against races, hangs, and leaks * fix: clear didAuthRetry on opt-out and order cache clear on executor
…at/push-notifications # Conflicts: # posthog-android/build.gradle.kts
…ent, parity with iOS)
…gistration is queued
posthog-android Compliance ReportDate: 2026-07-30 21:16:14 UTC ✅ All Tests Passed!46/46 tests passed Capture Tests✅ 29/29 tests passed View Details
Feature_Flags Tests✅ 17/17 tests passed View Details
|
|
🦔 ReviewHog reviewed this pull requestFound 1 must fix, 12 should fix, 3 consider. Published 16 findings (view the review). |
|
ReviewHog Alpha 🦔 If you find any issues helpful - please reply "valid", "invalid", etc., for evaluation purposes 🙏 |
There was a problem hiding this comment.
ReviewHog Report
Business logic
Issues: 7 issues
Files (1)
posthog/src/main/java/com/posthog/internal/PostHogPushSubscriptionManager.kt
What were the main changes
- New PostHogPushSubscriptionManager: persists a single latest-wins {deviceToken, appId, platform} registration and retries transient failures with an exponential backoff ladder (5s\u201330s) that persists across triggers
- Passive retry model with no timers \u2014 recovery driven only by flush()/identify()/relaunch; halts for the session on non-retryable or exhausted-retry failures but keeps the record for one retry next launch
- Identity token (pushIdentityProvider) resolution with in-memory caching, single 401 refresh-and-retry, and a 10s mint watchdog that falls back to a token-less send
- Durable single-slot unregister intent persisted before DELETE for logout/reset, cleared only on 2xx or terminal 4xx, replayed by retryPending()
- Race-condition fix so a fresh registration delivered to an identity supersedes a queued logout-DELETE for that same identity (log out of A, back into A)
- Reviewer-flagged issues: stale identity-mint completion can bypass the distinct-id guard after reset()/identify(); single unregister slot can silently drop an earlier identity's DELETE when a second reset/unregister arrives
Changes
Issues: 4 issues
Files (7)
posthog/src/main/java/com/posthog/PostHog.ktposthog/src/main/java/com/posthog/PostHogConfig.ktposthog/src/main/java/com/posthog/PostHogInterface.ktposthog/src/main/java/com/posthog/internal/PostHogApi.ktposthog/src/main/java/com/posthog/internal/PostHogPushSubscriptionRequest.ktposthog/src/testFixtures/java/com/posthog/PostHogFake.ktposthog/api/posthog.api
Feature
Issues: 5 issues
Files (10)
posthog-android/src/main/java/com/posthog/android/PostHogAndroid.ktposthog-android/src/main/java/com/posthog/android/PostHogAndroidConfig.ktposthog-android/src/main/java/com/posthog/android/internal/PostHogActivityLifecycleCallbackIntegration.ktposthog-android/src/main/java/com/posthog/android/internal/PostHogPushSubscriptionIntegration.ktposthog-android/api/posthog-android.apibuildSrc/src/main/java/PosthogBuildConfig.ktposthog-android/build.gradle.ktsposthog-android/consumer-rules.proposthog-android/gradle.lockfile.changeset/quirky-melons-jog.md
What were the main changes
- New PostHogPushSubscriptionIntegration auto-fetches the FCM token + Firebase project_id via reflection-gated FirebasePushTokenFetcher (compileOnly firebase-messaging dependency, no-op with debug log when absent) and registers it on startup
- PostHogAndroidConfig adds capturePushNotificationSubscriptions and capturePushNotificationOpened flags (both default true)
- PostHogAndroid wires the new integration and gates the activity-lifecycle integration on the new opened-capture flag
- PostHogActivityLifecycleCallbackIntegration auto-captures $push_notification_opened on cold-start tray taps via the launch intent's google.message_id extra, deduped per process and gated on fresh (non-recreated) launches
- Gradle/proguard/lockfile updates to add firebase-messaging as compileOnly + test dependency, plus consumer proguard rules for it
- Changeset documenting the new push notification support for posthog and posthog-android
dustinbyrne
left a comment
There was a problem hiding this comment.
I've read through this and taken the time to understand it. I don't have any additional comments outside of what the 🤖🤖🤖 have found - overall this direction seems clear, and it's cleanly scoped to work with FCM. With the comments addressed, I'd stamp
…t-out hydration, mid-send fold halt)
|
I think everything above is addressed already. Thank you everyone |
💡 Motivation and Context
Ships push notification support for PostHog Workflows: device token registration (auto via Firebase Cloud Messaging when
firebase-messagingis on the classpath, manual viaregisterPushNotificationToken(...)),$push_notification_openedcapture, identity-verified subscription requests, and durable register/unregister with passive retry.The pieces of this branch were already reviewed as they landed (#642 push notification support, #656 identity token attachment), but the branch has diverged a bit since: durable unregister intents, passive flush/identify/relaunch-driven retry with a backoff ladder that persists across triggers (no timers), retryable-by-default error classification, and a fix for a race where a queued logout DELETE could cancel a re-registered subscription for the same identity. I think it's worth a fresh full review before release.
💚 How did you test it?
Unit tests for the push subscription manager (47 tests: offline deferral, backoff/halt, identity change, durable unregister, the DELETE/POST supersede), plus
spotlessCheckandapiCheck.📝 Checklist
If releasing new changes
pnpm changesetto generate a changeset file🤖 Agent context
DRI: @ioannisj
Autonomy: Human-driven (agent-assisted)