Expo Push (Classic)

Tokens & receipts

How device tokens register and rotate, what Expo delivery receipts prove, when a dead token is removed, and where failures show.

Two token paths

Expo SDKPush notifications API
TokenExponentPushToken[…]APNs device token or FCM registration token
Mass registrationPOST /api/device/tokens (the SDK calls it)POST /api/universal/device/register
Indie registrationPOST /api/indie/idThe same endpoint, with subscriberId
Sends go throughExpo's push service, or FCM v1 and APNs directly when the app's credentials are saved in Native NotifyApple and Google directly
Delivery evidenceExpo receipts, checked about 15 minutes after a send. delivered means the device-side push service accepted it, not that the user saw it.APNs and FCM accept or reject at send time. Responses say delivered: null, and Native Notify never invents a number.

Registration and rotation

  • Register on every launch. Both endpoints are idempotent: re-registering never duplicates a device or token. The push API also refreshes lastSeenAt.
  • Tokens rotate after reinstalls and restores. Register the new value with the same device identity. On the push API, presenting a token again clears its dead flag.
  • A device can hold several tokens (an Expo token and a native APNs token), and a subscriber many devices. Native Notify never trims a device list. Only the receipt pipeline removes dead tokens.
  • Deregister (push API) is a hard, idempotent delete for logout and uninstall.
  • Registrations can carry optional metadata: app version, device timezone and, with the SDK's opt-in deviceId analytics flag, a stable device key.

Paired-token registration

native-notify v5.3.0 and later report a device's Expo token and native (FCM or APNs) token together as an optional devicePair, on POST /api/indie/id and POST /api/device/tokens (mass):

{
  "subID": "unique-user-id-string",
  "appId": 12345,
  "appToken": "your-app-token",
  "platformOS": "android",
  "expoToken": "ExponentPushToken[…]",
  "deviceToken": "<fcm-or-apns-token>",
  "devicePair": {
    "expoToken": "ExponentPushToken[…]",
    "nativeToken": "<fcm-or-apns-token>",
    "platform": "android"
  }
}

platform is "android" or "ios". The field is optional and additive: older servers ignore it, responses don't change, and a malformed devicePair is ignored. Without it, POST /api/indie/id infers the pair from expoToken and deviceToken.

With a pair, Native Notify sends a group, topic-group or follower push to a paired device once, not twice (through Expo and through FCM or APNs):

  • The Expo copy is skipped only when the device's native token is in the same send and the app's native credentials recently passed their live check.
  • Anything unknown, stale or failed keeps the Expo copy, so a broken credential never silences a device.
  • Unpaired subscribers and every other send path are unchanged, and a subscriber's token list is never trimmed.

Receipts (Expo path)

Native Notify queues the accepted tickets of mass and single-indie sends (group, topic and follower sends don't collect receipts) and checks receipts every 5 minutes, about 15 minutes after the send. It records accepted, delivered and failed counts per notification, updates the app's subscriber count from real deliveries and stores each failure's reason code. Read them in Native Notify (stats, failure reasons, open rates) or with get_notification_delivery_stats and get_notification_failures.

ReasonMeaning
DeviceNotRegisteredThat install is gone.
InvalidCredentials, MismatchSenderIdThe push credentials Expo holds for the token's Expo project are missing or wrong, so every token of that project fails.

You may also see MessageRateExceeded and MessageTooBig.

Delivery problem emails

When Expo can't find push credentials for an app's Expo project, Native Notify emails the app's owner with the fix. Replies go to support@nativenotify.com.

Expo's messageFix on expo.dev
Unable to retrieve the FCM server key…Add an FCM V1 service account key under Credentials → Android → Service credentials → FCM V1 service account key. See Add Android FCM V1 credentials.
Could not find APNs credentials for <bundle> (@owner/project)Set up a push key for that project: run eas credentials and choose iOS. See Expo's push notifications FAQ.

Only these two messages trigger an email, and only when one repeats across sends on 2 or more days, rejects at least 5 devices and 5% of a send, and is still happening. DeviceNotRegistered never does. An app gets at most one email per problem every 14 days (a repeat is marked as a reminder) and no more than three in all. To stop the emails, turn off Account & billing notices in Native Notify (gear icon → Email Preferences) or use the unsubscribe link in the email.

Dead tokens: strikes before cleanup

  1. A token earns a strike each time a receipt run reports DeviceNotRegistered.
  2. It's removed only after 2 or more strikes, never on the first.
  3. Removal is off by default on the server (DELETE_DEAD_TOKENS=1 enables it). With it off, strikes stay in the failure history and nothing is deleted.
  4. Deletion is suppressed for a run in which an app has 5 or more receipt errors and half or more are DeviceNotRegistered, or any is InvalidCredentials or MismatchSenderId. That shape is usually a credentials problem, not dead devices.

On the push API the same idea applies without receipts: a rejected token is recorded with the transport's answer, repeated failures accumulate strikes, and re-registering clears the dead flag (a token that moves to another device starts with fresh strikes). Check the counts with GET /api/universal/health/:appId/:appToken.

When a device didn't get it

  1. Registrations: is the token there, live and not struck? Check GET /api/universal/health/:appId/:appToken (push API) or the Audience view in Native Notify (Expo path).
  2. Failures: one device (DeviceNotRegistered) or every device (InvalidCredentials)? Check the failure-reasons card in Native Notify or get_notification_failures.
  3. One device: test-send (push API) or a test send from Native Notify (Expo path).
  4. The device: notification permission, battery and background settings, and whether the app deregistered it on logout.