Enable push notifications in a React Native app (iOS)

This guide walks you through enabling push notifications in your React Native app on iOS using the Acoustic Connect SDK.

Language: TypeScript and JavaScript. The Notification Service Extension and Notification Content Extension are implemented in Swift.

Availability: Pro, Premium, and Ultimate

Scope: Test setup for bare React Native — building, running on the simulator or a connected iPhone, and verifying push delivery. For Expo, see Enable push notifications in an Expo app (iOS). For production setup, see Move your React Native integration to production.

Sample app: See our GitHub repository for a reference implementation to compare your configuration against.


Prerequisites

  • Apple Developer Program membership — Push notifications require a paid account. Required role: Account Holder or Admin.
  • Connect app key that supports push notifications. Push requires an app key from a Mobile app integration created after April 2026. If you have an existing integration, ask your Connect administrator to create a new Mobile app integration and provide you with the new app key. For instructions, see Connect mobile apps in the Connect user guide.
  • Connect React Native SDK version 19.0.9 integrated in your project. If you have not yet integrated the SDK, follow Integrate the Connect SDK into a React Native app first.

Overview

  1. Generate an APNs authentication key.
  2. Share your push notification credentials with your Connect administrator.
  3. Configure push notifications in the Apple Developer portal.
  4. Configure push notifications in ConnectConfig.json.
  5. Configure push notifications in Xcode.
  6. Add code to request notification permission from your users.

Step 1: Get an APNs authentication key

Connect uses token-based APNs authentication. A single .p8 key works across all apps in your team.

  1. In your Apple Developer account, go to Certificates, Identifiers & Profiles > Keys > +.
  2. Enter a name (for example, "Acoustic Connect APNs") and enable Apple Push Notifications service (APNs).
  3. Click Configure next to Apple Push Notifications service (APNs) and set Environment to Sandbox & Production unless your security team requires tighter scoping. This setting cannot be changed once saved.
  4. Click Save > Continue > Register.
  5. Download the .p8 file — Apple only allows you to download it once.
  6. Note the Key ID shown on the confirmation page.
  7. Note your Team ID — a unique 10-character string shown in the top-right of the portal next to your team name.

Step 2: Share push notification credentials with your Connect administrator

Share the following with your Connect administrator so they can enable push notifications in your Mobile app integration.

CredentialWhere to find it
Encryption keyThe .p8 file you just downloaded
Key IDThe confirmation page, or the file name
Team IDTop-right of the Apple Developer portal, or under Membership
Bundle IDXcode target General > Identity > Bundle Identifier
EnvironmentSandbox — this guide covers test setup
📘

Note

Your Connect administrator needs to upload these credentials to your Mobile app integration in Connect before push notifications will be delivered. See Connect mobile apps in the Connect user guide. Steps 3–5 can be completed while you wait.


Step 3: Configure push notifications in the Apple Developer portal

Push notifications in Connect require three components working together:

ComponentPurpose
Main appInitializes the SDK with push configuration and requests notification permission from the user
Notification Service Extension (NSE)Downloads rich media (images) and records delivery, including dismissed notifications
Notification Content Extension (NCE)Renders the expanded notification view when the user long-presses a notification

All three targets share an App Group — a shared container that lets them exchange push data. The App Group identifier must be identical across all three targets. A mismatch causes delivery tracking and rich media to silently fail.

Register or update your App ID

If you are registering a new App ID:

  1. In your Apple Developer account, go to Identifiers > +.
  2. Select App IDs > App and click Continue.
  3. Enter a description and your bundle ID (for example, com.yourcompany.yourapp).
  4. Under Capabilities, enable Push Notifications and App Groups.
  5. Click Continue > Register.

If your App ID already exists:

  1. Click the App ID in the list to open it.
  2. Under Capabilities, enable Push Notifications and App Groups.
  3. Click Save.

Register App IDs for the extensions

Repeat the App ID registration for each extension target, enabling App Groups only. Extension App IDs do not require the Push Notifications capability.

  • com.yourcompany.yourapp.NotificationServiceExtension
  • com.yourcompany.yourapp.NotificationContentExtension

Create an App Group

  1. Go to Identifiers > + and select App Groups.
  2. Enter a description and an identifier using the group. prefix convention (for example, group.com.yourcompany.yourapp).
  3. Click Continue > Register.
  4. Go back to each of your three App IDs (main app, NSE, NCE) and assign the App Group you just created.

Step 4: Configure push notifications in ConnectConfig.json

Back in your project root (outside Xcode), open ConnectConfig.json and add the following keys inside the Connect object.

{
  "Connect": {
    "PushEnabled": true,
    "iOSPushMode": "automatic",
    "iOSAppGroupIdentifier": "group.com.yourcompany.yourapp",
    "iOSDevelopmentTeam": "YOUR_TEAM_ID"
  }
}
KeyValue
PushEnabledtrue — enables push support in the SDK.
iOSPushMode"automatic" — the SDK installs its own notification delegate and handles APNs registration, foreground delivery, and tap actions without any AppDelegate changes.
iOSAppGroupIdentifierThe App Group identifier you created in Step 3. Must match exactly across your Xcode targets and this file.
iOSDevelopmentTeamYour 10-character Apple Team ID (found in the Apple Developer portal under Membership). Required — without it the build omits the aps-environment entitlement and APNs does not issue a token.

After saving, re-run pod install to bake the new values into the native iOS configuration:

cd ios && pod install --repo-update
📘

Note

For manual mode — where your app manages its own AppDelegate push callbacks and forwards events to the SDK — contact Acoustic support. Manual mode is not covered in this guide.

Then validate the values you just set:

npx acoustic-connect doctor --require-push
What the Doctor does

--require-push treats the push-required values — AppKey, the collector URLs, iOSAppGroupIdentifier, and iOSDevelopmentTeam — as hard failures rather than warnings, so a typo or leftover placeholder is caught here instead of failing later in Xcode or on-device. It's safe to re-run any time you change ConnectConfig.json.


Step 5: Configure push notifications in Xcode

Confirm Xcode has picked up the portal changes

  1. Open ios/YourApp.xcworkspace in Xcode and select the main app target.
  2. Go to Signing & Capabilities.
  3. Click + Capability. Push Notifications and App Groups should both appear in the list.
  4. Add App Groups. The identifier you created in Step 3 should be selectable.
  5. Add Push Notifications.

If either capability is missing or the App Group is not selectable, Xcode has not yet picked up your portal changes. Go to Xcode > Settings > Apple Accounts, select your account, select your team, and click Download Manual Profiles, then try again.

Configure Info.plist

In ios/YourApp/Info.plist, add the following to enable background push delivery:

<key>UIBackgroundModes</key>
<array>
    <string>remote-notification</string>
</array>

Add the push notification extensions

Push notifications need two app-extension targets — a Notification Service Extension (NSE) that downloads rich media and records delivery, and a Notification Content Extension (NCE) that renders the expanded notification view when the user long-presses a notification.

Scaffold both with the SDK's CLI. Before running it, make sure:

  • You're on macOS — the command edits your Xcode project directly. On Windows or Linux it does nothing at all: no files are written, it just prints a warning and exits.
  • ruby and the xcodeproj gem are installed (gem install xcodeproj — ships with CocoaPods, so most Mac setups already have it). If either is missing, the command still writes the extension files but skips creating the Xcode targets; install the gem and run the command again.

Run it from your project root:

npx acoustic-connect setup-ios-push
What this creates and changes under ios/

New files (only created if missing — re-running never clobbers something you've since customized):

FileContents
ios/<AppTarget>/<AppTarget>.entitlementsaps-environment (development) + your App Group
ios/ConnectNSE/NotificationService.swiftSwift implementation, App Group substituted
ios/ConnectNSE/Info.plistNSE extension point identifier + principal class
ios/ConnectNSE/ConnectNSE.entitlementsApp Group
ios/ConnectNCE/NotificationViewController.swiftSwift implementation, App Group substituted
ios/ConnectNCE/Info.plistNCE extension point identifier + principal class
ios/ConnectNCE/ConnectNCE.entitlementsApp Group

Existing file it modifies:

FileWhat changes
ios/<YourApp>.xcodeproj/project.pbxprojAdds the ConnectNSE/ConnectNCE targets; adds an "Embed Foundation Extensions" build phase on the main app; links required system frameworks per extension; sets CODE_SIGN_ENTITLEMENTS and (if configured) DEVELOPMENT_TEAM on all three targets

It's safe to run more than once — running it again with nothing changed does nothing.

Reasons to run it again

Re-run it after a fresh clone and any time iOSDevelopmentTeam changes, or the signing team stamp on the extension targets goes stale — see Push works on one machine but not another in Troubleshooting.

Add extension targets to your Podfile

Both extension targets need the Connect SDK linked via CocoaPods — this pulls in the SDK and its dependencies automatically. For reference, compare against ios/Podfile in the sample app.

  1. In a text editor, open ios/Podfile.
  2. Add the following require line after the existing require block near the top of the file. This loads the acoustic_connect_pod helper used by the extension targets.
require Pod::Executable.execute_command('node', ['-p',
  'require.resolve(
    "react-native-acoustic-connect/cli/ios/connect_pods.rb",
    {paths: [process.argv[1]]},
  )', __dir__]).strip
  1. Add the following after the closing end of your main app target, at the end of the file. acoustic_connect_pod resolves the correct Connect pod version from your ConnectConfig.json.
target 'ConnectNSE' do
  connect_name, connect_requirements = acoustic_connect_pod(__dir__)
  pod connect_name, *connect_requirements
end

target 'ConnectNCE' do
  connect_name, connect_requirements = acoustic_connect_pod(__dir__)
  pod connect_name, *connect_requirements
end
  1. Run:
cd ios && pod install --repo-update
📘

Note

If Xcode shows "No such module 'Connect'" on either extension target before this step, that's expected — it resolves once the SDK is linked here and you run pod install.


Step 6: Request notification permission

Call pushRequestPermission() at an appropriate moment in your app's UX — for example, after an onboarding screen that explains the value of notifications, rather than on first launch.

import AcousticConnectRN from 'react-native-acoustic-connect'

const result = await AcousticConnectRN.pushRequestPermission()

if (result.granted) {
  console.log('Notification permission granted')
} else {
  console.log('Notification permission denied:', result.error)
}

pushRequestPermission() presents the system permission dialog when the state is undetermined. It always resolves and never rejects. If the user abandons the dialog (for example, by switching apps mid-prompt), it resolves with { granted: false, error: 'permission-prompt-abandoned' }.

⚠️

Warning

iOS only shows the system permission dialog once. If the user denies it, the only way to re-enable notifications is through the Settings app. Design your UX accordingly.

You can also check the current permission state without prompting:

const granted = await AcousticConnectRN.pushGetPermissionState()
// true = granted, false = denied, null = not yet determined

Testing

Enable debug logging

Before testing, add these environment variables with a value of 1 to your scheme so the Xcode console shows verbose SDK output for push registration and delivery: CONNECT_DEBUG, TLF_DEBUG, and EODebug.

Connect SDK environment variables

If you ever need to report an issue to support, attach your debug log.

On the simulator — instant verification

You can test push delivery in the simulator using .apns payload files — drag them onto the running simulator to send a test notification. This is not a full verification: the simulator confirms basic delivery and NCE invocation, but expanded images in rich notifications may not have time to load. Use a real iPhone to validate that images load correctly.

A basic payload sends a plain notification. Add "mutable-content": 1 and a data.notification object to test rich push — iOS invokes the NSE to display expanded content. Replace com.yourcompany.yourapp with your app's bundle ID.

{
    "Simulator Target Bundle": "com.yourcompany.yourapp",
    "aps": {
        "alert": {
            "title": "New arrivals",
            "body": "Fresh styles just landed. Open the app to take a look."
        },
        "sound": "default"
    }
}
{
    "Simulator Target Bundle": "com.yourcompany.yourapp",
    "aps": {
        "alert": {
            "title": "New arrivals",
            "body": "Fresh styles just landed."
        },
        "sound": "default",
        "mutable-content": 1,
        "category": "ACOUSTIC_RICH_NOTIFICATION"
    },
    "data": {
        "notification": {
            "expandedImage": "https://example.com/image.jpg",
            "expandedBody": "Tap to see everything new this week."
        }
    }
}
📘

Note

For title, body, and thumbnail image requirements, see Message structure below.

On an iPhone — real APNs delivery

  1. Build and run on a physical iPhone. Xcode uses a Development profile, which routes through Sandbox APNs.
  2. Grant notification permission when prompted. A new mobile contact is created in Connect and your iPhone becomes eligible to receive pushes.
  3. Coordinate with your marketing team to send a test push to your iPhone, or do it yourself — see Test your push notification setup.
  4. Verify the notification appears, images load, and push signals appear in the contact's activity feed in Connect.

Message structure

After you complete the setup, your marketing team creates message content in Message composer in Connect. The following elements make up a push message, and each has its own source and constraints.

  • App icon — shown alongside every notification. Unlike the other elements below, it's not set in Message composer; it's always taken from the development project. In Xcode, find it under Images.xcassets > AppIcon. If no icon is set there, notifications fall back to a placeholder instead of your branding. For how to add or replace it, see Apple's Configuring your app icon using an asset catalog.

Example with a placeholder app icon

  • Title and body — iOS limits titles to 50 characters and the combined title and body to 150 characters.
  • Thumbnail image — shown in the collapsed notification, useful for branding. Accepted formats: .jpg, .jpeg, .png. Minimum dimensions: 64 × 64 px. Maximum size: 5 MB.
  • Expandable content — additional text or a full image shown when the user expands the notification. This is rich media delivered through the Notification Service Extension and is fully customizable per message, independent of the app icon.
  • Actionable notifications — a built-in action that fires when the user taps the notification: open the app, call a number, or open a URL.

Troubleshooting

Push notifications not being delivered

To confirm whether the SDK registered an APNs device token, enable debug logging (see Enable debug logging) and look for a token registration entry in the Xcode console on startup. If no token is logged, check the following:

  • APNs tokens are not available on the simulator — test on a real iPhone.
  • Confirm the user granted notification permission.
  • Confirm remote-notification is present in UIBackgroundModes in Info.plist.
  • Confirm the bundle ID in your Xcode project matches the App ID with Push Notifications enabled in the Apple Developer portal.

Rich push images not showing

  • Confirm "mutable-content": 1 is present in the push payload.
  • Confirm the NSE target is present in your Podfile with the acoustic_connect_pod helper and that you ran pod install after adding it.
  • Confirm the appGroupIdentifier in the NSE exactly matches iOSAppGroupIdentifier in ConnectConfig.json.
  • Confirm the image URL is accessible over HTTPS and meets the format and size requirements in Message structure.

Push signals not appearing in Connect

Push delivery and user interactions with push notifications are registered as signals in Connect.

Sample push signals in Session intelligence

If you aren't getting these signals, check the following:

  • Confirm AppKey and PostMessageUrl in ConnectConfig.json match your Mobile app integration in Connect.
  • Confirm PushEnabled is true and you re-ran pod install after changing ConnectConfig.json.
  • Do not set your own UNUserNotificationCenter.delegate in automatic mode — doing so prevents push signals from being tracked.

Extension crashes on launch

  • Confirm libPods-<ExtensionName>.a appears under Frameworks and Libraries for the extension target — this is how the Podfile links the Connect library, not by adding it manually. If it's missing, re-run pod install --repo-update and rebuild.
  • Confirm NSExtensionPrincipalClass in the extension's Info.plist is $(PRODUCT_MODULE_NAME).YourClassName.
  • Confirm the App Group entitlement is present on the extension target.

Push works on one machine but not another ("works for me, not for them")

npx acoustic-connect setup-ios-push stamps DEVELOPMENT_TEAM onto the host app and both the ConnectNSE and ConnectNCE targets in project.pbxproj. That stamp lives in local build artifacts, not something a fresh clone automatically has — re-run the command after every fresh clone and any time iOSDevelopmentTeam changes in ConnectConfig.json. Every portal-side check (App ID push capability, provisioning profile, entitlements file contents, App Group registration, .p8 key upload) can look correct and still not reflect what's actually embedded in the binary on the iPhone.

To verify the stamp actually took, check the build artifacts directly rather than re-running and assuming it worked:

# DEVELOPMENT_TEAM must be stamped on the host AND both push extensions
grep DEVELOPMENT_TEAM ios/*.xcodeproj/project.pbxproj

# The installed binary must carry the entitlement — check the actual .app,
# not just the source .entitlements file in your repo
codesign -d --entitlements :- --xml /path/to/YourApp.app
# must show aps-environment in the output

If DEVELOPMENT_TEAM is missing for any of the three targets, or codesign doesn't show aps-environment, re-run setup-ios-push and do a clean rebuild.

iOS build fails with "Unexpected failure" on ConnectNSE or ConnectNCE

The CocoaPods resource-copy script for the extension targets tries to write a temporary file inside the Pods/ directory. Xcode's user script sandboxing blocks this write, causing the build phase to fail with Operation not permitted.

Add ENABLE_USER_SCRIPT_SANDBOXING = 'NO' to your Podfile's post_install block. The setting must be applied to both the Pods project targets and the main project targets:

post_install do |installer|
  # ... existing post_install code ...

  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['ENABLE_USER_SCRIPT_SANDBOXING'] = 'NO'
    end
  end

  installer.aggregate_targets.map(&:user_project).uniq.each do |project|
    project.targets.each do |target|
      target.build_configurations.each do |config|
        config.build_settings['ENABLE_USER_SCRIPT_SANDBOXING'] = 'NO'
      end
    end
    project.save
  end
end

After updating the Podfile, run pod install and clean the build folder before rebuilding.


Next step: Identify app users

Push setup is now complete. To make the most of push notifications, identify the people receiving them — see Identify users at sign-in (React Native) and Identify users at registration (React Native).