Alle Artikel
3. Oktober 2026

Push notifications on iOS: APNs setup, rich payloads and permission timing

iOS push notifications need a one hour APNs provider token, a payload under 4KB, and a permission prompt timed to a real user action, not app launch.

Direct answer

Setting up iOS push notifications correctly comes down to three parts: a provider authentication token that proves your server to Apple Push Notification service (APNs), a JSON payload built from the aps dictionary and kept under the platform's size limit, and a permission prompt that fires in response to a real user action instead of on first launch. Get those three right, and rich media, interactive actions, and reliable delivery all follow. This guide walks through each part in the order you'll actually build it: authentication and connection setup, the step by step flow from registration to a rich notification, and the mistakes that most often leave a feature working in TestFlight and silent in production.

Before you start

APNs accepts two ways to authenticate a provider server: a JWT based provider authentication token or a provider certificate. The token approach is what Apple documents as current practice. Each token is a JSON Web Token with a header specifying the signing algorithm, which must be ES256, plus a key identifier (kid), a 10 character key ID from your Apple Developer account. The claims payload carries iss, your 10 character Team ID, and iat, the Unix timestamp the token was issued.

Tokens signed with anything other than ES256 come back as a 403 InvalidProviderToken error. And the token's validity window is capped at one hour: APNs rejects a token whose iat falls outside the last 60 minutes with 403 ExpiredProviderToken. So the practical pattern is to mint one token, reuse it for every push you send during that hour, and rotate it before it expires rather than generating a fresh token per request. If a signing key is ever compromised, revoke it in the Developer account, close every open APNs connection, and reconnect using a token signed by the replacement key.

Device tokens handle the other half of addressing a notification. They're hexadecimal identifiers your app receives after the user grants permission, specific to both environment (development versus production) and topic, which APNs reads as your app's bundle ID. Send to the wrong environment, or to a token that's already been invalidated, and APNs returns 400 BadDeviceToken or 410 Unregistered, both quiet failure modes if you're not checking responses. Connections should stay open across many notifications; APNs requires TLS 1.2 or higher and treats rapid opening and closing of connections the way it treats a denial of service pattern, which is to say, it doesn't reward it. The discipline here is the same one we apply when shipping SwiftUI apps with confidence: verify the unglamorous plumbing before building the feature on top of it.

Step by step: from APNs setup to a rich notification

Register for remote notifications and request permission at the right moment. Call UNUserNotificationCenter.current().requestAuthorization(options:), and in the completion handler, call UIApplication.shared.registerForRemoteNotifications() only if granted is true. This same registration channel powers push driven Live Activities, so getting the request flow right once pays off across every notification driven feature you add later.

Build the aps payload. The alert key can be a plain string or a dictionary with title, body, and localization keys (title-loc-key, loc-key, loc-args) if you're localizing server side or via the app bundle. Add badge for the app icon count, sound for a custom audio file, category to reference a registered action set, and content-available: 1 for a silent background refresh notification. Apple recommends limiting those to a few per hour, since the system treats them as low priority and can throttle them.

Respect the size ceiling. A standard remote notification payload tops out at 4KB; VoIP payloads get 5KB. Strip whitespace and line breaks to stay under the limit, and never put sensitive data directly in the payload. Apple's own guidance: treat the notification as a signal that content exists, not a container for the content itself, the same way an email app should notify that mail arrived without putting the message body in the push.

Send with the right headers. authorization: bearer <JWT> carries your provider token, apns-topic carries the bundle ID (required when authenticating with a token), and apns-priority controls delivery behavior: 10 for immediate delivery that must trigger an alert, sound, or badge; 5 for power efficient delivery that the system can batch, throttle, or in some cases not deliver at all. apns-collapse-id, capped at 64 bytes, lets you group or replace duplicate notifications instead of stacking them.

Add rich media with a Notification Service Extension. The payload itself stays small, so images, video, and audio arrive by reference, not by value. A Notification Service Extension intercepts the notification before it displays, downloads the media the payload points to, and attaches it to the notification content. A Notification Content Extension goes further, replacing the system's default layout with fully custom UI. Both run in the roughly 30 second window iOS gives extensions before falling back to the unmodified notification.

Register categories and actions. UNNotificationCategory paired with one or more UNNotificationAction objects lets a user reply, dismiss, or act directly from the notification without opening the app. It's worth the small amount of extra registration code for any notification type people interact with often.

Common mistakes that break push notifications

The most common mistake, and the most avoidable, is asking for notification permission on first launch, before the user has any context for why your app wants to send them anything. Apple's own guidance is explicit here: the system prompt only ever fires once per install, the user's answer gets persisted, and every later call to requestAuthorization just returns that stored answer silently. There's no programmatic re-prompt. Ask too early, get denied, and the only recovery path left is sending the user to Settings by hand. Time the request to a moment the user has already signaled interest: finishing onboarding, enabling a feature that depends on alerts, tapping a "notify me" control.

The second most common failure is a device token mismatch: sending a production token to the development endpoint or vice versa, or sending to a bundle ID that doesn't match the token's topic. Both come back as 400 BadDeviceToken; a token the system has since invalidated comes back as 410 Unregistered. Neither failure is visible to the user, which is exactly why checking APNs response codes in your provider server matters more than it seems like it should.

Third, some teams mint a new provider token on every push instead of reusing one JWT for its full hour of validity. That adds signing overhead for no reason and, at volume, unnecessary load on the connection. Fourth, some servers open and close a connection per notification rather than holding one open, which APNs treats as abusive rather than efficient.

Finally, check your delegate and extension code against your concurrency model. If you're mid migration on Swift 6 strict concurrency, notification delegate callbacks and service extension entry points are exactly the kind of system provided, non actor isolated code that trips the compiler's new isolation checks, usually as a build error rather than a runtime bug. That's the better of the two outcomes.

Kallos Labs builds this authentication, payload, and permission sequence into the production readiness gate we run before every App Store submission on the iOS development work we ship for clients, because a push feature that passes QA on a developer's phone with debug entitlements and fails silently in production is one of the harder regressions to catch after the fact.

Frequently asked questions

When should an iOS app ask for notification permission?

In response to a specific user action that explains why notifications help, such as finishing onboarding or enabling a feature that depends on alerts, not on first launch before the user has any context.

What is the maximum size of an APNs push payload?

4KB for a standard remote notification and 5KB for VoIP; anything larger is rejected with a PayloadTooLarge error, so rich media has to be fetched after delivery, not embedded in the payload.

How long is an APNs provider authentication token valid?

One hour. The same JWT should be reused for every request during that window and rotated before it expires rather than minted per push.

How do you add images or video to an iOS push notification?

Use a Notification Service Extension to intercept the notification before it displays, download the media referenced by a small payload, and attach it to the notification content.

Why do some push notifications never arrive on iOS?

Common causes are a device token mismatched to the wrong environment or bundle ID (BadDeviceToken), a token the system has already invalidated (Unregistered), or sending at apns-priority 5, which Apple can batch, throttle, or drop under power constraints.