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.
Status
Section titled “Status”| Platform | Status | Gate |
|---|---|---|
| Android (FCM) | Server-side ready | KNOCK_FCM_CHANNEL_ID set, FCM channel provisioned |
| iOS (APNs) | Deferred | KNOCK_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.
Android — Firebase + FCM
Section titled “Android — Firebase + FCM”Step 1 — Firebase project + Android apps
Section titled “Step 1 — Firebase project + Android apps”- Create the project at console.firebase.google.com → New 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.jsonand 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.
Step 3 — Wire into Knock dashboard
Section titled “Step 3 — Wire into Knock dashboard”- 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 intoKNOCK_FCM_CHANNEL_IDfor each target environment. The env var lives onapps/api(Dokploy →apiapplication → 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.
Step 5 — Verify Android end-to-end
Section titled “Step 5 — Verify Android end-to-end”- 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.
iOS — Apple Developer + APNs
Section titled “iOS — Apple Developer + APNs”Step 1 — Apple Developer setup
Section titled “Step 1 — Apple Developer setup”- 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.p8file. This is a one-time download — server-side only, never commit it, never ship it in a mobile bundle. Treat it likeKNOCK_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
.p8file contents (or path)
Step 2 — Wire into Knock dashboard
Section titled “Step 2 — Wire into Knock dashboard”- 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_IDfor the matching env (Dokploy →apiapplication → Environment).
Step 3 — Delivery is server-gated
Section titled “Step 3 — Delivery is server-gated”- Delivery is gated server-side:
setPushDeviceTokenno-ops untilKNOCK_APNS_CHANNEL_IDis 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.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Likely cause | Fix |
|---|---|---|
registerDevice returns { accepted: false } (server logs a registerDevice no-op (<platform>) breadcrumb) | KNOCK_FCM_CHANNEL_ID (or _APNS_) empty on apps/api | Set the env var in Dokploy and redeploy |
| iOS permission prompt never appears at sign-in | Notification permission was previously denied (iOS won’t re-prompt) | If previously denied, reset under iOS Settings → the app → Notifications |
| Android push silent | google-services.json missing from the Android build | Add the file to the Android app module / build secret (see Android step 4) |
APNs returns BadDeviceToken in Knock logs | Sandbox p8 used against production APNs (or vice versa) | Provision separate Knock APNs channels per environment (iOS step 2) |
FCM returns SenderId mismatch or InvalidRegistration | Firebase package name doesn’t match the app’s applicationId | Re-add the Android app in Firebase with the exact com.realestatecore.{agent,user} package |
Related
Section titled “Related”- Notifications → Overview — architecture + workflow fan-out.
- Notifications → Configuration — env vars, dashboard checklist, per-device registration sequence diagram.
- Settings → Environment variables — canonical env-var list.
- Settings → Secrets —
google-services.jsongitignore policy. - Architecture → Deployment → Mobile distribution — native build + distribution.