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
- Generate an APNs authentication key.
- Share your push notification credentials with your Connect administrator.
- Configure push notifications in the Apple Developer portal.
- Configure push notifications in ConnectConfig.json.
- Configure push notifications in Xcode.
- 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.
- In your Apple Developer account, go to Certificates, Identifiers & Profiles > Keys > +.
- Enter a name (for example, "Acoustic Connect APNs") and enable Apple Push Notifications service (APNs).
- 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.
- Click Save > Continue > Register.
- Download the
.p8file — Apple only allows you to download it once. - Note the Key ID shown on the confirmation page.
- 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.
| Credential | Where to find it |
|---|---|
| Encryption key | The .p8 file you just downloaded |
| Key ID | The confirmation page, or the file name |
| Team ID | Top-right of the Apple Developer portal, or under Membership |
| Bundle ID | Xcode target General > Identity > Bundle Identifier |
| Environment | Sandbox — this guide covers test setup |
NoteYour 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:
| Component | Purpose |
|---|---|
| Main app | Initializes 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:
- In your Apple Developer account, go to Identifiers > +.
- Select App IDs > App and click Continue.
- Enter a description and your bundle ID (for example,
com.yourcompany.yourapp). - Under Capabilities, enable Push Notifications and App Groups.
- Click Continue > Register.
If your App ID already exists:
- Click the App ID in the list to open it.
- Under Capabilities, enable Push Notifications and App Groups.
- 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.NotificationServiceExtensioncom.yourcompany.yourapp.NotificationContentExtension
Create an App Group
- Go to Identifiers > + and select App Groups.
- Enter a description and an identifier using the
group.prefix convention (for example,group.com.yourcompany.yourapp). - Click Continue > Register.
- 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"
}
}| Key | Value |
|---|---|
PushEnabled | true — 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. |
iOSAppGroupIdentifier | The App Group identifier you created in Step 3. Must match exactly across your Xcode targets and this file. |
iOSDevelopmentTeam | Your 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
NoteFor manual mode — where your app manages its own
AppDelegatepush 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-pushWhat 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
- Open
ios/YourApp.xcworkspacein Xcode and select the main app target. - Go to Signing & Capabilities.
- Click + Capability. Push Notifications and App Groups should both appear in the list.
- Add App Groups. The identifier you created in Step 3 should be selectable.
- 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.
rubyand thexcodeprojgem 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-pushWhat this creates and changes under ios/
New files (only created if missing — re-running never clobbers something you've since customized):
| File | Contents |
|---|---|
ios/<AppTarget>/<AppTarget>.entitlements | aps-environment (development) + your App Group |
ios/ConnectNSE/NotificationService.swift | Swift implementation, App Group substituted |
ios/ConnectNSE/Info.plist | NSE extension point identifier + principal class |
ios/ConnectNSE/ConnectNSE.entitlements | App Group |
ios/ConnectNCE/NotificationViewController.swift | Swift implementation, App Group substituted |
ios/ConnectNCE/Info.plist | NCE extension point identifier + principal class |
ios/ConnectNCE/ConnectNCE.entitlements | App Group |
Existing file it modifies:
| File | What changes |
|---|---|
ios/<YourApp>.xcodeproj/project.pbxproj | Adds 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.
- In a text editor, open
ios/Podfile. - Add the following
requireline after the existingrequireblock near the top of the file. This loads theacoustic_connect_podhelper 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- Add the following after the closing
endof your main app target, at the end of the file.acoustic_connect_podresolves 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- Run:
cd ios && pod install --repo-update
NoteIf 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' }.
WarningiOS 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 determinedTesting
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.

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."
}
}
}
NoteFor title, body, and thumbnail image requirements, see Message structure below.
On an iPhone — real APNs delivery
- Build and run on a physical iPhone. Xcode uses a Development profile, which routes through Sandbox APNs.
- Grant notification permission when prompted. A new mobile contact is created in Connect and your iPhone becomes eligible to receive pushes.
- Coordinate with your marketing team to send a test push to your iPhone, or do it yourself — see Test your push notification setup.
- 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-notificationis present inUIBackgroundModesin 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": 1is present in the push payload. - Confirm the NSE target is present in your Podfile with the
acoustic_connect_podhelper and that you ranpod installafter adding it. - Confirm the
appGroupIdentifierin the NSE exactly matchesiOSAppGroupIdentifierin 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
AppKeyandPostMessageUrlin ConnectConfig.json match your Mobile app integration in Connect. - Confirm
PushEnabledistrueand you re-ranpod installafter changing ConnectConfig.json. - Do not set your own
UNUserNotificationCenter.delegatein automatic mode — doing so prevents push signals from being tracked.
Extension crashes on launch
- Confirm
libPods-<ExtensionName>.aappears 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-runpod install --repo-updateand rebuild. - Confirm
NSExtensionPrincipalClassin 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 outputIf 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
ConnectNSE or ConnectNCEThe 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
endAfter 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).
