Skip to content
WindowsDeep Dive Published Updated 9 min readViews unavailable

Windows App SDK Push Notifications: Channel Renewal, Identity, and Activation

Build a Windows App SDK push path with correct package identity, renewable WNS channels, ordered activation setup, and a tested fallback.

Windows App SDK push notifications combine a desktop app’s package and activation model with a cloud service that sends through Windows Push Notification Services (WNS). The operational work crosses three components: the app registration in Microsoft Entra ID, the installed app identity and activation metadata, and a backend that stores each device’s current WNS channel URI. A message can fail even when each component appears configured if those identities are mismatched or the app keeps using an expired channel. This walkthrough follows the current Microsoft Learn quickstart for Windows App SDK 1.7; verify requirements again when moving to another SDK release or packaging model.

Decide packaging and identity before implementation

The current quickstart supports MSIX-packaged apps, packaged apps with an external location, and a limited truly unpackaged path. For packaged apps, package identity is required for background delivery and COM activation. The app manifest must register a COM activator for the push activation path, and the COM class ID is the app’s Entra Application (client) ID. The packaged app’s Package Family Name (PFN) must also be mapped to that Azure AppId through Microsoft’s push mapping process. Microsoft says mapping requests are submitted by email and processed weekly, so treat this as a launch dependency rather than a final testing task.

Truly unpackaged apps can use a limited feature path, but the quickstart does not require the packaged-app COM activator or PFN mapping for that case. Do not assume that unpackaged behavior is interchangeable with background activation for MSIX. Similarly, Windows App SDK push support may not be available for a self-contained deployment or an app running elevated as administrator. Keep a supported fallback, and check PushNotificationManager::IsSupported() on the target device and deployment configuration rather than inferring support from a successful development-machine run.

Microsoft’s setup path requires a multi-tenant Entra app registration. Keep its identifiers distinct:

Value Used for
Application (client) ID / Azure AppId Packaged COM activator registration and the backend’s WNS access-token request.
Directory (tenant) ID The token endpoint used by the backend’s client-credentials flow.
Managed application Object ID / RemoteIdentifier The CreateChannelAsync request from the app. Microsoft specifically warns not to copy the unrelated Object ID shown on the registration’s Essentials page.
Package Family Name The installed package identity, which Microsoft maps to the Azure AppId for packaged delivery.

Before coding, record these values from the right Entra objects, then verify that the packaged app’s manifest, PFN mapping request, channel request, and backend token configuration all refer to the intended app registration. Do not copy sample GUIDs into a release build.

Register events before starting push activation

The Windows App SDK quickstart has an ordering constraint. Register foreground PushReceived handlers before calling PushNotificationManager::Default().Register(); the API reference documents an “Element not found” exception if the handler is registered afterward. Then follow the quickstart order and register the manager before asking AppInstance for activated event arguments, so startup can classify a push activation through the initialized push path.

The lifecycle has a matching shutdown requirement: before the app process terminates, call Unregister() for the ordinary case. Microsoft describes UnregisterAll() as an uncommon cleanup path that removes all push-registration data; notifications will not function again until Register() is called. Keep those operations distinct in code and tests. Normal process shutdown is not the same as intentionally clearing registration state. After Unregister(), Microsoft documents that a new process can be launched to handle later notifications.

The initialization path should look like this:

// Attach foreground handlers first.
PushNotificationManager::Default().PushReceived(OnPushReceived);

// Then initialize push activation before inspecting activation arguments.
PushNotificationManager::Default().Register();
auto activation = AppInstance::GetCurrent().GetActivatedEventArgs();

if (activation.Kind() == ExtendedActivationKind::Push) {
    auto args = activation.Data().as<PushNotificationReceivedEventArgs>();
    auto deferral = args.GetDeferral();
    ProcessSmallIdempotentSignal(args.Payload());
    deferral.Complete();
}

The handler name and payload function above are placeholders for application code. In a background push activation, use the deferral while the handler performs bounded work and complete it as soon as that work ends. Do not put a long data migration, unbounded network retry, or interactive UI workflow in the activation handler. In the foreground, use the event for immediate response while keeping the same validation rules as any other externally supplied input.

Push can be unavailable in supported deployment scenarios, so the app should have a deliberate alternate path such as polling when foregrounded or a separately supported socket design. IsSupported() is a capability check, not proof that a future WNS send will arrive. Treat the push payload as a signal to reconcile durable application state, not as the only copy of a business transaction. That is an application reliability choice; it avoids coupling data correctness to a transient notification event.

Request, register, and renew the channel URI

On app launch, request a channel using the correct Entra Object ID as the RemoteIdentifier. WNS channel URIs expire after 30 days. Microsoft’s quickstart says to request a fresh URI on every app launch and update the backend when it differs from the currently stored URI. Do not persist a URI in the app as a permanent endpoint or assume that the same installation will receive the same value on its next launch.

CreateChannelAsync can report progress states, including a retry state with an extended error and retry count. The current quickstart says the manager retries channel creation automatically for no more than 15 minutes. Keep this operation asynchronous: display or continue the app’s normal launch path while channel registration is pending, then surface a useful degraded state if it fails. Do not freeze the main window for the retry window or spin your own tight retry loop on top of the manager’s backoff.

When a channel request succeeds, send the URI to an authenticated backend registration endpoint. Associate it with the app identity and the server-side user/device installation record your product uses. Replace a previous URI only after the backend has validated the caller and accepted the new registration. If the endpoint is unchanged, an idempotent registration can avoid unnecessary writes. The URI contains a WNS token in its URL form; do not write it to ordinary telemetry, crash reports, or support logs. This protects routing data and makes accidental reuse less likely.

On the server, obtain WNS access tokens using the app registration’s client ID, tenant ID, and secret or configured credential flow. The client secret belongs in server-side secret storage such as the Azure Key Vault location recommended by Microsoft’s quickstart, not in the desktop binary. Store token acquisition errors separately from channel-registration errors: the first is a backend-to-Entra/WNS authentication problem, while the second is an app-to-service registration problem.

Separate the push endpoint from what the app does with it

WNS delivers to a channel URI, but the app still has to interpret the activation. A packaged app’s COM activator ties the Azure AppId in the manifest to the COM server WNS can launch for background activation. A push event while the process is running follows the foreground event handler; a background push activation is surfaced through the app lifecycle activation arguments. Test both paths. A single test that leaves the app open validates only foreground handling.

Keep push processing idempotent. Include a server-side event or synchronization cursor in the payload when appropriate, validate it before acting, and make a later foreground reconciliation recover from a duplicate, delayed, or missed signal. Microsoft reserves the right to disable or revoke app use of push notifications, and the overview documents deployment limitations; therefore, push should not be the sole mechanism that eventually commits critical state. Avoid asserting delivery guarantees beyond the supported WNS contract and your own service’s behavior.

Also distinguish raw push from a cloud-sourced app notification. Raw push carries app-directed data for your app to process. A cloud-sourced toast uses the WNS notification type and XML app-notification payload; showing a toast does not replace the app’s state-reconciliation path. If the user clicks a toast, treat the resulting activation as a separate input that can refer to state that has since changed.

Stage the launch and failure tests

Use a test package and test Entra registration to verify the entire path before submitting mapping requests for a production PFN. Record the Windows build, Windows App SDK package version, package identity model, PFN, app/client ID, the correct service-principal Object ID, channel expiration, and the last successful backend registration. Redact the channel URI and client secret from collected logs.

At minimum, test:

  • A packaged install with its COM activator and PFN mapping, plus the truly unpackaged path only if the product intends to support it.
  • Foreground push receipt with the event handler registered before Register().
  • Background push activation after closing the app, followed by successful deferral completion.
  • Graceful process shutdown calls Unregister(), then a later notification is handled by a newly launched process; separately verify that intentional UnregisterAll() cleanup requires a subsequent Register() before notifications function again.
  • Launch-time channel creation, a changed URI update to the backend, a failed backend registration, and a channel request that enters retry.
  • Reopening after more than 30 days, so the backend stops targeting a stale URI after the client reports a new one.
  • Elevated and self-contained configurations if they are in scope, including the IsSupported() fallback.
  • Duplicate, stale, malformed, and out-of-order application-level signals, followed by a foreground reconciliation.

For an incident, identify which boundary failed before regenerating credentials: installation identity and PFN mapping, Entra registration and RemoteIdentifier, WNS channel creation/expiration, backend URI storage, token acquisition, or app activation. This makes the repair targeted and prevents an unnecessary identity change from invalidating otherwise healthy channels. The durable contract is a renewable per-app channel and correctly ordered activation setup, not a permanent URL or a guarantee that a notification replaces synchronization.

Related:

Sources:

Comments