diff --git a/src/pages/docs/platform/account/app/notifications.mdx b/src/pages/docs/platform/account/app/notifications.mdx
index c5ab19b5d5..36950412a7 100644
--- a/src/pages/docs/platform/account/app/notifications.mdx
+++ b/src/pages/docs/platform/account/app/notifications.mdx
@@ -36,6 +36,35 @@ Before you can start using Ably's [push notification](/docs/push) services, you
* Copy everything between and including `-----BEGIN CERTIFICATE-----` and `-----END CERTIFICATE-----`, and paste it into the **PEM cert** text box of the Apple push notification service section of your Ably notifications app [dashboard](https://ably.com/accounts).
* Similarly, copy everything between and including `-----BEGIN PRIVATE KEY-----` and `-----END PRIVATE KEY-----`, and paste it into the **PEM private key** text box of the same section. Then, click **Save**.
+#### APNs sandbox endpoint
+
+Apple provides both production and sandbox endpoints for APNs. The sandbox endpoint is used for testing push notifications during development, while the production endpoint is used for live apps.
+
+When to use the sandbox endpoint:
+- During app development and testing.
+- For test/staging environments.
+- When using development provisioning profiles.
+- For internal testing before app store release.
+
+When to use the production endpoint:
+- For live apps distributed through the App Store.
+- For production environments.
+- When using distribution provisioning profiles.
+
+Important considerations:
+- Device tokens generated in the sandbox environment will **not work** in production, ensuring safe testing.
+- Many developers maintain separate Ably apps: one configured for sandbox (testing) and another for production.
+- Device tokens are environment-specific and cannot be used interchangeably.
+
+How to configure the sandbox endpoint:
+1. In the Ably [dashboard](https://ably.com/accounts), navigate to the **Notifications** tab under app settings.
+2. Go to **push notifications setup** and click **configure push**.
+3. In the **Apple push notification service** section, look for the **sandbox endpoint** option.
+4. Enable the sandbox endpoint checkbox to use Apple's testing environment.
+5. Click **save** to apply the changes.
+
+This separation between sandbox and production environments ensures that test notifications don't reach real users and provides a safe environment for development and testing.
+
## Push inspector
The Push inspector tool enables you to manually send push notifications by specifying the required data and notification fields. This tool helps test and debug your notification setup before going live.
@@ -50,9 +79,9 @@ Define the content of your push notification using the fields below:
| Field | Purpose | How to Use |
| ----- | ------- | ---------- |
-| **Notification title** | A title for the push notification, which will appear as the headline on the user's device. | Enter a short, clear title that captures the essence of the notification. |
-| **Notification body** | The main content of the notification to be displayed below the title. | Write the key information or message that you want the user to read. |
-| **Notification data** | Optional JSON data that the app can use for specific actions upon receiving the notification. | Include any extra data needed for app functionality, such as custom metadata or instructions. |
+| Notification title | A title for the push notification, which will appear as the headline on the user's device. | Enter a short, clear title that captures the essence of the notification. |
+| Notification body | The main content of the notification to be displayed below the title. | Write the key information or message that you want the user to read. |
+| Notification data | Optional JSON data that the app can use for specific actions upon receiving the notification. | Include any extra data needed for app functionality, such as custom metadata or instructions. |
### Push notification target
@@ -60,9 +89,9 @@ Direct your push notifications to specific targets within the Ably platform. Sel
| Target | Purpose | How to Use |
| ------ | ------- | ---------- |
-| **Channel name** | Push notifications to all subscribers of a specific channel. | Enter the channel name and click **push to channel** to notify all devices subscribed to that channel. |
-| **Device ID** | Send a notification directly to a specific device. | Enter the Device ID and click **push to device** to target a single device with the notification. |
-| **Client ID** | Notify a specific client registered with Ably. | Enter the Client ID and click **push to client** to send the notification to the chosen client. |
+| Channel name | Push notifications to all subscribers of a specific channel. | Enter the channel name and click push to channel to notify all devices subscribed to that channel. |
+| Device ID | Send a notification directly to a specific device. | Enter the Device ID and click push to device to target a single device with the notification. |
+| Client ID | Notify a specific client registered with Ably. | Enter the Client ID and click push to client to send the notification to the chosen client. |
## Push inspector widget
@@ -70,6 +99,6 @@ The Push Inspector widget allows you to monitor and manage your push notificatio
| Section | Purpose | How to Use |
| ------- | ------- | ---------- |
-| **Channel subscriptions** | View and inspect all channels currently subscribed to push notifications. | Click **inspect channel** to see detailed information about a specific channel, including the number of subscribers and recent activity. |
-| **Device registrations** | Access a list of all devices registered to receive push notifications. | Click **inspect device** to view detailed information about a specific device, such as its registration status, platform, and recent notifications. |
-| **Client registrations** | Get an overview of all clients registered for push notifications within your Ably account. | Click **inspect client ID** to see detailed information about a specific client, including its subscriptions and recent activity. |
+| Channel subscriptions | View and inspect all channels currently subscribed to push notifications. | Click inspect channel to see detailed information about a specific channel, including the number of subscribers and recent activity. |
+| Device registrations | Access a list of all devices registered to receive push notifications. | Click inspect device to view detailed information about a specific device, such as its registration status, platform, and recent notifications. |
+| Client registrations | Get an overview of all clients registered for push notifications within the Ably account. | Click inspect client ID to see detailed information about a specific client, including its subscriptions and recent activity. |
diff --git a/src/pages/docs/platform/pricing/faqs.mdx b/src/pages/docs/platform/pricing/faqs.mdx
index a641669003..b55fbe1ad4 100644
--- a/src/pages/docs/platform/pricing/faqs.mdx
+++ b/src/pages/docs/platform/pricing/faqs.mdx
@@ -29,6 +29,8 @@ FAQs related to pricing concepts and package limits.
The [limit](/docs/platform/pricing/limits#connection) on concurrent connections is for the maximum number of realtime clients [connected](/docs/connect) to Ably simultaneously at any point in time. HTTP requests such as those from REST clients do not count towards this number, as it is solely related to realtime connections.
+Push notifications and connections: Devices registered for [push notifications](/docs/push) don't count toward concurrent connection limits when they're not actively connected to Ably, since push notifications are delivered through the device's operating system rather than maintaining an active connection.
+
### How do you count concurrent channels?
The [limit](/docs/platform/pricing/limits#channel) on concurrent channels is for the maximum number of channels that are active simultaneously at any point in time.
@@ -37,6 +39,8 @@ The [limit](/docs/platform/pricing/limits#channel) on concurrent channels is for
A channel will automatically close when there are no more realtime clients attached, and approximately one minute has passed since the last realtime client detached or since a message was published to the channel.
+Push notifications and channels: When publishing [push notifications](/docs/push) via channels, those channels become activated and count toward concurrent (peak) channel count for billing purposes, even if no realtime clients are connected to them.
+
For example, if you have 10,000 users, and at your busiest time of the month there is a single spike where 500 customers establish a realtime connection to Ably and each attach to one unique channel, and one global shared channel. The concurrent number of channels would be the sum of the 500 unique channels per client and the one global shared channel, so 501 concurrent channels.
### How is maximum message size measured?
diff --git a/src/pages/docs/push/configure/device.mdx b/src/pages/docs/push/configure/device.mdx
index 36286e65f9..a62dbc14c5 100644
--- a/src/pages/docs/push/configure/device.mdx
+++ b/src/pages/docs/push/configure/device.mdx
@@ -468,6 +468,78 @@ private ClientOptions GetAblyOptions(string clientId)
```
+#### Manual device registration with cross-platform push services
+
+In some scenarios, you may want to use FCM for iOS devices instead of APNs, or manually register devices with custom platform configurations. This is useful when using Firebase's unified messaging system across both Android and iOS platforms.
+
+
+
+To manually register a device using FCM with iOS:
+
+1. Use the Firebase iOS SDK to obtain an FCM registration token.
+2. Create a `DeviceDetails` object with the appropriate platform and transport configuration.
+3. Register the device using the Push Admin API.
+
+
+```swift
+// Get FCM token from Firebase iOS SDK first
+// Then create DeviceDetails object manually
+
+let deviceDetails = ARTDeviceDetails(id: ULID().ulidString)
+deviceDetails.secret = generateSecret()
+deviceDetails.platform = "ios" // or "android"
+deviceDetails.formFactor = "phone" // or "tablet", "tv", "watch", "desktop", "car"
+deviceDetails.clientId = "clientA"
+deviceDetails.metadata = NSMutableDictionary()
+deviceDetails.push.recipient = [
+ "transportType": "fcm", // Use FCM instead of APNs
+ "registrationToken": "your-fcm-registration-token"
+]
+
+// Register the device using Push Admin API
+rest.push.admin.deviceRegistrations.save(deviceDetails) { error in
+ if let error = error {
+ print("Registration failed: \(error)")
+ } else {
+ print("Device registered successfully")
+ }
+}
+```
+
+```objc
+// Get FCM token from Firebase iOS SDK first
+// Then create DeviceDetails object manually
+
+ARTDeviceDetails *deviceDetails = [[ARTDeviceDetails alloc] initWithId:[[NSUUID UUID] UUIDString]];
+deviceDetails.secret = [self generateSecret];
+deviceDetails.platform = @"ios"; // or @"android"
+deviceDetails.formFactor = @"phone"; // or @"tablet", @"tv", @"watch", @"desktop", @"car"
+deviceDetails.clientId = @"clientA";
+deviceDetails.metadata = [[NSMutableDictionary alloc] init];
+deviceDetails.push.recipient = @{
+ @"transportType": @"fcm", // Use FCM instead of APNs
+ @"registrationToken": @"your-fcm-registration-token"
+};
+
+// Register the device using Push Admin API
+[rest.push.admin.deviceRegistrations save:deviceDetails callback:^(ARTErrorInfo *error) {
+ if (error) {
+ NSLog(@"Registration failed: %@", error);
+ } else {
+ NSLog(@"Device registered successfully");
+ }
+}];
+```
+
+
+This approach allows:
+- Using FCM for both Android and iOS devices for unified push messaging.
+- Overriding the default platform-specific push service assignments.
+- Maintaining full control over device registration parameters.
+- Integration with Firebase's analytics and other services across platforms.
+
#### Test your push notification activation
* Use the Ably [dashboard](https://ably.com/accounts) or [API](https://ably.com/docs/docs/api/realtime-sdk/push-admin) to send a test push notification to a registered device.
diff --git a/src/pages/docs/push/index.mdx b/src/pages/docs/push/index.mdx
index 074380898c..45798f66d1 100644
--- a/src/pages/docs/push/index.mdx
+++ b/src/pages/docs/push/index.mdx
@@ -17,11 +17,19 @@ redirect_from:
Push notifications notify user devices or browsers regardless of whether an application is open and running. They deliver information, such as app updates, social media alerts, or promotional offers, directly to the user's screen. Ably sends push notifications to devices using [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging) or [Apple Push Notification Service](https://developer.apple.com/notifications/), and to browsers using [Web Push](https://developer.mozilla.org/en-US/docs/Web/API/Push_API). Push notifications don't require a device or browser to stay connected to Ably. Instead, a device's or browser's operating system or web browser maintains its own battery-efficient transport to receive notifications.
+## Billing and connection considerations
+
+Push notifications have specific billing implications that differ from regular realtime connections:
+
+- Push subscriptions don't count as connections: Devices registered for push notifications don't count toward [concurrent connection](/docs/platform/pricing/limits#connection) limits when they're not actively connected to Ably.
+- Channel activation affects billing: When publishing push notifications via channels, those channels become activated and count toward concurrent (peak) channel count for billing purposes.
+- Multiple devices, one message count: Publishing a single push notification to multiple devices via a channel counts as one outbound message per recipient device.
+
You can publish push notifications to user devices or browsers [directly](/docs/push/publish/#direct-publishing) or [via channels](#via-channels).
-**Publishing directly** sends push notifications to specific devices or browsers identified by unique identifiers, such as a `deviceId` or a `clientId`. This approach is akin to sending a personal message or alerts directly to an individual user's device or browser, bypassing the need for channel subscriptions. It excels in targeted and personalized communications, such as alerts specific to a user's actions, account notifications, or customized updates.
+Publishing directly sends push notifications to specific devices or browsers identified by unique identifiers, such as a `deviceId` or a `clientId`. This approach is akin to sending a personal message or alerts directly to an individual user's device or browser, bypassing the need for channel subscriptions. It excels in targeted and personalized communications, such as alerts specific to a user's actions, account notifications, or customized updates.
-**Publishing via channels** uses a [Pub/Sub](/docs/channels/messages) model. Messages are sent to channels to which multiple devices or browsers can subscribe. When a message is published to a channel, all devices or browsers subscribed to that channel receive the notification. This approach is particularly powerful for simultaneously publishing messages to multiple users.
+Publishing via channels uses a [Pub/Sub](/docs/channels/messages) model. Messages are sent to channels to which multiple devices or browsers can subscribe. When a message is published to a channel, all devices or browsers subscribed to that channel receive the notification. This approach is particularly powerful for simultaneously publishing messages to multiple users.
## Push notification process
@@ -107,3 +115,84 @@ const channel = realtime.channels.get('[meta]log:push');
channel.subscribe(msg => console.log(msg));
```
+
+## Troubleshooting push notifications
+
+If not receiving expected push notifications, follow these debugging steps:
+
+### Verify device registration
+
+Check if the device is registered in the Ably dashboard:
+1. Go to Notifications tab → Device inspector
+2. Search for the device using `deviceId` or `clientId`
+3. Verify device state:
+ - ACTIVE: Ready to receive notifications
+ - FAILED: Check error details (common: registration expired, invalid token)
+
+### Test delivery
+
+Use the [push inspector](/docs/platform/account/app/notifications#push-inspector) in the dashboard to send test notifications directly to the device.
+
+### Check for errors
+
+Monitor the `[meta]log:push` channel for delivery errors. Common platform-specific issues:
+
+APNs (iOS):
+- BadDeviceToken: Environment mismatch (sandbox vs production)
+- DeviceTokenNotForTopic: Bundle ID mismatch in certificate
+
+FCM (Android):
+- FcmTransport not configured: Invalid service account JSON
+- 403 Forbidden: FCM API not enabled or permission issues
+- 404 NOT_FOUND: Device unregistered (app uninstalled)
+
+Web Push:
+- Service worker registration failed: Conflicting VAPID keys from previous registrations
+
+### Cannot publish push notifications
+
+If unable to publish push notifications on channels, check these common issues:
+
+#### Channel rules
+
+Push notifications are disabled by default on all channels. Explicitly enable push notifications for each channel or namespace:
+
+1. Go to the Ably dashboard → App settings → Channel rules.
+2. Create or edit a rule for your channel/namespace.
+3. Enable "Push notifications enabled".
+
+#### Permissions
+
+Ensure the publisher has the correct capabilities:
+
+- Publishing requires `publish` capability for the channel.
+- Push-specific operations may require additional push capabilities.
+
+#### Invalid payload
+
+Verify the push payload in the message `extras` field is valid:
+
+- Must include a `push` object with valid notification structure.
+- Check payload format matches platform requirements (APNs, FCM, Web Push).
+
+### Duplicate notifications
+
+If receiving duplicate push notifications for the same device, this typically occurs when a device has both a `deviceId` and `clientId` subscription to the same channel:
+
+- `deviceId` subscriptions deliver notifications to a single specific device.
+- `clientId` subscriptions deliver notifications to all devices sharing that client ID.
+
+If a device has both subscription types, it will receive the notification twice. To resolve:
+1. Use the [push inspector widget](/docs/platform/account/app/notifications#push-inspector-widget) to check channel subscriptions.
+2. Remove either the `deviceId` or `clientId` subscription for the affected device.
+
+### Debug checklist
+
+- Device shows as ACTIVE in dashboard.
+- Push credentials configured correctly.
+- Environment settings match (sandbox/production).
+- App has notification permissions.
+- No errors in `[meta]log:push` channel.
+- Check for duplicate subscriptions (both `deviceId` and `clientId`).
+
+For detailed troubleshooting steps, see the [push inspector documentation](/docs/platform/account/app/notifications#push-inspector) and [error logging](/docs/metadata-stats/metadata/subscribe#log).
diff --git a/src/pages/docs/push/publish.mdx b/src/pages/docs/push/publish.mdx
index 7837104677..2b25ca3208 100644
--- a/src/pages/docs/push/publish.mdx
+++ b/src/pages/docs/push/publish.mdx
@@ -1345,3 +1345,121 @@ channel = rest.channels.get('pushenabled:foo');
channel.publish(message);
```
+
+## Handling push notifications on devices
+
+After Ably delivers push notifications to devices, mobile applications need to handle them appropriately. This includes processing the notification content, navigating users to relevant sections of the app, and implementing deep linking functionality.
+
+### Deep links and app navigation
+
+Deep links allow push notifications to navigate users directly to specific screens or content within mobile applications. While Ably handles the delivery of push notifications, creating and handling deep links is implemented within mobile app code.
+
+Deep links are not created using Ably itself. Instead, handle deep links in app push notification receivers and use the notification data to determine where to navigate users.
+
+#### Android deep linking with push notifications
+
+On Android, implement deep links and app navigation in several ways.
+
+Using `onMessageReceived` (recommended):
+
+Handle push notifications in `FirebaseMessagingService` and create deep links programmatically:
+
+```java
+@Override
+public void onMessageReceived(RemoteMessage remoteMessage) {
+ // Extract data from Ably push notification
+ Map data = remoteMessage.getData();
+
+ // Create deep link based on notification data
+ if (data.containsKey("screen")) {
+ String targetScreen = data.get("screen");
+ Intent intent = createDeepLinkIntent(targetScreen, data);
+ startActivity(intent);
+ }
+}
+
+private Intent createDeepLinkIntent(String screen, Map data) {
+ Intent intent = new Intent(this, MainActivity.class);
+ intent.putExtra("navigate_to", screen);
+ intent.putExtra("data", new Bundle(data));
+ return intent;
+}
+```
+
+Using `click_action` with intent filters:
+
+Specify a `click_action` in the push notification payload and handle it with intent filters:
+
+1. Add `click_action` to the notification payload when publishing via Ably:
+
+```json
+{
+ "push": {
+ "notification": {
+ "title": "New Message",
+ "body": "You have a new message",
+ "click_action": "OPEN_CHAT_SCREEN"
+ },
+ "data": {
+ "chat_id": "12345",
+ "user_id": "67890"
+ }
+ }
+}
+```
+
+2. Configure intent filters in `AndroidManifest.xml`:
+```xml
+
+
+
+
+
+
+```
+
+#### iOS deep linking with push notifications
+
+On iOS, handle deep links in the app delegate or scene delegate:
+
+Using URL schemes:
+```swift
+func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
+ // Extract navigation data from Ably push notification
+ if let screen = userInfo["screen"] as? String {
+ navigateToScreen(screen, with: userInfo)
+ }
+}
+
+private func navigateToScreen(_ screen: String, with data: [AnyHashable: Any]) {
+ // Implement navigation logic based on screen parameter
+ switch screen {
+ case "chat":
+ if let chatId = data["chat_id"] as? String {
+ showChatScreen(chatId: chatId)
+ }
+ case "profile":
+ if let userId = data["user_id"] as? String {
+ showProfileScreen(userId: userId)
+ }
+ default:
+ showMainScreen()
+ }
+}
+```
+
+Using Universal Links:
+Configure universal links in the app and handle them when users tap on push notifications:
+
+```swift
+func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
+ guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
+ let url = userActivity.webpageURL else {
+ return false
+ }
+
+ // Handle universal link from push notification
+ handleUniversalLink(url)
+ return true
+}
+```