Expo push notifications work in development but not in the APK
The common reasons Expo push notifications stop in production builds: FCM v1 credentials, google-services.json, Expo Go limits, receipts and Android channels.
The push works in development and stops in the APK, or works on iOS and not Android. Almost every case is one of five things. Check them in this order.
1. No FCM v1 key in EAS
On Android, Expo sends through your Firebase project. Since Google retired the FCM legacy API in 2024, Expo needs an FCM v1 service account key for your project. Without it, Android pushes fail while iOS keeps working.
Upload it once:
eas credentials
# Android > production > Google Service Account
# > Manage your Google Service Account Key for Push Notifications (FCM V1)
You can also do it on expo.dev under your project’s Credentials, Android, then your application id, then FCM V1 service account key.
2. google-services.json missing from the build
The build must include the google-services.json from the same Firebase project the key came from. Point to it in app.json:
{
"expo": {
"android": {
"package": "in.kiranaexpress.app",
"googleServicesFile": "./google-services.json"
}
}
}
A file from another project gives you tokens that your key cannot send to.
3. Testing in Expo Go
Recent Expo SDKs removed remote push from Expo Go on Android, and Expo Go never used your own credentials anyway. Make a development build with eas build --profile development and test there.
4. Reading the ticket instead of the receipt
The push API answers with a ticket. "status": "ok" means Expo queued it. Delivery problems appear later in the receipt:
// after sending, keep the ticket ids, then a few minutes later:
const res = await fetch("https://exp.host/--/api/v2/push/getReceipts", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ ids: ticketIds }),
});
const { data } = await res.json();
// data[id].status === "error" with details.error such as
// "DeviceNotRegistered" (delete the token) or "InvalidCredentials" (fix step 1)
5. Android’s own reasons
Once Expo hands the message to FCM, the usual Android causes apply: the notification permission on Android 13 and later, a missing channel, and battery savers on Xiaomi, Oppo and Vivo. Create a channel at start with Notifications.setNotificationChannelAsync, ask for permission with requestPermissionsAsync, and read why pushes go missing on Xiaomi, Oppo and Vivo.
Sending from your own server instead
Expo’s push service is free and fine for Expo apps. If you also want topics, scheduling, a composer for your marketing team, push approval for logins or email codes on the same user id, SignOps Push takes your FCM and APNs credentials directly and works the same for Expo, bare React Native, Flutter and native apps.
Sources: Expo: FCM v1 migration, Expo: support for FCM HTTP v1, Expo forums: push not working after EAS build.