APNs (iOS push)
Appwin sends iOS push notifications with your Apple key, not its own. They therefore go out under your publisher identity, and you keep control if you change provider.
An APNs .p8 key works for every app of the same Apple team, and for both
sandbox and production. One file is enough for several projects.
The Android side is on FCM.
1. Create the key
On developer.apple.com: Certificates, Identifiers & Profiles → Keys → +
- Name the key ("appwin push")
- Tick Apple Push Notifications service (APNs)
- Continue, then Register
- Download: the file
AuthKey_XXXXXXXXXX.p8downloads
The .p8 downloads only once. Apple does not regenerate it. Put it in
your password manager before closing the tab - lose the file and you have to
revoke the key and create another one.
Do not confuse this key with the App Store Connect API key: same .p8
format, same portal, two unrelated uses. This one must have APNs ticked.
2. Collect the identifiers
| Field | Where to find it |
|---|---|
| Key ID | On the key's page, or in the file name AuthKey_XXXXXXXXXX.p8 |
| Team ID | Apple Developer → Membership details, or top right under the team name |
| Bundle ID | Xcode → Target → General → Bundle Identifier |
| Private key | The contents of the .p8, headers included |
The contents of the .p8 look like:
-----BEGIN PRIVATE KEY-----
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg...
-----END PRIVATE KEY-----
Paste it whole, BEGIN and END lines included. The simplest route is
Import a file: Appwin reads the .p8 and derives the Key ID from its name,
which saves retyping it.
The Bundle ID must match the App ID declared in Identifiers. A bundle that does not match gives sends that Apple accepts but never delivers.
3. Enter them in Appwin
Settings → Projects → your app → Integrations → Apple Push (APNs).
| Field | Value |
|---|---|
| Key ID | 2X9R4HXF34 |
| Team ID | A1B2C3D4E5 |
| Bundle ID | com.yourstudio.yourapp |
| Private Key (.p8) | The file contents |
Sandbox or production
The API picks Apple's server from a production flag stored with the
integration: api.push.apple.com if true, api.sandbox.push.apple.com
otherwise. The default is sandbox.
That flag is not exposed in the form yet. In practice, sends currently target the sandbox: a token obtained from an Xcode build arrives, a TestFlight or App Store token does not. That is the classic iOS push trap, and the number one reason for "the send went out, nothing arrived".
4. Register the device token
A key on the server side is not enough: every device has to declare its token after the user grants permission.
import AppwinNotifications
func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
let token = deviceToken.map { String(format: "%02x", $0) }.joined()
Task {
try? await AppwinCore.registerPushToken(token, platform: "ios")
}
}
The token arrives as Data: Appwin expects its hexadecimal form, hence the
conversion above.
Checking
With no key configured, the API simulates the send and logs it instead of failing. That is deliberate - the rest of the product can be developed without keys at hand - but it also means a campaign can look like it went out with nothing sent.
Check the API logs: an APNs not configured - simulating delivery means the key
is missing.
Troubleshooting
| Symptom | Likely cause |
|---|---|
| Send "succeeded", nothing received | A production token, sent to the sandbox |
BadDeviceToken | A token from one environment, the other environment's server |
InvalidProviderToken | Wrong Key ID or Team ID, or a truncated .p8 |
TopicDisallowed | The Bundle ID entered differs from the app's |
| Nothing goes out at all | No key: the API simulates and says so in its logs |
| Received in debug, not in TestFlight | Same cause as the first row |
Next
- FCM - the Android side
- Notifications - campaigns, audiences, automations