FCM and Expo Push

Firebase Cloud Messaging Behind Expo's Push Service

Expo 6,418 's push service is a relay: your server posts one JSON format to https://exp.host/--/api/v2/push/send 20,479 , and Expo forwards each message to FCM or APNs with credentials you uploaded once. You can skip it and call FCM's HTTP v1 API and APNs yourself (FCM's legacy API was shut down from July 2024), but then you run two integrations and handle two token formats. Setting up the Android side, not run here since it needs a Firebase 1 account: create a Firebase project with the app's package name, download google-services.json, point android.googleServicesFile at it in app.json, and upload an FCM V1 service account key with eas credentials. The request itself is plain HTTP:

Sending a push through Expo's serviceShell
curl -s https://exp.host/--/api/v2/push/send -H 'content-type: application/json' \
  -d '{"to":"ExponentPushToken[AAAAAAAAAAAAAAAAAAAAAA]","title":"New at BookNest",
       "body":"Salt and Saffron (Paperback) is in stock","data":{"url":"/book/61"},
       "channelId":"new-arrivals"}'
Output
{"data":{"status":"error","message":"\"ExponentPushToken[AAAAAAAAAAAAAAAAAAAAAA]\" is not a
  registered push
notification recipient or it is associated with a project that does not
  exist.","details":{"error":
"DeviceNotRegistered","expoPushToken":"ExponentPushToken[AAAAAAAAAAAAAAAAAAAAAA]"}}}

A made-up token gets the answer an uninstalled app's token gets, DeviceNotRegistered: delete it and stop sending. A successful send returns a ticket; about 15 minutes later, /push/getReceipts says whether FCM or APNs accepted it. Send at most 100 messages per request and 4 KB per payload, and turn on push security so a leaked token is useless without your access token. On Node, expo-server-sdk (https://github.com/expo/expo-server-sdk-node 1,036 ) (7.2.0, MIT) chunks, retries and reads receipts.