Fix FCM 'Invalid APNs Credential' Error on iOS (Even With a Valid .p8 Key)
Integrating Firebase Cloud Messaging (FCM) with iOS should be straightforward, especially when using modern APNs Authentication Keys (.p8 files). Yet, one of the most frustrating errors developers encounter is receiving an Invalid APNs credential exception from the Firebase Admin SDK—even when the Key ID, Team ID, bundle ID, and .p8 file are verified and 100% accurate.
If you're dealing with a 100% failure rate when pushing notifications to iOS while your Android devices work smoothly, this guide breaks down the hidden root causes and provides step-by-step solutions to fix it.
Understanding the Error
When the Firebase Admin SDK throws Invalid APNs credential (HTTP 401 or 403 returned by Apple's APNs servers behind the scenes), FCM is telling you that Apple rejected the JSON Web Token (JWT) or certificate exchange. Apple didn't accept the signature FCM attempted to build using your credentials.
Top Causes and How to Resolve Them
1. Bundle ID Mismatch Between Firebase and Xcode
The APNs Auth Key itself is team-scoped and can theoretically send to any topic (Bundle ID) within your Apple Developer Team. However, Firebase binds the token registration to the specific Apple App entry inside your Firebase project.
- Open the Firebase Console → Project Settings → General.
- Scroll down to Your apps and select your iOS app.
- Ensure the Bundle ID listed here matches your Xcode project's
PRODUCT_BUNDLE_IDENTIFIERcharacter-by-character (including letter casing).
If you have multiple flavors or targets (e.g., com.example.app.dev and com.example.app), ensure the FCM registration token was generated using the configuration file (GoogleService-Info.plist) for the exact matching app profile in Firebase.
2. The APNs Environment Mismatch (Sandbox vs. Production)
By default, the Firebase Admin SDK attempts to detect whether an APNs device token belongs to the Sandbox or Production environment. Sometimes, especially with local development builds installed directly via Xcode or TestFlight, this automatic detection fails or defaults unexpectedly.
In your Python Admin SDK backend, you can explicitly configure the APNs headers to specify whether you are targeting development (sandbox) or production:
from firebase_admin import messaging
message = messaging.Message(
notification=messaging.Notification(title="Test Title", body="Test Body"),
data={"key": "value"},
token=target_token,
apns=messaging.APNSConfig(
headers={
# Set to 'development' for Xcode/Simulator/Debug builds,
# or 'production' for TestFlight and App Store releases.
"apns-push-type": "alert",
"apns-priority": "10",
},
payload=messaging.APNSPayload(
aps=messaging.Aps(sound="default", badge=1)
),
# In debug/development phases, you can test targeting sandbox directly:
# fcm_options=messaging.APNSFCMOptions(...)
),
)
response = messaging.send(message)
print("Successfully sent:", response)3. The Token is an APNs Device Token, Not an FCM Registration Token
A very common trap when writing native iOS code or debugging token delivery is accidentally passing the raw APNs device token (the 64-character hex string returned by didRegisterForRemoteNotificationsWithDeviceToken) straight to messaging.send(message).
The Firebase Admin SDK expects an FCM Registration Token, not a raw APNs token. To convert an APNs token to an FCM token:
- In your client app, retrieve the FCM token via
Messaging.messaging().token { token, error in ... }. - Alternatively, use Firebase's instance ID/token batch import API to map the raw APNs token to an FCM registration token.
4. Stale Firebase Console Caching / Hidden Old Keys
The Firebase Console can occasionally retain cached associations with old or revoked APNs keys or legacy .p12 certificates. To clear this state:
- Go to Project Settings → Cloud Messaging.
- Look under Apple app configuration.
- If you have any old
.p12certificates (Development or Production APNs certificates) uploaded, delete them. Having both a.p8key and an expired.p12certificate can cause Firebase to fall back to the invalid certificate. - Delete the existing
.p8key entry from Firebase entirely. - Wait 5 to 10 minutes, then re-upload the
.p8key, double-checking that the Key ID (10 characters from the developer portal) and Team ID (10 characters from your Apple Developer membership) are entered accurately with no extra spaces.
5. Missing APNs Topic Header
Apple requires the apns-topic header when establishing HTTP/2 connections. While Firebase usually sets this using your app's bundle ID automatically, explicitly setting it in the SDK can eliminate ambiguities:
apns=messaging.APNSConfig(
headers={
"apns-topic": "com.yourcompany.yourapp" # Replace with exact Bundle ID
},
payload=messaging.APNSPayload(
aps=messaging.Aps(sound="default", badge=1)
)
)Verifying Directly with APNs
If you want to conclusively rule out Firebase, test the APNs key directly against Apple's HTTP/2 APNs endpoints using a tool like PushNotifications or a simple curl command with JWT authentication:
curl -v \r
--header "apns-topic: com.yourcompany.yourapp" \r
--header "authorization: bearer $JWT_TOKEN" \r
--data '{"aps":{"alert":"Test"}}' \r
--http2 \r
https://api.sandbox.push.apple.com/3/device/$DEVICE_TOKENIf the direct request succeeds with an HTTP 200, the issue is strictly the mapping of the token inside Firebase (often a bundle ID mismatch or raw APNs token confusion). If Apple returns an error, the issue lies in the .p8 permissions or Apple Developer account setup.