Skip to content
Shiny.NET

Platform Setup

A push only arrives when three pieces agree: the platform (Apple, Google, Microsoft, the browser) knows your app, the app registers with it and hands its token to your server, and the server holds the credentials to send. This page walks through all three for every platform Shiny.Push supports.

Platform Platform console App Server
iOS / macOS (APNs) Apple Developer Shiny.Push AddApns
Android (FCM) Firebase Shiny.Push AddFcm
iOS via Firebase Firebase + Apple Developer Shiny.Push.FirebaseMessaging AddApns with the native token
Windows (WNS) Partner Center Shiny.Push AddWns
Blazor WebAssembly (Web Push) none (VAPID keys) Shiny.Push AddWebPush
Azure Notification Hubs Azure portal + the stores above Shiny.Push.AzureNotificationHubs Azure Notification Hubs SDK

The server column is the Shiny.Extensions.Push library, which sends to APNs, FCM, WNS and Web Push directly. You can use any backend instead: the app only needs to send its token somewhere.

Every platform ends the same way: IPushDelegate.OnNewToken hands you a token, and the app sends it to your server. Do it from the delegate so token rotation is covered, not only the first registration:

public class MyPushDelegate(IMyApi api) : PushDelegate
{
public override Task OnNewToken(string token) => api.RegisterPushToken(new
{
DeviceToken = token,
#if IOS || MACCATALYST
Platform = "iOS",
#elif MACOS
Platform = "MacOS",
#elif ANDROID
Platform = "Android",
#elif WINDOWS
Platform = "Windows",
#else
Platform = "WebBrowser",
#endif
#if DEBUG
Environment = "Sandbox", // debug builds get APNs sandbox tokens
#else
Environment = "Production", // App Store and TestFlight builds get production tokens
#endif
DeviceId = myInstallId, // a GUID you persist once per install
UserIdentifier = currentUserId
});
public override Task OnUnRegistered(string token) => api.UnregisterPushToken(token);
}

On the server, turn that into a DeviceRegistration, which says what each platform’s token looks like and how Web Push subscriptions are split up.


Covers iOS, Mac Catalyst, macOS and tvOS. Requires a paid Apple Developer Program membership.

  1. Enable push on the App ID. Open Certificates, Identifiers & Profiles → Identifiers, select your App ID (it must match ApplicationId in your .csproj), tick Push Notifications and save.

  2. Regenerate your provisioning profiles. Profiles created before you enabled push don’t carry the capability. Re-download them under Profiles, or let automatic provisioning refresh them.

  3. Create an APNs auth key for the server. See APNs → Apple Developer setup. One .p8 key works for every app on the team, in both sandbox and production.

  1. Add the entitlement. aps-environment must be development for debug builds and production for release builds:

    <ItemGroup Condition="$(TargetFramework.Contains('-ios')) OR $(TargetFramework.Contains('-maccatalyst'))">
    <CustomEntitlements Include="aps-environment" Type="string" Value="development" Condition="'$(Configuration)' == 'Debug'" Visible="false" />
    <CustomEntitlements Include="aps-environment" Type="string" Value="production" Condition="'$(Configuration)' == 'Release'" Visible="false" />
    </ItemGroup>

    Signing fails with a profile that doesn’t have the Push Notifications capability, such as a wildcard profile.

  2. Enable background delivery. Add remote-notification to UIBackgroundModes in Platforms/iOS/Info.plist so silent pushes (content-available: 1) wake the app:

    <key>UIBackgroundModes</key>
    <array>
    <string>remote-notification</string>
    </array>
  3. Register Shiny.Push with services.AddPush<MyPushDelegate>() (Getting Started), call IPushManager.RequestAccess(), and send the token to your server.

  4. Test on a physical device. The simulator can receive pushes but can’t register for a real APNs token.

Configure AddApns with the Team ID, Key ID, bundle id and .p8 key. Register each device with Platform = iOS (or MacOS) and the Environment the app reported: debug-signed builds get sandbox tokens, while App Store and TestFlight builds get production tokens, and the two aren’t interchangeable.

For the payload to reach IPushDelegate.OnReceived in the background, send ContentAvailable = true (see the FAQ).


  1. Create or open a project in the Firebase console.

  2. Add the Android app. In Project settings → General, click Add app → Android and enter your ApplicationId as the package name.

  3. Download google-services.json from the same page. This is the app’s configuration, and it isn’t secret.

  4. Create a service account key for the server. See FCM → Firebase setup. That key is secret and never goes in the app.

  1. Add google-services.json to Platforms/Android with build action GoogleServicesJson, or pass a FirebaseConfig with the values instead (Getting Started).

  2. Add the tap intent filter to MainActivity so IPushDelegate.OnEntry fires when a notification is tapped (Getting Started).

  3. Register Shiny.Push with services.AddPush<MyPushDelegate>(), call IPushManager.RequestAccess() (it also asks for the Android 13+ notification permission), and send the token to your server.

Configure AddFcm with the service account JSON and register each device with Platform = Android. Two payload rules decide which delegate method fires:

  • OnReceived only fires for messages with a data section, so put something in PushNotification.Data.
  • PushNotification.DeepLink is sent as the Android click_action. The tap only reaches OnEntry if that matches the intent filter (SHINY_PUSH_NOTIFICATION_CLICK by default), so for Shiny apps put deep links in Data instead.

Use this when you want FCM tokens and topics on iOS as well as Android. Android setup is unchanged.

Firebase console and Apple Developer portal

Section titled “Firebase console and Apple Developer portal”
  1. Do the Apple Developer portal steps above. Firebase still delivers through APNs.

  2. Add the iOS app. In Project settings → General, click Add app → iOS and enter your bundle id. Download GoogleService-Info.plist.

  3. Give Firebase your APNs key. Under Project settings → Cloud Messaging → Apple app configuration, upload the .p8 APNs auth key with its Key ID and Team ID.

Add GoogleService-Info.plist with build action BundleResource, add the entitlement and background mode from the Apple section, and register with services.AddPushFirebaseMessaging<MyPushDelegate>(). See Firebase (iOS).

OnNewToken receives the FCM token on iOS. The Shiny.Extensions.Push FCM transport only delivers to Platform = Android registrations, so either send IPushManager.NativeRegistrationToken (the APNs token) as an iOS registration and use AddApns, or send the FCM token through your own FCM sender.


  1. Reserve the app name in Partner Center → Apps and games. WNS only issues channels to apps with a Store identity.

  2. Associate the project with the Store. In Visual Studio, right-click the project → Publish → Associate App with the Store, or copy the identity from the app’s Product identity page into Platforms/Windows/Package.appxmanifest. The app must run packaged (MSIX).

Register with services.AddPush<MyPushDelegate>(). On Windows, RequestAccess() creates a channel with PushNotificationChannelManager, and the token is the channel URI. Send it to your server.

Configure AddWns and register each device with Platform = Windows.


There’s no store console. You generate your own VAPID key pair, and the browser vendor’s push service delivers to whatever server holds the private key.

  1. Generate a VAPID key pair once, for example with the web-push CLI:

    Terminal window
    npx web-push generate-vapid-keys
  2. Keep the private key on the server only. The public key goes in the app.

  1. Register with the public key:

    services.AddPush<MyPushDelegate>(new WebPushOptions
    {
    VapidPublicKey = "<vapid-public-key>"
    });
  2. Serve the service worker. The bundled worker is served from ./_content/Shiny.Push/push-sw.js (the default ServiceWorkerPath). If you already have a service worker, set ServiceWorkerPath to yours and importScripts('./_content/Shiny.Push/push-sw.js') from it.

  3. Serve over HTTPS (localhost is fine for development). Safari only allows Web Push after the user installs the site as a PWA.

  4. Send the token. On the web the token is the browser’s whole PushSubscription serialized as JSON.

Configure AddWebPush with the same key pair and a contact Subject. The subscription JSON is split into a DeviceRegistration: endpoint becomes DeviceToken, and keys.p256dh and keys.auth go in Data. See Client Apps → Web Push. The bundled service worker shows title, body and icon and passes Data to your delegate, so put deep links in Data.


Azure Notification Hubs sits between your server and the stores: the app registers an installation with the hub, and your server sends to the hub by tag. Supported on iOS, Android and Windows.

Do the platform console steps above first, for every platform you target:

  • Apple: the App ID push capability and an APNs .p8 key (how).
  • Android: the Firebase project, google-services.json, and a service account key (how).
  • Windows: the Partner Center association, plus the Package SID and client secret from the app’s WNS settings.
  1. Create a Notification Hub (and its namespace) from Create a resource → Notification Hub.

  2. Add the store credentials under the hub’s Settings:

    • Apple (APNS): choose Token, enter the Key ID, bundle id, Team ID and the contents of the .p8 file, and pick Sandbox or Production to match your build.
    • Google (FCM v1): enter the private key, client email and project id from the service account JSON.
    • Windows (WNS): enter the Package SID and security key.
  3. Copy the connection strings from Manage → Access Policies. The app uses DefaultListenSharedAccessSignature, which can only register. The server uses DefaultFullSharedAccessSignature, which can send, and must never ship in the app.

Microsoft’s Notification Hubs documentation has screenshots of each step.

Do the app steps for each platform above (entitlements, google-services.json, Store association), then register with the listen connection string:

services.AddPushAzureNotificationHubs<MyPushDelegate>(
"Endpoint=sb://<namespace>.servicebus.windows.net/;SharedAccessKeyName=DefaultListenSharedAccessSignature;SharedAccessKey=<key>",
"<hub-name>"
);

OnNewToken receives the hub installation id, not the native token. Tags set through IPushManager.Tags are stored on the installation. See Azure Push Notifications.

Shiny.Extensions.Push doesn’t talk to Notification Hubs. Send with Microsoft’s Microsoft.Azure.NotificationHubs package and the full connection string:

var hub = NotificationHubClient.CreateClientFromConnectionString(fullAccessConnectionString, "<hub-name>");
// to every installation tagged "premium"
await hub.SendAppleNativeNotificationAsync("{\"aps\":{\"alert\":\"Hello\"}}", "premium");
await hub.SendFcmV1NativeNotificationAsync("{\"message\":{\"data\":{\"message\":\"Hello\"}}}", "premium");
// to one device, using the installation id the app reported from OnNewToken
await hub.SendAppleNativeNotificationAsync(payload, $"$InstallationId:{installationId}");