new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Expo & React Native · all subjects

push-notifications

32 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

Push receipt error: MessageRateExceeded

If a push receipt has details.error set to 'MessageRateExceeded', you are sending messages too frequently to the given device. Implement exponential backoff and slowly retry sending messages.

Expo Push Service API endpoint

Send push notifications by making POST requests to https://exp.host/--/api/v2/push/send for sending messages and https://exp.host/--/api/v2/push/getReceipts for retrieving push receipts. The API does not currently require authentication, but you can optionally enable enhanced security by requiring access tokens.

Push notification message request HTTP headers

When sending push notifications to the Expo Push Service, use these HTTP headers: host: exp.host, accept: application/json, accept-encoding: gzip, deflate, content-type: application/json.

Expo Push Service message request body format

The request body must be JSON. It may be either a single message object or an array of up to 100 message objects, as long as they are all for the same project. It is recommended to use an array when sending multiple messages to minimize the number of requests to Expo servers.

Message request format fields for push notifications

Push notification messages support the following fields: | Field | Platform | Type | Description | |-------|----------|------|-------------| | to | Android and iOS | string \| string[] | An Expo push token or array of tokens specifying recipients (required) | | contentAvailable | iOS Only | boolean | When true, causes the iOS app to start in background to run a background task; app must be configured to support this; maps to aps.content-available | | data | Android and iOS | Object | A JSON object delivered to the app; may be up to about 4KiB; total notification payload must be at most 4KiB | | title | Android and iOS | string | The title to display in the notification, often displayed above the body; maps to AndroidNotification.title and aps.alert.title | | body | Android and iOS | string | The message to display in the notification; maps to AndroidNotification.body and aps.alert.body | | ttl | Android and iOS | number | Time to Live: number of seconds the message may be kept for redelivery if not delivered; omit to use each provider's default of 4 weeks | | expiration | Android and iOS | number | Timestamp since Unix epoch specifying when the message expires; same effect as ttl, but ttl takes precedence | | priority | Android and iOS | 'default' \| 'normal' \| 'high' | Delivery priority of the message; 'default' or omit to use platform defaults (normal on Android, high on iOS) | | subtitle | iOS Only | string | The subtitle to display below the title; maps to aps.alert.subtitle | | sound | iOS Only | string \| null | Play a sound when recipient receives notification; specify 'default' for device's default sound; custom sounds must be configured via config plugin and specified with file extension like 'bells_sound.wav' | | badge | iOS Only | number | Number to display in badge on app icon; specify zero to clear the badge | | interruptionLevel | iOS Only | 'active' \| 'critical' \| 'passive' \| 'time-sensitive' | The importance and delivery timing of a notification; corresponds to UNNotificationInterruptionLevel enumeration cases | | channelId | Android Only | string | ID of the Notification Channel through which to display this notification; if specified channel does not exist on device, notification will not display | | icon | Android Only | string | The notification's icon; name of an Android drawable resource (example: 'myicon'); defaults to icon specified in config plugin | | richContent | Android and iOS | Object | Currently supports setting a notification image with key 'image' and string URL value; Android shows image out of box; iOS requires Notification Service Extension target | | categoryId | Android and iOS | string | ID of the notification category this notification is associated with | | collapseId | Android and iOS | string | Identifier for collapsing notifications; on Android coalesces messages in transit and maps to FCM collapse_key; on iOS coalesces in transit and replaces already-displayed notifications, maps to apns-collapse-id | | tag | Android Only | string | Identifier for replacing notifications already displayed on device; if device shows notification with same tag, new notification replaces it; separate from collapseId which coalesces in transit; maps to FCM notification.tag | | threadId | iOS Only | string | Identifier by which system visually groups notifications together; notifications sharing threadId are stacked into single group; unlike collapseId, no notification is replaced; maps to aps.thread-id | | mutableContent | iOS Only | boolean | Specifies whether this notification can be intercepted by client app; defaults to false |

Push ticket response format

Push ticket responses from sending notifications contain the structure: { "data": [ { "status": "error" | "ok", "id": string (receipt ID if ok), "message": string (if error), "details": JSON (if error) }, ... ], "errors": [{ "code": string, "message": string }] (only populated if there was an error with entire request) }

Push receipt request format

To fetch push receipts, send a POST request to https://exp.host/--/api/v2/push/getReceipts with JSON request body: { "ids": string[] } where ids is an array of ticket ID strings.

Push receipt response format

Push receipt responses contain the structure: { "data": { Receipt ID: { "status": "error" | "ok", "message": string (if error), "details": JSON (if error) }, ... }, "errors": [{ "code": string, "message": string }] (only populated if there was an error with entire request) }

Push ticket status meanings

A status of 'ok' along with a receipt ID means the message was received by Expo's servers, not that it was received by the user. To determine if the message reached the user, you must check the push receipt.

Push ticket error: DeviceNotRegistered

If a push ticket has an error status with details.error set to 'DeviceNotRegistered', the device cannot receive push notifications anymore and you should stop sending messages to the corresponding Expo push token.

Push receipt error: DeviceNotRegistered

If a push receipt has an error status with details.error set to 'DeviceNotRegistered', the device cannot receive push notifications anymore and you should stop sending messages to the corresponding Expo push token. This indicates the device has unsubscribed from notifications (e.g., by revoking permissions or uninstalling the app) and APNs or FCM has responded with this information.

Push receipt error: MessageTooBig

If a push receipt has details.error set to 'MessageTooBig', the total notification payload was too large. On Android and iOS, the total payload must be at most 4096 bytes.

Push receipt error: MismatchSenderId

If a push receipt has details.error set to 'MismatchSenderId', there is an issue with FCM push credentials. Both the FCM server key and google-services.json file must be associated with the same sender ID. Check that the server key from EAS dashboard under Credentials > Application identifier > Service Credentials > FCM V1 service account key and the sender ID from google-services.json > project_number match the sender ID shown in Firebase console under Project Settings > Cloud Messaging tab > Cloud Messaging API (Legacy).

Push receipt error: InvalidCredentials

If a push receipt has details.error set to 'InvalidCredentials', your push notification credentials for your standalone app are invalid (e.g., you may have revoked them). For Android: make sure you have correctly uploaded the server key from Firebase Console. For iOS: run eas credentials and follow prompts to regenerate new push notification credentials. If you revoke an APN key, all apps relying on it cannot send or receive notifications until you upload a new key. Uploading a new APN key will not change users' Expo Push Tokens. Sometimes these errors contain an InvalidProviderToken detail, which is tied to both your APN key and provisioning profile; rebuild the app and regenerate a new push key and provisioning profile to resolve.

Request error: TOO_MANY_REQUESTS

If a push request fails with error code 'TOO_MANY_REQUESTS', you are exceeding the request limit of 600 notifications per second per project. Implement rate-limiting in your server to prevent sending more than 600 notifications per second. The expo-server-sdk-node already implements this along with exponential backoffs for retries.

Request error: PUSH_TOO_MANY_EXPERIENCE_IDS

If a push request fails with error code 'PUSH_TOO_MANY_EXPERIENCE_IDS', you are trying to send push notifications to different Expo experiences (e.g., @username/projectAAA and @username/projectBBB). Check the details field for a mapping of experience names to their associated push tokens from the request, and remove any from another experience.

Request error: PUSH_TOO_MANY_NOTIFICATIONS

If a push request fails with error code 'PUSH_TOO_MANY_NOTIFICATIONS', you are trying to send more than 100 push notifications in one request. Make sure you are only sending 100 (or fewer) notifications in each request.

Request error: PUSH_TOO_MANY_RECEIPTS

If a push request fails with error code 'PUSH_TOO_MANY_RECEIPTS', you are trying to get more than 1000 push receipts in one request. Make sure you are only sending an array of 1000 (or fewer) ticket ID strings to get your push receipts.

Limit concurrent connections when sending bulk notifications

When sending a large number of push notifications at once, limit the number of concurrent connections. The Node SDK implements this and opens a maximum of six concurrent connections to smooth out peak load and help the Expo push notification service receive requests successfully.

Retry push notifications with exponential backoff

When sending push notifications fails due to temporary issues (network errors, HTTP 429 Too Many Requests, HTTP 5xx Server Errors), use exponential backoff to wait before retrying. If the first retry is unsuccessful, wait longer and retry again following exponential backoff pattern to let the temporarily unavailable service recover.

When to check push receipts for errors

You must check push receipts for errors. If there is an issue delivering push notifications, push receipts are the best way to get information about the underlying cause. Push receipts may indicate a problem with FCM, APNs, the Expo push notification service, or your notification payload. Recommend checking push receipts 15 minutes after sending notifications, as receipts are often available much sooner but a 15-minute window gives the service comfortable time to make receipts available. Push receipts are cleared after 24 hours.

cURL example: send single push notification

Example of sending a single push notification using cURL: ```sh curl -H "Content-Type: application/json" -X POST "https://exp.host/--/api/v2/push/send" -d '{ "to": "ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]", "title":"hello", "body": "world" }' ```

cURL example: send multiple push notifications

Example of sending multiple push notifications in a single request: ```json [ { "to": "ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]", "sound": "default", "body": "Hello world!" }, { "to": "ExponentPushToken[yyyyyyyyyyyyyyyyyyyyyy]", "badge": 1, "body": "You've got mail" }, { "to": [ "ExponentPushToken[zzzzzzzzzzzzzzzzzzzzzz]", "ExponentPushToken[aaaaaaaaaaaaaaaaaaaaaa]" ], "body": "Breaking news!" } ] ```

cURL example: get push receipts

Example of fetching push receipts for sent notifications: ```sh curl -H "Content-Type: application/json" -X POST "https://exp.host/--/api/v2/push/getReceipts" -d '{ "ids": [ "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", "YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY", "ZZZZZZZZ-ZZZZ-ZZZZ-ZZZZ-ZZZZZZZZZZZZ" ] }' ```

Push notification payload compression

The Expo Push Service optionally accepts gzip-compressed request bodies, which can greatly reduce the amount of upload bandwidth needed to send large numbers of notifications. The Node Expo Server SDK automatically gzips requests and automatically throttles requests to smooth out the load.

Server SDKs for Expo Push Service

Expo provides and community maintains server SDKs for sending push notifications. Official SDKs: expo-server-sdk-node (Node.js, maintained by Expo team). Community SDKs: expo-server-sdk-python (Python), expo-server-sdk-ruby (Ruby), expo-push-notification-client-rust (Rust), expo-notifier (Symfony), exponent-server-sdk-php (PHP), expo-server-sdk-php (PHP), exponent-server-sdk-golang (Golang), exponent (Golang), exponent-server-sdk-elixir (Elixir), expo-server-sdk-dotnet (dotnet), expo-server-sdk-java (Java), laravel-expo-notifier (Laravel). Each SDK is a wrapper around the Expo Push Service API.

Push notification security: access token authentication

You can enable enhanced push security from your EAS Dashboard to require any push requests to be sent with a valid access token before Expo will deliver them. By default, tokens can be sent without authentication, but if tokens leak, a malicious user could impersonate your server. If using expo-server-sdk-node v3.6.0 or later, pass your accessToken as an option in the constructor. Otherwise, pass the header 'Authorization': 'Bearer ${accessToken}' with requests to the push API. Requests sent without a valid access token after enabling push security result in error code 'UNAUTHORIZED'.

Push notification delivery guarantees and policy

Expo makes a best effort to deliver notifications to push notification services operated by Google and Apple. Expo's infrastructure is designed for at least one attempt at delivery to underlying services. It is more likely for a notification to be delivered to Google or Apple more than once rather than not at all; however, both results are uncommon. After a notification is handed off to an underlying service, Expo creates a push receipt that records whether the handoff was successful. A push receipt denotes whether the underlying service received the notification. The push notification services from Google and Apple follow their own policies to deliver notifications to devices.

Note on TTL field for push notifications

On Android, Expo makes best effort to deliver messages with zero TTL immediately and does not throttle them. However, setting TTL to a low value (e.g., zero) can prevent normal-priority notifications from ever reaching Android devices in doze mode. To guarantee notification delivery, TTL must be long enough for the device to wake from doze mode. The ttl field takes precedence over expiration when both are specified.

Note on priority field for push notifications

On Android, normal-priority messages won't open network connections on sleeping devices and delivery may be delayed to conserve battery. High-priority messages are more likely to be delivered immediately and may wake sleeping devices to open network connections, consuming energy. On iOS, normal-priority messages are sent considering power and may be grouped and delivered in bursts; they are throttled and may not be delivered by Apple. High-priority messages are usually sent immediately. Normal priority corresponds to APNs priority level 5 and high priority to level 10.

Note on channelId field for push notifications

If channelId is left null, a 'Default' channel is used and Expo creates the channel on the device if it does not yet exist. However, use caution, as the 'Default' channel is user-facing and you may not be able to fully delete it.

Note on contentAvailable field deprecation

The contentAvailable field replaces the deprecated _contentAvailable field, which is still accepted for backwards compatibility. If you specify both, contentAvailable takes precedence.

Give your agent this brain