Enable push notifications in a Cordova app (iOS)
This guide walks you through enabling push notifications in your Cordova app on iOS using the Acoustic Connect SDK.
Language: JavaScript, with TypeScript definitions. The Notification Service Extension and Notification Content Extension are implemented in Swift and ship with the plugin — you don't write or maintain this Swift code yourself.
Availability: Pro, Premium, and Ultimate
Scope: Test setup — building, running on the simulator or a connected iPhone, and verifying push delivery. Covers automatic push mode.
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.
- The Connect Cordova SDK, integrated in your project with a Connect app key from a Mobile app integration created after April 2026 — earlier integrations don't support push. See Integrate the Connect SDK into a Cordova app to install the plugin first.
Overview
- Get an APNs authentication key.
- Share your push notification credentials with your Connect administrator.
- Set up the Apple Developer portal.
- Update ConnectConfig.json in your project.
- Add entitlements and build for iOS.
- 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 and enable Apple Push Notifications service (APNs).
- Click Configure, set Environment to Sandbox & Production unless your security team requires tighter scoping — this can't be changed later.
- Save > Continue > Register.
- Download the
.p8file — Apple only allows this once. - Note the Key ID and your Team ID (10-character string, top-right of the portal).
Step 2: Share push notification credentials with your Connect administrator
| Credential | Where to find it |
|---|---|
| Encryption key | The .p8 file from Step 1 |
| Key ID | The confirmation page, or the file name |
| Team ID | Top-right of the Apple Developer portal, or under Membership |
| Bundle ID | Your Cordova project's config.xml — the id attribute on the <widget> element |
| Environment | Sandbox — this guide covers test setup |
NoteYour Connect administrator needs to upload these credentials 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: Set up the Apple Developer portal
Push notifications in Connect require three components sharing an App Group — a container that lets them exchange push data. The identifier must be identical across all three or delivery tracking and rich media silently fail.
| Component | Purpose |
|---|---|
| Main app | Initializes the SDK and requests notification permission |
| Notification Service Extension (NSE) | Downloads rich media and records delivery, including dismissed notifications |
| Notification Content Extension (NCE) | Renders the expanded notification view on long-press |
Do the following in the portal:
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.
- 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
This step is automated — you don't do it in the portal. The plugin creates the ConnectNSE and ConnectNCE extension targets in Xcode for you on every build (see Step 5).
Create an App Group
- Go to Identifiers > + and select App Groups.
- Enter a description and an identifier using the
group.prefix (for example,group.com.yourcompany.yourapp). - Click Continue > Register.
- Go back to your main App ID and assign the App Group you just created.
Xcode's automatic signing assigns this same App Group to the extension App IDs when you build in Step 5.
Step 4: Update ConnectConfig.json
Back in your project root (outside Xcode), open ConnectConfig.json and add the following keys inside the Connect object.
{
"Connect": {
"AppKey": "...",
"PostMessageUrl": "...",
"iOSPushMode": "automatic",
"iOSAppGroupIdentifier": "group.com.yourcompany.yourapp",
"iOSDevelopmentTeam": "YOUR_TEAM_ID"
}
}| Key | Value |
|---|---|
iOSPushMode | 'automatic' (default) — the SDK handles APNs registration, delivery, and tap actions. |
iOSAppGroupIdentifier | The App Group identifier from Step 3. Substituted automatically into the NSE/NCE Swift source and into the host app's and both extensions' entitlements files — you don't edit any .entitlements file by hand. |
iOSDevelopmentTeam | Required. Your 10-character Apple Team ID. Stamped onto the main app target and both extension targets automatically on every prepare. |
Step 5: Add entitlements and build
Before your first build, add an entitlements file and a hook so the Push Notifications capability is applied automatically on every prepare — no manual Xcode step needed.
- Create the
entitlements/folder at your Cordova project root and add a file namedApp.entitlements:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
</dict>
</plist>- Create
hooks/after_prepare_entitlements.js:
#!/usr/bin/env node
'use strict';
const fs = require('fs');
const path = require('path');
module.exports = function (context) {
const platforms = context.opts.platforms || [];
if (!platforms.includes('ios')) return;
const projectRoot = context.opts.projectRoot;
const platformRoot = path.join(projectRoot, 'platforms', 'ios');
if (!fs.existsSync(platformRoot)) return;
const xcodeproj = fs.readdirSync(platformRoot).find(f => f.endsWith('.xcodeproj'));
if (!xcodeproj) return;
const appName = xcodeproj.replace('.xcodeproj', '');
const appDir = path.join(platformRoot, appName);
const src = path.join(projectRoot, 'entitlements', 'App.entitlements');
if (!fs.existsSync(src)) return;
fs.mkdirSync(appDir, { recursive: true });
fs.copyFileSync(src, path.join(appDir, 'App.entitlements'));
const setting = `CODE_SIGN_ENTITLEMENTS = $(PROJECT_DIR)/${appName}/App.entitlements`;
['build-debug.xcconfig', 'build-release.xcconfig'].forEach(cfg => {
const cfgPath = path.join(platformRoot, 'cordova', cfg);
if (!fs.existsSync(cfgPath)) return;
const content = fs.readFileSync(cfgPath, 'utf8');
if (!content.includes('CODE_SIGN_ENTITLEMENTS')) {
fs.appendFileSync(cfgPath, `\n${setting}\n`);
}
});
};- Register the hook in
config.xmlunder the iOS platform block:
<platform name="ios">
<hook type="after_prepare" src="hooks/after_prepare_entitlements.js" />
</platform>- Build. Run from your Cordova project root — the directory containing
config.xmlandConnectConfig.json.
npx cordova build iosEvery build wires up Xcode for you automatically — you do not need to manually create extension targets, edit a Podfile, or open Xcode to add capabilities.
What happens automatically on every build
- Your entitlements hook copies
App.entitlementsinto the Xcode project and setsCODE_SIGN_ENTITLEMENTSin the build configuration, applying the Push Notifications capability without a manual Xcode step. - The plugin copies the NSE (
NotificationService.swift) and NCE (NotificationViewController.swift) source intoplatforms/ios/, with your App Group identifier substituted in. - Creates the
ConnectNSEandConnectNCEapp-extension targets in your.xcodeproj(idempotently — safe to run every prepare), links the required system frameworks (UserNotifications, andUserNotificationsUI+UIKitfor the NCE), and adds an "Embed Foundation Extensions" build phase on the main app target. - Adds
ConnectNSEandConnectNCEas CocoaPods targets in yourPodfileand runspod install— only when the Podfile actually changed, to avoid an unnecessary reinstall on every build. - Adds the App Group and
aps-environmententitlement to the main app's entitlements file (and both extensions'). - Adds
UIBackgroundModes→remote-notificationtoInfo.plist. - Sets
DEVELOPMENT_TEAMandCODE_SIGN_STYLE = Automaticon all three targets (main app, ConnectNSE, ConnectNCE) using theiOSDevelopmentTeamvalue from ConnectConfig.json, so Xcode manages provisioning profiles automatically.
After the build completes, open platforms/ios/App.xcworkspace to run the app. Xcode's automatic signing registers the extension App IDs and assigns the App Group in the Apple Developer portal — no manual portal configuration is needed for the extensions.
Step 6: Request notification permission
const result = await AcousticConnect.push.requestPermission();
if (result.granted) {
console.log('Notification permission granted');
} else {
console.log('Notification permission denied:', result.error);
}push.requestPermission() presents the system permission dialog when the state is undetermined. Call it at an appropriate moment in your UX rather than on first launch.
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.
Check the current state without prompting:
const granted = await AcousticConnect.push.getPermissionState();
// true = granted, false = denied, null = not yet determinedMessage 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.
-
Notification icon — shown alongside every notification. iOS uses your app icon (
Assets.xcassets/AppIcon.appiconset) for all messages. iOS does not support custom notification icons. -
Title and body — authored per message. Title: up to 50 characters. Body: up to 150 characters.
-
Action — what happens when the user taps the notification: open the app, call a number, or open a URL.
-
Thumbnail image (optional) — shown to the right of the notification body. Accepted formats: .jpg, .jpeg, .png. Minimum dimensions: 64 × 64 px. Maximum size: 5 MB.
-
Expandable content — optionally add expanded text or an expanded image, shown when the user long-presses the notification. Accepted image formats: .jpg, .jpeg, .gif, .png. Minimum dimensions: 1024 × 1024 px. Maximum size: 5 MB. The image is downloaded by the Notification Service Extension and requires
mutable-content: 1in the APNs payload.
Testing
On the simulator — quick test
You can test push delivery in the simulator using .apns payload files. Create them in any text editor and drag them onto the running simulator to send a test notification. We keep ours in a TestPayloads/ folder inside the project. 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.
{
"Simulator Target Bundle": "com.yourcompany.yourapp",
"aps": {
"alert": { "title": "New arrivals", "body": "Fresh styles just landed." },
"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"
},
"expandedImage": "https://example.com/image.jpg"
}On an iPhone — server-sent delivery
- Build and run on a physical iPhone.
- Grant notification permission when prompted. A new mobile contact is created in Connect.
- Coordinate with your marketing team to send a test push, 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.
Verification
The steps above are enough to confirm push is working. This section is optional — use it to inspect what the SDK is doing under the hood, or to collect diagnostic information when something isn't working as expected.
Enable debug logging
Confirm "useRelease": false in ConnectConfig.json (the default for development). Then 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.
Verify push registration in the debug log
After you accept the notification permission request, check the Xcode console for these log entries, in order:
| Entry | What it means |
|---|---|
Flushing <Connect.ConnectPushRegistrationMessage: ...> | Push registration message queued for delivery |
Connected to wifi, using WiFi log level | Network check passed |
url For flush: https://...collectorPost | Registration data posted to the collector |
Then filter by pushRegistration to find the JSON payload. Look for these fields:
| Field | Expected value | What it means |
|---|---|---|
acceptPush | true | Notification permission granted |
mobileProvider | APN | Registered with APNs |
mobileToken | present (long hex string) | Device token obtained — the SDK can receive push. On the simulator, a token is only generated on iOS 16 or later. |
newRegistration | true on first run, false after | Whether this is a new registration or a refresh |
If acceptPush is missing, notification permission has not been granted — check that push.requestPermission() is being called. If mobileToken is absent, confirm the Push Notifications capability is added and the device has network access.
Log entries that are safe to ignore
| Entry | Reason |
|---|---|
APNs device token registration failed: ... remote notifications are not supported in the simulator | Expected on iOS 15 simulators — APNs tokens are only generated on iOS 16 or later |
THREAD WARNING: ['ConnectPlugin'] | Performance note, not a failure |
EOCoreSettings bundle not found | Expected with programmatic config — no settings bundle needed |
Value for key URLEncoded could not be found | Config lookup miss — default is used |
Found 0 saved queues | Normal on first launch |
Troubleshooting
Push notifications not being delivered
- Check your debug log for an APNs token. If none appears, confirm the Push Notifications capability is added to the main app target and that the device has network access. On the simulator, APNs tokens are only generated on iOS 16 or later.
- Confirm the user granted notification permission.
- Confirm
remote-notificationis inUIBackgroundModesinInfo.plist(added automatically — check if you've overriddenInfo.plistmanually). - Confirm the bundle ID matches the App ID with Push Notifications enabled in the portal.
Rich push images not showing
- Confirm the image URL is accessible over HTTPS.
- Confirm
iOSAppGroupIdentifierin ConnectConfig.json matches the App Group on your main App ID in the portal. Xcode's automatic signing applies it to the extension App IDs. - Confirm
pod installran after any App Group change — check theafter_prepareconsole output forPodfile changed — running pod install.
Extension targets missing or stale after a fresh clone / plugin re-add
Run npx cordova prepare ios (or npx cordova build ios) again — the extension targets, embed phase, and entitlements are rebuilt idempotently by the after_prepare hook each time. If signing looks stale specifically, re-open Xcode, confirm Signing & Capabilities shows a Team on all three targets, and re-download provisioning profiles if App Groups isn't selectable.
[after_prepare] pod install failed
[after_prepare] pod install failedCocoaPods isn't installed, or your local spec repo is stale. Run sudo gem install cocoapods && pod repo update, then npx cordova build ios again.
Unable to find host target(s) for ConnectNSE, ConnectNCE during pod install
Unable to find host target(s) for ConnectNSE, ConnectNCE during pod installThis can happen on a fresh platform add, when the Podfile still references extension targets from a previous prepare but the .xcodeproj was reset. The plugin's before_prepare hook detects and strips stale references automatically, and after_prepare re-adds them once the project is consistent again — if you still see this error, re-run npx cordova prepare ios once more before investigating further.
Next step: Identify app users
Push setup is now complete. If you haven't already, identify the people receiving your push notifications — see Identify users at sign-in.
Updated 3 days ago
