Setting up iOS push, you will reach a step asking for a .p8 file, a Key ID, and a Team ID. This covers what those are, why Apple needs them, and how to get them, including the one step you genuinely cannot undo.
What APNs Is
Apple Push Notification service is the only path to an iOS device. Every notification your app receives, from every provider, arrives through APNs. Services like Firebase or OneSignal do not bypass it, they talk to it on your behalf.
The flow:
- Your app asks the user for notification permission, then registers with APNs and receives a device token.
- Your server stores that token and, when there is something to say, calls APNs: “deliver this payload to this token, for this app.”
- APNs delivers it, holding onto the message if the device is offline.
Apple controls that path deliberately. It is why a backgrounded iOS app cannot drain your battery polling for updates, and it is why you need credentials to use it.
Why a .p8 Key
APNs will not accept sends from anyone. You have to prove you represent the app you are sending to.
There are two mechanisms, and the older one is still all over the internet:
.p12 certificate | .p8 auth key | |
|---|---|---|
| Expires | Every year | Never |
| Scope | One app | All apps in your team |
| Environments | Separate cert per environment | One key covers both |
| On expiry | Push silently stops | Nothing to expire |
| Apple recommends | No | Yes |
The .p12 route means an annual renewal that, when forgotten, breaks push notifications in production with no warning and no obvious cause. Many teams have lost a day to exactly that.
Use the .p8. One key, no expiry, covers everything. The only reason to touch certificates now is an old system that cannot do token auth.
The Four Things You Need
A .p8 on its own is not enough. Apple needs to know which key, whose account, and which app:
| Value | Looks like | Where to find it |
|---|---|---|
.p8 file | AuthKey_ABC123DEFG.p8 | Downloaded at creation, once |
| Key ID | ABC123DEFG | Next to the key name in Apple Developer |
| Team ID | 1A2B3C4D5E | Membership Details |
| Bundle ID | com.yourcompany.app | Identifiers, and your Xcode target |
The Key ID is also embedded in the filename, which is handy when you find an old .p8 and cannot remember which key it was.
Creating the Key
You need a paid Apple Developer Program membership. The free tier cannot create APNs keys.
1. Enable Push Notifications on your App ID
In Apple Developer, go to Certificates, Identifiers & Profiles → Identifiers and select your app’s identifier. Make sure Push Notifications is ticked in the capabilities list, and save.
Also enable the Push Notifications capability on your target in Xcode, which creates the entitlements file.
2. Create the auth key
Go to Certificates, Identifiers & Profiles → Keys and click +.
Give it a name you will recognise in two years, something like Push - MyApp, and tick Apple Push Notifications service (APNs). Continue, then Register.
3. Download it, once
You now get a Download button. This is the step you cannot repeat.
Apple lets you download a
.p8exactly once. There is no second chance and no recovery. If you lose the file, your only option is to revoke the key and create a new one.
Download it and immediately put a copy in your password manager. Not your Downloads folder. Not a Slack message to yourself.
While you are on that screen, copy the Key ID.
4. Get your Team ID
Top right of the Apple Developer portal, or under Membership Details. Ten characters, and it is not the same as your Apple ID.
Sandbox vs Production
This is the single most common source of “push works on my machine but not in production.”
Device tokens are environment specific.
| Build | Environment | Token valid against |
|---|---|---|
| Run from Xcode | Sandbox | Sandbox endpoint only |
| TestFlight | Production | Production endpoint only |
| App Store | Production | Production endpoint only |
TestFlight is production. People assume a pre-release build is “development” and it is not, which is why push often breaks the moment a build leaves Xcode.
Your .p8 key works for both environments, so the key is not the problem. What has to match is the endpoint your server sends to and the environment the token came from. If they disagree, APNs rejects it with BadDeviceToken.
The practical fix is to record which environment each token came from when you register it, and send accordingly. Most SDKs expose this as an isSandbox flag.
Common Failure Modes
BadDeviceToken. Environment mismatch, almost always. A sandbox token sent to production or the reverse. Check what build produced the token.
InvalidProviderToken. Your Key ID, Team ID, or the .p8 itself do not agree. Usually a copy-paste error, or the wrong key file where you have more than one.
TopicDisallowed or DeviceTokenNotForTopic. The Bundle ID you are sending as does not match the app the token belongs to. Note the topic is your Bundle ID exactly, and app extensions have their own suffixed identifiers.
ExpiredProviderToken. The short-lived JWT your server generates from the .p8 has aged out. Apple expects them refreshed roughly hourly, and this is your sending code’s job, not the key’s.
Nothing arrives at all, no error. Check the user actually granted permission, and test on a real device. Simulator push support depends on your Xcode and macOS versions, so a real device removes one variable when you are trying to work out why nothing is arriving.
Where This Fits
The credential is the easy part. What follows is the real work: storing tokens with their environment, pruning dead ones, deciding who should receive a given notification, building payloads, and refreshing provider tokens on schedule.
If your data lives in Supabase, Entrig handles that half. You upload the .p8, Key ID, Team ID and Bundle ID once, and notifications become configuration against your existing tables rather than a service you maintain. Sandbox and production are stored independently, so a development build and an App Store build can both work at the same time.
You still need this key either way. Apple requires your own credentials to reach your own app, and no service can remove that.