Skip to content

Mobile push setup

This page is the operator runbook for the three credential dashboards (Apple Developer, Firebase, Knock) that have to converge before the registerDevice → setChannelData flow can actually deliver a push. The per-device registration sequence itself is documented in Configuration → Mobile push registration flow; this page picks up at the dashboard side.

PlatformStatusGate
Android (FCM)Server-side readyKNOCK_FCM_CHANNEL_ID set, FCM channel provisioned
iOS (APNs)DeferredKNOCK_APNS_CHANNEL_ID empty — provision the channel (steps below)

The native mobile apps (apps/ios-user, apps/android/app-user, apps/ios-agent, apps/android/app-agent) all wire their client side — each obtains a device token via the native Firebase / APNs SDK and calls registerDevice after sign-in (PushManager.swift on iOS, PushService.kt on Android) — see Conventions → Mobile (Native). They register through the same registerDevice → Knock FCM/APNs server flow described here, and the dashboard/credential setup below is unchanged. What is still outstanding is the server side for iOS: KNOCK_APNS_CHANNEL_ID is empty until the APNs channel is provisioned, so iOS clients register against a channel that does not exist yet.

Step 1 — Firebase project + Android apps

Section titled “Step 1 — Firebase project + Android apps”
  • Create the project at console.firebase.google.comNew project. One project shared across both Android apps is fine; each app is a separate Firebase “App” inside it. (This is also the project used by Firebase App Distribution for test builds — see Deployment → Sending test builds.)
  • Project settings → General → Your apps → Add app → Android. Do this twice — once for the agent app, once for the user app.
  • Package name MUST match each app’s applicationId (com.realestatecore.agent / com.realestatecore.user). Mismatch = silent token drop on the Firebase side; the Knock log will read “delivered” but the device will never receive it.
  • Download the per-app google-services.json and drop it into the Android app module. The file is gitignored — see Settings → Secrets.

Step 2 — Service-account JSON for Knock (FCM v1 API)

Section titled “Step 2 — Service-account JSON for Knock (FCM v1 API)”

Knock dispatches via FCM v1 (the HTTP v1 API, not the legacy server-key flow), which requires a Google service-account credential.

  • Firebase console → Project Settings → Service accounts tab → Generate new private key → download the JSON.
  • This file is server-side only — never commit it, never ship it in a mobile bundle. Treat it like KNOCK_SECRET_API_KEY.
  • Knock dashboard → Channels → Add channel → Push → Firebase Cloud Messaging (HTTP v1).
  • Upload the service-account JSON from step 2. The Firebase project ID auto-fills from the JSON.
  • After save, copy the channel ID (channel_xxx...) and paste into KNOCK_FCM_CHANNEL_ID for each target environment. The env var lives on apps/api (Dokploy → api application → Environment) and on the worker. See Settings → Environment variables for the canonical row.

Step 4 — Bundle google-services.json into the Android build

Section titled “Step 4 — Bundle google-services.json into the Android build”

The native Android apps read google-services.json from the app module at build time (Gradle’s Google Services plugin) — there is no EAS upload step. The file is gitignored; place it in each Android app’s module locally and provide it to CI / Firebase App Distribution builds as a secret.

  • Sign into the agent or user app on a real Android device (FCM doesn’t work in emulators without Play services configured).
  • Knock dashboard → Workflows → pick one with a mobile-push step (e.g. new-chat-message) → Test → Run → enter the signed-in user’s ID.
  • Knock dashboard → Logs: confirm channel = FCM and outcome = delivered.
  • Device should receive the push within ~5 s.
  • developer.apple.com → Certificates, Identifiers & Profiles.
  • Identifiers: confirm each app’s existing Bundle ID (com.realestatecore.user / com.realestatecore.agent) is registered. Enable the Push Notifications capability for each.
  • Keys: ”+” → name it (e.g. Knock APNs — real-estate-core) → check Apple Push Notifications service (APNs) → Continue → Register → download the .p8 file. This is a one-time download — server-side only, never commit it, never ship it in a mobile bundle. Treat it like KNOCK_SECRET_API_KEY.
  • Record from the Keys page:
    • Key ID (10-char string, visible after creation)
    • Team ID (top-right of developer.apple.com on every page)
    • Bundle ID of each app (different per app — agent vs user have separate identifiers)
    • The .p8 file contents (or path)
  • Knock dashboard → Channels → Add channel → Push → Apple Push Notification Service (APNs).
  • Mode: Token-based (p8). Upload the p8 + Key ID + Team ID + Bundle ID (Topic).
  • Sandbox vs Production: APNs runs two networks. The same p8 key authenticates both, but Apple routes tokens by environment. Create two Knock channels per app — one in sandbox mode (for debug / ad-hoc builds) and one in production mode (for store builds) — if the same Bundle ID ships to both. If dev and prod use different Bundle IDs, one channel each is fine.
  • Copy each channel ID into KNOCK_APNS_CHANNEL_ID for the matching env (Dokploy → api application → Environment).
  • Delivery is gated server-side: setPushDeviceToken no-ops until KNOCK_APNS_CHANNEL_ID is set, so iOS push starts flowing the moment Step 1 + Step 2 above are done.
  • If the server still has no APNs channel id when a device registers, the server leaves a registerDevice no-op (ios) breadcrumb so the gap is visible rather than silent.
SymptomLikely causeFix
registerDevice returns { accepted: false } (server logs a registerDevice no-op (<platform>) breadcrumb)KNOCK_FCM_CHANNEL_ID (or _APNS_) empty on apps/apiSet the env var in Dokploy and redeploy
iOS permission prompt never appears at sign-inNotification permission was previously denied (iOS won’t re-prompt)If previously denied, reset under iOS Settings → the app → Notifications
Android push silentgoogle-services.json missing from the Android buildAdd the file to the Android app module / build secret (see Android step 4)
APNs returns BadDeviceToken in Knock logsSandbox p8 used against production APNs (or vice versa)Provision separate Knock APNs channels per environment (iOS step 2)
FCM returns SenderId mismatch or InvalidRegistrationFirebase package name doesn’t match the app’s applicationIdRe-add the Android app in Firebase with the exact com.realestatecore.{agent,user} package