Getting push credentials (APNs .p8 and Firebase service account)
A tenant needs these to receive push notifications — the wake-up that rings a call or shows a chat notification when the app is backgrounded or killed. Chat and calling both work over the live WebSocket without any of this; push only matters for the "app isn't open right now" case. Both credential types are entered in the admin panel under Configuration → APNs (iOS) and Configuration → Firebase Cloud Messaging (Android), by a tenant admin or a super admin configuring the tenant directly.
Neither of these is a Relay-specific concept — they're the same credentials any push-enabled iOS or Android app needs, from Apple and Google directly. If your team already has them for another app, you can reuse the same Apple Developer account / Firebase project, or create a second key scoped to just this app — either works.
1. APNs .p8 key (iOS)
What you need first
- A paid Apple Developer Program membership ($99/yr) — the free tier can't create push keys.
- Your app's bundle ID already registered in that account (e.g.
com.acme.myapp) — this is what Relay calls the APNs topic, and it must be the exact bundle ID your shipped app uses, since that's how APNs routes the push to the right app.
Steps
- Go to developer.apple.com/account → Certificates, Identifiers & Profiles → Keys (left sidebar).
- Click the + button to register a new key.
- Give it a name (e.g. "Relay push key" — the name is just for your own reference, it has no effect on how it works).
- Check Apple Push Notifications service (APNs), then click Continue → Register.
- Click Download. The file is named
AuthKey_XXXXXXXXXX.p8(the 10 characters before.p8are the Key ID — you'll need it, but you don't have to remember it: the admin panel reads it straight out of the filename when you upload it, as long as you don't rename the file). - This is the only time Apple lets you download this file. If you lose it, you have to revoke the key and generate a new one — there's no "download again" later. Keep a copy somewhere your team can find it (a password manager or secrets vault, not Slack).
- Note your Team ID — visible on the same Keys page (top right, under your name/org) or on Membership details. It's a 10-character alphanumeric string, different from the Key ID.
What goes where in the admin panel
| Apple gives you | Enter as | Notes |
|---|---|---|
The downloaded AuthKey_XXXXXXXXXX.p8 file |
Upload .p8 key file… | Upload the file directly — the admin panel reads the Key ID from the filename and the key itself from the file contents. Don't rename it before uploading, or paste its contents into a text field; drop the original file in. |
| Team ID (from the Keys page / Membership details) | Team ID | 10-character string, e.g. A1B2C3D4E5. |
| Your app's bundle ID | Bundle ID (APNs topic) | Must exactly match the bundle ID the shipped app uses — a mismatch means APNs silently drops every push. |
| — | Try the APNs sandbox host first checkbox | Turn this on while testing with a development-signed build (Xcode Debug builds, TestFlight internal builds signed with a Development provisioning profile use the sandbox APNs environment). Turn it off for App Store / TestFlight external / Ad Hoc builds, which use the production APNs environment. If you're not sure which you're running, Relay's push sender already retries the other environment automatically on a specific "wrong environment" error from Apple — this checkbox only controls which one it tries first, so getting it wrong costs you one extra round-trip, not a broken push. |
One key, multiple apps
A single .p8 key is not tied to one app — it's tied to your Apple Developer account. The topic (bundle ID) field is what scopes a given push to a specific app, so if you have several apps under one Apple Developer account, you can reuse the same .p8 key across several Relay tenants and just set each tenant's topic to that tenant's own bundle ID.
Revoking a key
If a .p8 key leaks (checked into a public repo, shared insecurely), revoke it immediately from
the same Keys page in the Apple Developer portal, then generate a new one and re-upload it in
the admin panel. Revoking takes effect for all apps using that key, so if you shared one key
across multiple tenants, you'll need to update all of them.
2. Firebase service account JSON (Android)
What you need first
- A Firebase project with your app added to it (Firebase Console → your project → Project
settings → Your apps → Add app → Android, with your app's
applicationIdas the package name). Free tier (Spark plan) is enough — FCM doesn't require a paid plan.
Steps
- Go to console.firebase.google.com → select your project → the gear icon (top left) → Project settings.
- Open the Service accounts tab.
- Under "Firebase Admin SDK", click Generate new private key. Confirm the dialog.
- A JSON file downloads immediately (something like
myapp-firebase-adminsdk-abc123-1234567890.json). Unlike the APNs key, you can generate a new one later if you lose this one — old keys aren't invalidated by generating a new one, though it's good practice to delete unused ones from that same page.
What goes where in the admin panel
Just upload the whole downloaded JSON file under Upload service-account JSON key… — the admin
panel reads everything it needs (project_id, client_email, private_key) straight out of the
file, so there's nothing to copy or retype by hand and no risk of a paste error breaking the key's
line breaks (a common way this goes wrong if you ever do it manually — a private key with
\n escapes mangled by a text editor won't verify).
| Field inside the JSON | Shown in the admin panel as |
|---|---|
project_id |
Firebase project ID (read-only display, confirms you uploaded the right project) |
client_email |
Service account email (read-only display) |
private_key |
Stored encrypted, never displayed again — re-upload the file if you need to rotate it |
This is a server credential — treat it like one
This file grants send-on-behalf-of-this-project access to Firebase Cloud Messaging — anyone
who has it can send push notifications as your app. It is uploaded to the Relay service (encrypted
at rest with RELAY_SECRETS_KEY) and never sent to any client app. Don't commit it to a
repository, don't attach it to a support ticket, don't paste it into a chat message — if it leaks,
delete that service account key from Firebase Console → Project settings → Service accounts, then
generate a new one and re-upload it in the admin panel.
google-services.json is a different file — you don't need it here
Don't confuse the service account key above (server-side, what Relay needs) with
google-services.json (client-side, what your Android app build needs so the Firebase SDK
inside it knows which project to talk to). Your Android app still needs its own
google-services.json dropped into its module and the google-services Gradle plugin applied —
that's a normal Android/Firebase setup step, unrelated to what you upload in the Relay admin
panel. See packages/android/app-demo/README.md's push section for exactly what that looks like
in a real app module.
Testing without real push (what already works, and what doesn't)
Chat and calling both work fully over the live WebSocket connection while both apps are
foregrounded and connected — none of the above is required to try that (see
sample-apps/web-demo, sample-apps/ios-demo,
and packages/android/app-demo). What you specifically can't
verify without the real credentials above (and, for iOS, a physical device — the Simulator has no
APNs connection at all) is the wake-up-a-backgrounded-app path: an incoming call ringing
CallKit from a cold start, or a chat push arriving while the app isn't running. Each demo app's
README says so explicitly rather than pretending that part works when it hasn't actually been
exercised.