Skip to content
Document DB v8.0 Interceptors, Temporal Support, Telemetry Collection, All Calculations, String Based APIs, & Orleans Storage Providers! Feed The Machine Here

APNs

Shiny.Extensions.Push.Apns talks to Apple Push Notification service directly — HTTP/2 with token-based (.p8) authentication. No FCM/Google dependency for Apple devices. It handles iOS and macOS registrations.

You need an APNs auth key (.p8) from the Apple Developer portal, its Key ID, your Team ID, and the app’s bundle id.

push.AddApns(o =>
{
o.TeamId = "ABCDE12345"; // 10-char Apple Team ID (JWT "iss")
o.KeyId = "KEY1234567"; // 10-char Key ID of the .p8 (JWT "kid")
o.BundleId = "com.example.app"; // default apns-topic
o.PrivateKeyPath = "AuthKey_KEY1234567.p8"; // or o.PrivateKey = "<PEM contents>"
// o.ForceEnvironment = PushEnvironment.Sandbox; // default: honour each registration
});

The provider builds and caches its ES256 provider JWT (refreshed ~every 50 minutes — Apple rejects tokens regenerated too frequently) and reuses a single pooled HTTP/2 connection across all sends.

APNs tokens are environment-specific — a token registered against the sandbox (debug/TestFlight builds) is not valid in production and vice-versa. Set DeviceRegistration.Environment per device:

await pushManager.RegisterDevice(new DeviceRegistration
{
DeviceToken = "<token>",
Platform = DevicePlatform.iOS,
Environment = PushEnvironment.Sandbox
});

The provider routes each push to the correct host based on the registration. Use ApnsOptions.ForceEnvironment only if you want to pin every push to one environment.

ApplePushOptions on the notification maps to the aps dictionary and APNs headers:

new PushNotification
{
Title = "Title",
Apple = new ApplePushOptions
{
Subtitle = "Subtitle",
Category = "MESSAGE_CATEGORY", // actionable notifications
ThreadId = "thread-1", // grouping
MutableContent = true, // Notification Service Extension
ContentAvailable = false, // true => silent/background push
// TopicOverride / PushTypeOverride for VoIP / complication topics
}
};

Common cross-cutting fields also map to APNs: Badge, Sound, CollapseId (apns-collapse-id), TimeToLive (apns-expiration), and Priority (10/5; forced to 5 for background pushes).

The provider normalizes APNs responses to PushDeliveryStatus:

APNs resultStatusManager action
410 UnregisteredTokenExpiredtoken pruned
BadDeviceToken / DeviceTokenNotForTopicInvalidTokentoken pruned
429 TooManyRequestsRateLimitedsurfaced (no auto-retry)
ExpiredProviderToken / InvalidProviderTokenErrorJWT cache invalidated

Register one keyed provider per bundle id — see Sending → Multiple apps. Each key gets its own options, .p8 key, and JWT cache.