# Reference

## Obtain Google Service Account Keys using FCM V1

Learn how to create or use a Google Service Account Key for sending Android Notifications using FCM.

## Create a new Google Service Account Key
Here are the steps to configure a new Google Service Account Key in EAS for sending Android Notifications using FCM V1.
Step 1: 
Create a new Firebase project for your app in the [Firebase Console](https://console.firebase.google.com). If you already have a Firebase project for your app, continue to the next step.
Step 2: 
In the Firebase console, open **Project settings** > [**Service accounts**](https://console.firebase.google.com/project/_/settings/serviceaccounts/adminsdk) for your project.
Step 3: 
Click **Generate New Private Key**, then confirm by clicking **Generate Key**. Securely store the JSON file containing the private key.
Step 4: 
Upload the JSON file to EAS and configure it for sending Android notifications. This can be done using EAS CLI or in [EAS dashboard](https://expo.dev).
- Run `eas credentials`
- Select `Android` > `production` > `Google Service Account`
- Select `Manage your Google Service Account Key for Push Notifications (FCM V1)`
- Select `Set up a Google Service Account Key for Push Notifications (FCM V1)` > `Upload a new service account key`
- If you've previously stored the JSON file in your project directory, the EAS CLI automatically detects the file and prompts you to select it. Press <kbd>Y</kbd> to continue.
> **Note**: Add the JSON file to your version source control's ignore file (for example, **.gitignore**) to avoid committing it to your repository since it contains sensitive data.
- Under **Project settings**, click [**Credentials**](https://expo.dev/accounts/[account]/projects/[project]/credentials) in the navigation menu
- For **Android**, click **Add Application Identifier** or select an existing **Application identifier**
- Under **Service Credentials** > **FCM V1 service account key**, click **Add a service account key**
- Under **Upload new key**, upload your JSON credential and click **Save**
Step 5: 
Configure the **google-services.json** file in your project. Download it from the Firebase Console and place it at the root of your project directory.
This file is required for your Android app to be registered with FCM. You may commit this file to your repository since it contains public-facing identifiers from your Firebase project.
**Note**: You can skip this step if **google-services.json** has already been set up.
In **app.json**, add [`expo.android.googleServicesFile`](/versions/latest/config/app/#googleservicesfile) with its value as the path of the **google-services.json**.
```json app.json
{
  "expo": {
  "android": {
    "googleServicesFile": "./path/to/google-services.json"
  }
}
```
Step 6: 
You're all set! You can now send notifications to Android devices via Expo Push Notifications using the FCM V1 protocol.
## Use an existing Google Service Account Key
Step 1: 
Open the [IAM Admin page](https://console.cloud.google.com/iam-admin/iam?authuser=0) in Google Cloud Console. In the Permissions tab, locate the **Principal** you intend to modify and click the pencil icon for **Edit Principal**.
Step 2: 
Click **Add Role** and select the **Firebase Messaging API Admin** role from the dropdown. Click **Save**.
Step 3: 
You have to specify to EAS which JSON credential file to use for sending FCM V1 notifications, using EAS CLI or in [EAS dashboard](https://expo.dev). You can upload a new JSON file or select a previously uploaded file.
- Run `eas credentials`
- Select `Android` > `production` > `Google Service Account`
- Select `Manage your Google Service Account Key for Push Notifications (FCM V1)`
- Select `Set up a Google Service Account Key for Push Notifications (FCM V1)` > `Upload a new service account key`
- The EAS CLI automatically detects the file on your local machine and prompts you to select it. Press <kbd>Y</kbd> to continue.
> **Note**: Add the JSON file to your version source control's ignore file (for example, **.gitignore**) to avoid committing it to your repository since it contains sensitive data.
- Under **Project settings**, click [**Credentials**](https://expo.dev/accounts/[account]/projects/[project]/credentials) in the navigation menu
- For **Android**, click **Add Application Identifier** or select an existing **Application identifier**
- Under **Service Credentials** > **FCM V1 service account key**, click **Add a service account key**
- Under **Upload new key**, upload your JSON credential and click **Save**
Step 4: 
Configure the **google-services.json** file in your project. Download it from the Firebase Console and place it at the root of your project directory.
This file is required for your Android app to be registered with FCM. You may commit this file to your repository since it contains public-facing identifiers from your Firebase project.
**Note**: You can skip this step if **google-services.json** has already been set up.
In **app.json**, add [`expo.android.googleServicesFile`](/versions/latest/config/app/#googleservicesfile) with its value as the path of the **google-services.json**.
```json app.json
{
  "expo": {
    "android": {
    }
  }
}
```
Step 5: 
You're all set! You can now send notifications to Android devices via Expo Push Notifications using the FCM V1 protocol.


## Send notifications with FCM and APNs

Learn how to send notifications with FCM and APNs.

You may need finer-grained control over your notifications, in which case communicating directly with FCM and APNs may be necessary. The Expo platform does not lock you into using Expo Application Services, and the `expo-notifications` API is push-service agnostic.
> **Note**: This guide does not aim to be a comprehensive resource for sending notifications via FCM or APNs. We recommend you read the official documentation to make sure you're following the latest instructions.
## Obtaining a device token for FCM or APNs
When using Expo notification service, you use the `ExpoPushToken` obtained with [`getExpoPushTokenAsync`](/versions/latest/sdk/notifications/#getexpopushtokenasyncoptions-expotokenoptions-expopushtoken).
If you instead want to send notifications via FCM or APNs, you need to obtain the native device token with [`getDevicePushTokenAsync`](/versions/latest/sdk/notifications/#getdevicepushtokenasync-devicepushtoken).
```diff
diff --git a/App.js b/App.js
index 0000000..1111111 100644
--- a/App.js
+++ b/App.js
@@ -1,4 +1,4 @@
 import * as Notifications from 'expo-notifications';
 // ...
-const token = (await Notifications.getExpoPushTokenAsync()).data;
+const token = (await Notifications.getDevicePushTokenAsync()).data;
 // send token to your server
```
## FCMv1 server
This guide is based on [Firebase official documentation](https://firebase.google.com/docs/cloud-messaging/server).
Communicating with FCM is done by sending a POST request. However, before sending or receiving any notifications, you'll need to follow the steps to [configure FCM](/push-notifications/fcm-credentials/) and get your `FCM-SERVER-KEY`.
### Getting an authentication token
FCM requires an Oauth 2.0 access token, which must be obtained via one of the methods described in ["Update authorization of send requests"](https://firebase.google.com/docs/cloud-messaging/migrate-v1#update-authorization-of-send-requests).
For testing purposes, you can use the Google Auth Library and your private key file obtained above, to obtain a short lived token for a single notification, as in this Node example adapted from Firebase documentation:
```ts
import { JWT } from 'google-auth-library';
function getAccessTokenAsync(
  key: string // Contents of your FCM private key file
) {
  return new Promise(function (resolve, reject) {
    const jwtClient = new JWT(
      key.client_email,
      null,
      key.private_key,
      ['https://www.googleapis.com/auth/cloud-platform'],
      null
    );
    jwtClient.authorize(function (err, tokens) {
      if (err) {
        reject(err);
        return;
      }
      resolve(tokens.access_token);
    });
  });
}
```
### Sending the notification
The example code below calls `getAccessTokenAsync()` above to get the Oauth 2.0 token, then constructs and sends the notification POST request. Note that unlike FCM legacy protocol, the endpoint for the request includes the name of your Firebase project.
```ts
// FCM_SERVER_KEY: Environment variable with the path to your FCM private key file
// FCM_PROJECT_NAME: Your Firebase project name
// FCM_DEVICE_TOKEN: The client's device token (see above in this document)
async function sendFCMv1Notification() {
  const key = require(process.env.FCM_SERVER_KEY);
  const firebaseAccessToken = await getAccessTokenAsync(key);
  const fcmToken = process.env.FCM_DEVICE_TOKEN;
  const messageBody = {
    message: {
      token: fcmToken,
      data: {
        channelId: 'default',
        message: 'Testing',
        title: `This is an FCM notification message`,
        body: JSON.stringify({ title: 'bodyTitle', body: 'bodyBody' }),
        scopeKey: '@yourExpoUsername/yourProjectSlug',
        experienceId: '@yourExpoUsername/yourProjectSlug',
      },
    },
  };
  const response = await fetch(
    `https://fcm.googleapis.com/v1/projects/${process.env.FCM_PROJECT_NAME}/messages:send`,
    {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${firebaseAccessToken}`,
        Accept: 'application/json',
        'Accept-encoding': 'gzip, deflate',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(messageBody),
    }
  );
  const readResponse = (response: Response) => response.json();
  const json = await readResponse(response);
  console.log(`Response JSON: ${JSON.stringify(json, null, 2)}`);
}
```
The `experienceId` and `scopeKey` fields are only applicable when using Expo Go (from SDK 53, push notifications support is removed from Expo Go). Otherwise, your notifications will not go through to your app. FCM has a list of supported fields in the [notification payload](https://firebase.google.com/docs/cloud-messaging/http-server-ref#notification-payload-support), and you can see which ones are supported by `expo-notifications` on Android by looking at the [FirebaseRemoteMessage](/versions/latest/sdk/notifications/#firebaseremotemessage).
FCM also provides some [server-side libraries in a few different languages](https://firebase.google.com/docs/cloud-messaging/send-message#node.js) you can use instead of raw `fetch` requests.
### How to find FCM server key
Your FCM server key can be found by making sure you've followed the [configuration steps](/push-notifications/push-notifications-setup/#android), and instead of uploading your FCM key to Expo, you would use that key directly in your server (as the `FCM-SERVER-KEY` in the previous example).
## APNs server
> **info** This documentation is based on [Apple's documentation](https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/APNSOverview.html), and this section covers the basics to get you started.
Communicating with APNs is a little more complicated than with FCM. Some libraries wrap all of this functionality into one or two function calls such as [`node-apn`](https://github.com/node-apn/node-apn). However, in the examples below, a minimum set of libraries are used.
### Client APNs entitlement
Receiving push notifications only works if your iOS app has the APNs entitlement. For apps using [CNG](/workflow/continuous-native-generation/),
the Expo config needs to be modified in one of two ways:
- **Recommended**: Add the `expo-notifications` library to your app, and ensure that its plugin appears in the `plugins` array of your [app config](/workflow/configuration/):
```json app.json
{
  "expo": {
    "plugins": [
      "expo-notifications"
      ]
  }
}
```
- If you do not plan to use the `expo-notifications` library, then you should [add the `aps-environment` entitlement manually to the Expo configuration](/build-reference/ios-capabilities/#entitlements),
  as in the example below:
```json app.json
{
  "expo": {
    "ios": {
      "entitlements": {
        "aps-environment": "development"
      }
    }
  }
}
```
If you are not using CNG, then you should [add the push notification entitlement in Xcode](https://developer.apple.com/documentation/usernotifications/registering-your-app-with-apns).
> **Note**: If you are upgrading an Expo app from a version from SDK 51 and earlier, you should refer to [this FYI document](https://expo.fyi/apns-entitlement-sdk-51).
### Authorization
Initially, before sending requests to APNS, you need permission to send notifications to your app. This is granted via a JSON web token which is generated using iOS developer credentials:
- APN key (`.p8` file) associated with your app
- Key ID of the above `.p8` file
- Your Apple Team ID
```js
const jwt = require("jsonwebtoken");
const authorizationToken = jwt.sign(
  {
    iss: "YOUR-APPLE-TEAM-ID"
    iat: Math.round(new Date().getTime() / 1000),
  },
  fs.readFileSync("./path/to/appName_apns_key.p8", "utf8"),
  {
    header: {
      alg: "ES256",
      kid: "YOUR-P8-KEY-ID",
    },
  }
);
```
### HTTP/2 connection
After getting the `authorizationToken`, you can open up an HTTP/2 connection to Apple's servers. In development, send requests to `api.sandbox.push.apple.com`. In production, send requests to `api.push.apple.com`.
Here's how to construct the request:
```js
const http2 = require('http2');
const client = http2.connect(
  IS_PRODUCTION ? 'https://api.push.apple.com' : 'https://api.sandbox.push.apple.com'
);
const request = client.request({
  ':method': 'POST',
  ':scheme': 'https',
  'apns-topic': 'YOUR-BUNDLE-IDENTIFIER',
  ':path': '/3/device/' + nativeDeviceToken, // This is the native device token you grabbed client-side
  authorization: `bearer ${authorizationToken}`, // This is the JSON web token generated in the "Authorization" step
});
request.setEncoding('utf8');
request.write(
  JSON.stringify({
    aps: {
      alert: {
        title: "\uD83D\uDCE7 You've got mail!",
        body: 'Hello world! \uD83C\uDF10',
      },
    },
    experienceId: '@yourExpoUsername/yourProjectSlug', // Required only when testing in legacy Expo Go (in SDK 52 and earlier)
    scopeKey: '@yourExpoUsername/yourProjectSlug', // Required only when testing in legacy Expo Go (in SDK 52 and earlier)
  })
);
request.end();
```
> This example is minimal and includes no error handling and connection pooling. For testing purposes, you can refer to [`sendNotificationToAPNS`](https://github.com/expo/expo/blob/main/docs/public/static/examples/sendNotificationToAPNS.js) example code.
APNs provide their full list of supported fields in the [notification payload](https://developer.apple.com/documentation/usernotifications/generating-a-remote-notification#Payload-key-reference).


## Push notifications troubleshooting and FAQ

A collection of common questions about Expo push notification service.

A collection of common issues and FAQs when setting up push notifications with the `expo-notifications` library and Expo push notification service.
## Expo push notification service FAQ
### Cost of the push notification service
There is no cost associated with sending notifications through Expo push notification service.
### Limit of sending notifications
There is a limit of 600 notifications per second per project that can be sent. If you exceed this rate, subsequent requests will fail until the rate falls below 600 per second again.
For best results, we recommend you add throttling (which is handled automatically in the [`expo-server-sdk-node`](https://github.com/expo/expo-server-sdk-node)) and retry logic to your server.
### Using Expo push notification service is not mandatory
You can use any push notification service for Expo projects. The [`getDevicePushTokenAsync` method from `expo-notifications`](/versions/latest/sdk/notifications/#getdevicepushtokenasync-devicepushtoken) allows you to get the native device push token, which you can then use with other services, or even [send your notifications directly through FCM and APNs](/push-notifications/sending-notifications-custom).
### Connections to notification service are encrypted
Expo's connections to Apple and Google are encrypted and use HTTPS.
### Contents of the notification are not stored
Expo doesn't store the contents of push notifications any longer than it takes to deliver them to the push notification services operated by Google and Apple. Notifications are stored only in memory and in message queues, not in databases.
### Contents of the notifications may be seen by Expo staff
If the Expo team is actively debugging the push notifications service, we may see notification contents (for example, at a breakpoint) but Expo cannot see push notification contents otherwise.
### Delivery guarantees
Expo makes the best effort to deliver notifications to the push notification services operated by Google and Apple. Expo's infrastructure is designed for at-least-once delivery to the underlying push notification services. In some cases, a notification may be delivered to Google or Apple more than once or not at all, although these cases are rare.
After a notification has been handed off to an underlying push notification service, Expo creates a "push receipt" that records whether the handoff was successful. A push receipt denotes whether the underlying push notification service received the notification.
Finally, the push notification services from Google and Apple follow their policies to deliver the notification to the device.
### When and why does the `ExpoPushToken` change
The `ExpoPushToken` remains the same across app upgrades. On Android, reinstalling the app may result in the token changing. On iOS, the token also remains the same even after uninstalling the app and reinstalling it.
It also changes if you change your [`applicationId`](/versions/latest/sdk/application/#applicationapplicationid) or `experienceId` (usually `@expoUsername/projectSlug`).
The `ExpoPushToken` never expires. However, if one of your users uninstalls the app, you'll receive a `DeviceNotRegistered` error back from Expo's servers. This means you should stop sending notifications to this token.
## Push notifications troubleshooting
### Notifications aren't working
Push notifications have a lot of moving parts, so this can be due to a wide variety of reasons. To narrow things down, check the [push ticket](/push-notifications/sending-notifications/#push-tickets) and [push receipt](/push-notifications/sending-notifications/#push-receipts) for error messages.
You can also narrow things even further by testing [local notifications](/versions/latest/sdk/notifications/#schedulenotificationasyncnotificationrequest-notificationrequestinput-promisestring) in your app. This ensures all of your client-side logic is correct, and narrow things down to the server side or app credentials.
Note: See here for some quick terminal commands you can use to get the push receipt
---
1. Send a notification:
```sh
curl -H "Content-Type: application/json" -X POST "https://exp.host/--/api/v2/push/send" -d '{
  "to": "ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]",
  "title":"hello",
  "body": "world"
}'
```
2. Use the resulting ticket `id` to request the push receipt:
```sh
curl -H "Content-Type: application/json" -X POST "https://exp.host/--/api/v2/push/getReceipts" -d '{
  "ids": [
    "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
  ]
}'
```
---
### Notifications work in development, but not in release mode
This indicates that you have either misconfigured your credentials or didn't configure them at all in your production app. In SDK 53 and later, Expo Go does not support push notifications functionality, so to test push you should use a [development build](/develop/development-builds/introduction/). In SDK 52 and earlier, Expo Go used Expo's credentials, which allowed push notifications to work in development without setting up your own credentials.
When you build your app for the app stores, you need to generate and use your own credentials. On Android, follow [this guide](/push-notifications/fcm-credentials). On iOS, this is handled by your [push key](/app-signing/app-credentials/#push-notification-keys) (revoking the push key associated with your app results in your notifications failing to be delivered. To fix that, add a new push key with `eas credentials`).
For more information, see [app signing](/app-signing/app-credentials).
### Notifications occasionally stop coming through on Android
This is likely due to the `priority` level of the notifications you're sending. You can learn more about [Android priority](https://firebase.google.com/docs/cloud-messaging/http-server-ref#downstream-http-messages-json). [Expo accepts four priorities](/push-notifications/sending-notifications/#message-request-format):
- `default`: manually mapped to the default priority documented by Apple and Google
- `high`: mapped to the high priority level documented by Apple and Google
- `normal`: mapped to the normal priority level documented by Apple and Google
- (priority omitted): treated exactly as if `default` were specified
Setting the priority to `high` gives your notification the greatest likelihood that Android will display the notification.
### Handle expired push notification credentials
When your push notification credentials have expired, run `eas credentials`, choose iOS and a build profile, then remove your push notification key and generate a new one.
### No valid aps-environment entitlement string found error for iOS
This error occurs if you haven't set up a push notification key for your iOS project. To check, go to the [Project Credentials page](https://expo.dev/accounts/[account]/projects/[project]/credentials/ios).
To generate a new push notification key, trigger a new build by running:
```sh
$ eas build --profile [profile] --platform ios
```
For a visual guide, see the [Expo Notifications with EAS video](https://youtu.be/BCCjGtKtBjE?t=2123).
### Error message when sending a notification
Check the `details` property of the returned push ticket or receipt for more information. [Read this](/push-notifications/sending-notifications/#errors) for common error code responses and their associated solutions.
### Fetching a push token takes a long time on iOS
`getDevicePushTokenAsync` and `getExpoPushTokenAsync` can sometimes take a long time to resolve on iOS. This is outside of `expo-notifications`'s control, as stated in Apple's [Troubleshooting Push Notifications](https://developer.apple.com/library/archive/technotes/tn2265/_index.html) technical note:
> This is not necessarily an error condition. The system may not have Internet connectivity at all because it is out of range of any cell towers or Wi-Fi access points, or it may be in airplane mode. Instead of treating this as an error, your app should continue normally, disabling only that functionality that relies on push notifications.
Here are some ways our community members have resolved this issue:
Note: Read the Apple's Technical Note on troubleshooting push notifications
---
Read Apple's [Technical Note on troubleshooting push notifications](https://developer.apple.com/library/archive/technotes/tn2265/_index.html)! This is the single most reliable source of information on this problem. To help you grasp what they're suggesting:
- Make sure the device has a reliable connection to the Internet (try turning off Wi-Fi or switching to another network, and disabling the firewall block on port 5223, as suggested in [this SO answer](https://stackoverflow.com/a/34332047/1123156)).
- **Bare React Native apps** must [manually enable the **Push Notifications** capability](/build-reference/ios-capabilities#manual-setup). If you have trouble setting this up, refer to [this Stack Overflow answer](https://stackoverflow.com/a/10791240/1123156). You may also want to try to debug this even further by logging persistent connection debug information as outlined by [this Stack Overflow answer](https://stackoverflow.com/a/8036052/1123156).
---
Note: Try again in a little while
---
- APNS servers near the device may be down as indicated by [this forum thread](https://developer.apple.com/forums/thread/52224). Take a walk and try again later!
- Try again in a few days as suggested by [this GitHub comment](https://github.com/expo/expo/issues/10369#issuecomment-717872956).
---
Note: Disable network sharing on your device
---
You may need to disable network sharing as this may impact the registration as suggested by [this Stack Overflow answer](https://stackoverflow.com/a/59156989/1123156).
---
Note: Restart your device
---
If you just changed the APNS servers where the app should be registering (by installing a TestFlight build over an Xcode build on the same device) you may need to restart your device as suggested by [this Stack Overflow answer](https://stackoverflow.com/a/59864028/1123156).
---
Note: Setup your device with a SIM card
---
If the device you're experiencing this on hasn't been setup with a SIM card it looks like configuring it may help mitigate this bug as suggested by [this Stack Overflow answer](https://stackoverflow.com/a/19432504/1123156).
---
## Miscellaneous
### Sending notifications directly through FCM and APNs
If you are not using [Expo push notification service](/push-notifications/sending-notifications) and instead, would like to communicate with Google and Apple directly, see [Send notifications with FCM and APNs](/push-notifications/sending-notifications-custom).
### Notification icon on Android is a gray or white square
This indicates an issue with the image asset you're providing. The image should be all white with a transparent background (this is required and enforced by Google, not Expo). For more information, see [this article](https://clevertap.com/blog/fixing-notification-icon-for-android-lollipop-and-above/).