---
source_url: https://www.pubnub.com/docs/migration-guides/apns2-push-notifications
title: Migrate to APNs2 push notifications
updated_at: 2026-09-30T07:20:08.000Z
---

# Migrate to APNs2 push notifications

## Documentation index

To discover more PubNub resources:

1. Fetch [PubNub's llms.txt](https://www.pubnub.com/llms-full.txt) for a list of available pages in Markdown format.
2. Identify relevant URLs from that index.
3. Fetch the target pages.

Do not assume a path exists, always check the index first.

If your keyset still uses a certificate-based APNs1 configuration for [Mobile Push Notifications](https://www.pubnub.com/docs/integrations/mobile-push-notifications/overview.md) to Apple Push Notification service (APNs) devices, follow this guide to move to token-based APNs2 push. A keyset can hold an APNs1 certificate and an APNs2 token at the same time. That means you can complete every step below without a hard cutover: existing APNs1 devices keep receiving notifications until you remove their registrations in the last step.

Apple retired the legacy binary APNs protocol on March 31, 2021. PubNub still processes APNs1 certificate-based requests, so nothing breaks if you wait. But Apple no longer develops that protocol, so plan to complete this migration.

## Before you start

Confirm you have:

* A paid [Apple Developer Program](https://developer.apple.com/programs/) membership, to create the APNs2 authentication key.
* Access to the [Admin Portal](https://admin.pubnub.com/) for the keyset you're migrating.
* An SDK version that exposes APNs2 device-registration methods, such as `addAPNSDevicesOnChannels` in the Swift SDK or the `pushGateway: 'apns2'` option in the JavaScript SDK. Check your platform's reference in [Available SDKs](https://www.pubnub.com/docs/getting-started/available-sdks.md) if you're not sure your version supports it.

## Migrate with an AI coding assistant

If you use an AI coding assistant, paste this prompt into it to update your code. The prompt makes the assistant read this guide, list the affected code first, and stop before it changes push payloads or removes APNs1 registrations. Do the Apple Developer and Admin Portal steps in [Configure APNs2 on your keyset](#configure-apns2-on-your-keyset) yourself.

```text
Migrate this codebase's PubNub push notifications from certificate-based APNs1
to token-based APNs2.

1. Read the migration guide first:
   https://www.pubnub.com/docs/migration-guides/apns2-push-notifications.md
   If the PubNub MCP server is connected, you can call get_general_migration_guide
   instead. Where the guide and your own knowledge of PubNub SDKs disagree,
   follow the guide and tell me.
2. Before you edit anything, list every APNs device registration and removal
   call, such as pushGateway 'apns' or addPushChannelRegistrations. Also list
   every publish payload that contains pn_apns, and the PubNub SDK version in use. Then show me
   a plan and wait for my approval.
3. Before you change code, ask me to confirm that I uploaded the APNs2 .p8 token
   file to the keyset in the Admin Portal. You can't do that step.
4. Change device registrations to APNs2. Keep the channels and device token,
   drop the old push type, and add topic and environment. Set topic to the app's
   bundle identifier. Use development for Xcode-installed builds and production for
   TestFlight and App Store builds. If the SDK version has no APNs2 registration
   method, upgrade it and tell me.
5. STOP before you touch push payloads. Ask me to confirm that listing the push
   channels for a test device returns the channels we expect.
6. In each payload, add pn_push inside pn_apns with version "v2" and a targets
   entry with topic and environment. Change only the payload, not the publish()
   call.
7. STOP before you remove any APNs1 registration. Remove them only after I
   confirm that every device we care about is verified on APNs2. A device
   registered under both gateways gets every notification twice.
8. Make one small change at a time. After each change, run the build and tests
   and show me the output.
9. Never add the .p8 file, the PubNub secret key, or any other credential to the
   repository or to client code.
10. When you finish, list every file you changed and the steps left for me.
```

## Configure APNs2 on your keyset

Create an APNs2 authentication key in your Apple Developer account, then hand it to PubNub.

1. Sign in to your [Apple Developer account](https://developer.apple.com/account/) and select **Certificates, Identifiers & Profiles**.
2. Select **Keys** in the sidebar, then select the add button (**+**) to register a new key.
3. Give the key a name, select the **Apple Push Notifications service (APNs)** checkbox, then select **Continue** and **Register**.
4. Select **Download**. You get a file named something like `AuthKeyABCD1234.p8`. Apple lets you download it once, so keep it somewhere safe.
5. Note the Key ID, the part of the filename between `AuthKey` and `.p8`. Also note your Team ID, the 10-character string next to your team name under **Membership details**.
6. Open the [Admin Portal](https://admin.pubnub.com) and select the app and keyset you're migrating.
7. Go to the **Key Options** page and scroll to the **Mobile Push Notifications** section.
8. Select the **APNs2** tab, then select **Upload Token File** and choose the `.p8` file you downloaded. This also populates the Team ID and Auth Key ID fields from the file, so confirm they match what you noted above.
9. Save the keyset.

:::note Existing certificate stays active
Uploading a token doesn't remove your existing APNs1 certificate. Both configurations stay active on the keyset until you decide to remove the certificate yourself.
:::

If your keyset doesn't show the APNs2 tab, contact [PubNub Support](https://support.pubnub.com/) before continuing.

## Add APNs2 device registrations

Update the device-registration calls in your client or server code. APNs2 registration takes two extra parameters that APNs1 didn't need: `environment` (`development` or `production`) and `topic` (your app's bundle identifier).

### Before: APNs1

#### JavaScript

```javascript
pubnub.push.addChannels(
  {
    channels: ['ch1', 'ch2'],
    device: 'deviceToken',
    pushGateway: 'apns',
  },
  function (status) {
    if (status.error) {
      console.log('operation failed w/ error:', status);
    }
  }
);
```

#### Swift

```swift
pubnub.addPushChannelRegistrations(
  ["ch1", "ch2"],
  for: deviceToken
) { result in
  switch result {
  case let .success(channels):
    print("The list of channels added for push: \(channels)")
  case let .failure(error):
    print("Failed Push Modification Response: \(error.localizedDescription)")
  }
}
```

### After: APNs2

#### JavaScript

```javascript
// Function to add a device to a channel for APNs2
try {
  const response = await pubnub.push.addChannels({
    channels: ['a', 'b'],
    device: 'niceDevice',
    pushGateway: 'apns2',
    environment: 'production',
    topic: 'com.example.bundle_id',
  });
  console.log('device added to channels response:', response);
} catch (error) {
  console.error(`Error adding device to channels: ${error}`);
}

// Function to add a device to a channel for FCM
try {
  const response = await pubnub.push.addChannels({
    channels: ['a', 'b'],
    device: 'niceDevice',
    pushGateway: 'fcm',
  });
  console.log('device added to channels response:', response);
} catch (error) {
  console.error(`Error adding device to channels: ${error}`);
}
```

The same snippet also shows the equivalent Firebase Cloud Messaging (FCM) call. Ignore that block if you're migrating Apple push only.

#### Swift

```swift
// Enable APNS push notifications for a device on a provided channel
pubnub.addAPNSDevicesOnChannels(
  ["channelSwift"],
  device: Data([0x01, 0x02, 0x03, 0x04]), // Replace with actual device token
  on: "com.app.bundle",
  environment: .production
) { result in
  switch result {
  case let .success(channels):
    print("The list of channels added for push: \(channels)")
  case let .failure(error):
    print("Failed Push List Response: \(error.localizedDescription)")
  }
}
```

Use `development` while you test with an Xcode-installed build, and `production` for TestFlight and App Store builds. Other SDKs, including Objective-C, migrate the same way: keep the channels and device token, drop the old push type, and add the topic and environment your APNs2 method expects. Check your platform's reference in [Available SDKs](https://www.pubnub.com/docs/getting-started/available-sdks.md) for the exact method signature.

## Verify the new registrations

Confirm the device is registered on the channels you expect before you touch the message payload or remove anything.

### JavaScript

```javascript
// for APNs2
try {
  const response = await pubnub.push.listChannels({
    device: 'niceDevice',
    pushGateway: 'apns2',
    environment: 'production',
    topic: 'com.example.bundle_id',
  });
  console.log('listing channels for device response:', response);
  response.channels.forEach((channel) => {
    console.log(channel);
  });
} catch (error) {
  console.error(`Error listing channels for device: ${error}`);
}

// for FCM
try {
  const response = await pubnub.push.listChannels({
    device: 'niceDevice',
    pushGateway: 'fcm',
  });

  console.log('listing channels for device response:', response);

  response.channels.forEach((channel) => {
    console.log(channel);
  });
} catch (error) {
  console.error(`Error listing channels for device: ${error}`);
}
```

### Swift

```swift
// Retrieve all channels on which APNS push notification has been enabled using specified device token and topic
pubnub.listAPNSPushChannelRegistrations(
  for: Data([0x01, 0x02, 0x03, 0x04]), // Replace with actual device token
  on: "com.app.bundle",
  environment: .production
) { result in
  switch result {
  case let .success(channels):
    print("The list of channels enabled for push: \(channels)")
  case let .failure(error):
    print("Failed Push List Response: \(error.localizedDescription)")
  }
}
```

An empty result means the registration in Step 2 didn't take effect. Recheck the topic and environment before moving on. You can also run this check without writing code by following [Check push device registration](https://www.pubnub.com/docs/integrations/mobile-push-notifications/check-push-device-registration.md), which calls the same lookup over REST.

## Add the APNs2 fields to your push payload

APNs2 needs two fields inside `pn_apns.pn_push` that APNs1 didn't require: `version`, set to `v2`, and a `targets` entry naming the `topic` and `environment` to send to. PubNub can deliver both APNs1 and APNs2 notifications from the same publish, so you don't need to change the `publish()` call itself, only the payload you pass to it. For every other `pn_push` field, refer to [Push notification format configuration](https://www.pubnub.com/docs/sdks/javascript/api-reference/mobile-push.md#push-notification-format-configuration).

### Before: APNs1

```json
{
  "text": "hello world",
  "pn_apns": {
    "aps": {
      "alert": "hello world"
    }
  }
}
```

### After: APNs2

#### JavaScript

```javascript
const payloadBuilder = PubNub.notificationPayload('Chat invitation', "You have been invited to 'quiz' chat");
payloadBuilder.apns.configurations = [{ targets: [{ topic: 'com.meetings.chat.app' }] }];
payloadBuilder.sound = 'default';

console.log(JSON.stringify(payloadBuilder.buildPayload(['apns2', 'fcm']), null, 2));
```

`buildPayload()` returns the `pn_apns` object shown below. Pass it as the `message` for your channel's `publish()` call.

```json
{
  "pn_apns": {
    "aps": {
      "alert": {
        "title": "Chat invitation",
        "body": "You have been invited to 'quiz' chat"
      }
    },
    "pn_push": [
      {
        "targets": [
          { "topic": "com.meetings.chat.app", "environment": "development" }
        ],
        "version": "v2"
      }
    ]
  }
}
```

#### Swift

```swift
// Publish a message to a channel with APNS and FCM payloads
let pushMessage = PubNubPushMessage(
  apns: PubNubAPNSPayload(
    aps: APSPayload(alert: .object(.init(title: "Apple Message")), badge: 1, sound: .string("default")),
    pubnub: [.init(
      targets: [.init(topic: "com.pubnub.swift", environment: .production)],
      collapseID: "SwiftSDK",
      pushType: .alert
    )],
    payload: "Push Message from PubNub Swift SDK"
  ),
  fcm: PubNubFCMPayload(
    payload: "Push Message from PubNub Swift SDK",
    target: .topic("com.pubnub.swift"),
    notification: FCMNotificationPayload(title: "Android Message"),
    android: FCMAndroidPayload(collapseKey: "SwiftSDK", notification: FCMAndroidNotification(sound: "default"))
  ),
  additional: "Push Message from PubNub Swift SDK"
)

pubnub.publish(
  channel: "my-channel",
  message: pushMessage
) { result in
  switch result {
  case let .success(timetoken):
    print("Message Successfully Published at: \(timetoken)")
  case let .failure(error):
    print("Failed Response: \(error.localizedDescription)")
  }
}
```

| Item | Limit |
| --- | --- |
| Push credentials per keyset | 1 APNs certificate and 1 FCM key |
| Push notification payload size | 2 KB for APNs, 4 KB for FCM |

:::tip Can't update the client
If you can't ship a client update that changes the payload, add the APNs2 fields on the server instead with a [PubNub Function](https://www.pubnub.com/docs/message-processing/serverless/overview.md) that runs before the publish completes.
:::

## Remove the APNs1 registrations

Once every device you care about is verified on APNs2, remove its APNs1 registration. A device left registered under both gateways receives every push notification twice.

### JavaScript

```javascript
pubnub.push.removeChannels(
  {
    channels: ['ch1', 'ch2'],
    device: 'deviceToken',
    pushGateway: 'apns',
  },
  function (status) {
    if (status.error) {
      console.log('operation failed w/ error:', status);
    }
  }
);
```

### Swift

```swift
// Remove push notification functionality on provided set of channels
pubnub.removePushChannelRegistrations(
  ["channelSwift"],
  for: Data([0x01, 0x02, 0x03, 0x04]) // Replace with actual device token
) { result in
  switch result {
  case let .success(channels):
    print("The list of channels disabled for push: \(channels)")
  case let .failure(error):
    print("Failed Push Modification Response: \(error.localizedDescription)")
  }
}
```

Run [Check push device registration](https://www.pubnub.com/docs/integrations/mobile-push-notifications/check-push-device-registration.md) afterward to confirm the channel list under the old push type is empty. Once it is, you can also remove the APNs1 certificate from the keyset in the Admin Portal, since nothing on that keyset uses it anymore.

## Related tasks

* [Mobile push notifications](https://www.pubnub.com/docs/integrations/mobile-push-notifications/overview.md). How PubNub bridges publishing with APNs and FCM, and why the same event can arrive twice.
* [Send push notifications on iOS](https://www.pubnub.com/docs/integrations/mobile-push-notifications/send-push-notifications-ios.md). Set up a new iOS integration with APNs2 from scratch instead of migrating one.
* [Check push device registration](https://www.pubnub.com/docs/integrations/mobile-push-notifications/check-push-device-registration.md). Confirm a device token's current channel registrations with a single REST call.
* [Debug push notification messages](https://www.pubnub.com/docs/integrations/mobile-push-notifications/debug-push-notification-messages.md). Read the `-pndebug` companion channel to see why a push failed after the migration.
* [Available SDKs](https://www.pubnub.com/docs/getting-started/available-sdks.md). Find the mobile push API reference and minimum SDK version for your platform.

Last updated at: 2026-09-30T07:20:08.000Z
