Push Notification Payload Reference

Updated: 2026-10-02 18:34 ET · Audited: 2026-06-26

TL;DR: Contract reference for the content-driven push notification payload. Routing is driven by contentType — Document, WebLink, DeepLink, FlightNotification, or empty string (title/message rendered in a WebView) — with destination, docType, and fileId conditionally required. Deep links use the PageName?Param=Value format against a fixed set of supported pages. requiresLogin and iconUrl are required: the parser reads both unguarded, so omitting either throws KeyNotFoundException and drops the notification.

This document defines the payload contract used by ALPA Mobile for content-driven push notifications.

Overview

The app supports content payloads that route users to one of these content types:

The docType field further identifies render behavior for document content, such as pdf or htm.

Field definitions

FieldRequiredTypeDescription
idYesstringUnique content identifier as string.
titleYesstringNotification title shown to the user.
messageYesstringNotification body text.
contentTypeYesstringOne of Document, WebLink, DeepLink, FlightNotification, or empty string (displays title/message in WebView).
destinationConditionalstringURL or route target. Required for Document, WebLink, and DeepLink.
docTypeConditionalstringRequired for Document; typical values: pdf, htm, webpage.
fileIdConditionalstringDocument file identifier when source is a managed file.
sourceNostringUpstream content source identifier (e.g. sitecore).
correlationKeyNo — but required in practice on FlightNotificationstringGroups related notifications — used as the Android notification group (FirebaseService.SetGroup). Defaults to a fresh GUID when omitted. On a flight alert it carries the flight's schedule key, bare (no fs-), and that is what a tap routes by (see Flight alert routing below): with it, the tap opens that flight's detail screen; without it, the tap can only reach the alert list.
portalIdNonumberDeprecated — Legacy portal identifier (not parsed by the app).
requiresLoginYesstring"true"/"false" — sets RequiresAuthentication. Read unguarded in PushNotification.PushNotificationFromFBM/FromAPNS; omission throws KeyNotFoundException.
iconUrlYesstringRead unguarded in both parse paths; omission throws KeyNotFoundException. Value may be empty but the key must be present.
iconUrl2NostringOptional secondary icon URL (parsed when present).
iconUrl3NostringOptional tertiary icon URL (parsed when present).

Payload examples

Every payload must also carry the requiresLogin and iconUrl keys (values may be "false"/empty); the examples below predate that finding and omit them.

Document (HTML)

{
  "id": "0",
  "message": "Commemorating the Passage of Historic Pilot Qualification and Training Legislation",
  "title": "FastRead",
  "contentType": "Document",
  "docType": "htm",
  "source": "sitecore",
  "destination": "~/media/F994CC65B5FB45ECABD1BF61F5C85E61.ashx?modified=20230803T2128Z",
  "fileId": "{F994CC65-B5FB-45EC-ABD1-BF61F5C85E61}"
}

Document (PDF)

{
  "id": "1005",
  "message": "ALPA MAG Test",
  "title": "Alpa Mag Test",
  "contentType": "Document",
  "destination": "~/media/D65CA7D4314B43C6B75A1C5181B1D475.ashx?modified=1589232930000",
  "docType": "pdf",
  "fileId": "{D65CA7D4-314B-43C6-B75A-1C5181B1D475}",
  "source": "sitecore"
}

Document (Website)

{
  "id": "1000",
  "message": "Live Article Test",
  "title": "Test Article",
  "contentType": "Document",
  "destination": "https://mag.alpa.org/Articles/mag-hotline-april-9",
  "docType": "webpage"
}
{
  "id": "01",
  "message": "Testing with google link",
  "title": "Weblink Test",
  "contentType": "WebLink",
  "destination": "https://google.com"
}

Flight notification

Flight-change alerts for subscribed flights, sent by Flight Change Notifier (FlightNotifications.App/App.cs) to the fs-{ScheduleKey} audience via Azure Notification Hub. correlationKey is the bare schedule key — only the audience carries fs-. Tapping opens that flight's detail screen (see Flight alert routing).

{
  "id": "188040",
  "message": "DL5328 from JFK to SYR now arrives at 07/29/2026 15:38.",
  "title": "Flight Changed",
  "contentType": "FlightNotification",
  "correlationKey": "9fceb91f-0488-41ac-a2ab-40619d8d155c"
}

Title and message in WebView body

When contentType is empty, the app displays the title and message directly in a WebView:

{
  "id": "01",
  "message": "Testing message goes here",
  "title": "Testing title goes here",
  "contentType": ""
}

The MAUI app uses a simplified deep link format with the page name followed by optional query parameters.

Format: PageName?QueryParam1=Value1&QueryParam2=Value2

Supported pages (the Blazor Hybrid app resolves the same strings via DeepLinkResolver.cs, which maps each page name to a Blazor route — 27 mapped names plus two pseudo-pages):

Page nameBlazor route
HomePage/home
UserProfilePage/profile
SettingsPage/profile (settings live in a section of the profile page)
FavoritesPage/favorites
DocumentCenterPage/documents
DocumentsListPage/documents
DocumentsPage/documents
FlightDutyPeriodListPage/ftdt/dashboard
JumpseatFlightFinderSavedFlightsPage/jumpseat/saved
JumpseatFlightFinderFrequentSearchesPage/jumpseat/recent
PushNotificationsListPage/notifications
PushNotificationsSettingsPage/notifications/settings
MemberResourcesPage/member-resources
MyPilotGroupPage/mec
PilotGroupCommitteePage/mec/committees
PilotGroupEventsPage/mec/events
PilotGroupHotelsPage/mec/hotels
PilotGroupRepresentativesPage/mec/reps
KCMHomePage/kcm
KCMAirlinesPage/kcm/airlines
KCMAirportsPage/kcm/airports
JumpseatInfoPage/jumpseat/search
JumpseatFlightFinderSearchPage/jumpseat/search
JumpseatAirlinePolicyPage/jumpseat/airline-policies
JumpseatEtiquettePage/jumpseat/etiquette
JumpseatMissionPage/jumpseat/mission
AdvocacyPage/advocacy

Pseudo-pages (intercepted, not navigated to):

Query keys are carried across with keys lowercased (native binds Scope, Blazor binds scope). An unmapped page name is reported as unresolved rather than guessed.

WebViewPage?... parameters (native AppShell.OnNavigating only):

Since AB#2591, BannerSubtitle/ImageGlyph/ImageGlyphFontFamily are still parsed but no longer rendered anywhere — AuthenticatedWebViewPage's header has one title slot (falls back BannerTitle → page title), no subtitle or glyph slot.

{
  "id": "1000",
  "message": "DeepLink notification",
  "title": "DeepLink notification",
  "contentType": "DeepLink",
  "destination": "DocumentCenterPage?Scope=ALPA"
}

Validation notes

Flight alert routing (2026-08-14, corrected 2026-10-02)

A tapped FlightNotification opens the flight the alert is about, not a list of alerts. PushNotificationHandler reads the schedule key from correlationKey (FlightNotificationText.ScheduleKeyFrom(PushNotification); a fs--prefixed key is accepted too) and routes to /jumpseat/flight-detail/alert/{ScheduleKey}?notif={id}; the screen resolves that key against the loaded search results, then against Saved Flights, and when the device holds neither — a flight unsaved since the alert was sent, or saved on another device — asks the service (POST /api/FlightSearchV2/FindLegsByScheduleKey, IFlightSearchQueries.FindFlightByScheduleKeyAsync). That answer is the one leg the key names, so the card shows one leg. If the service cannot supply it, the empty state says why: no longer in the schedule, offline, signed out, or could not load (pull down retries). Back from there returns to the alert list on the Flight Finder tab with the tapped alert focused.

When the payload carries no correlationKey, the handler looks the alert up in the Flight Finder alert feed by id and takes the key from its fs-{ScheduleKey} audience. That feed holds only the flights subscribed now and must answer within 5 seconds, so it is a fallback, not a route: when it misses, the tap lands on /notifications?tab=flight&focus={id}.

Correction (AB#3059). Until 2026-10-02 the handler required correlationKey to start with fs-. The sender has sent the bare key since 2025-04-02 (Flight Change Notifier commit 81b136f), so the payload never matched and every tap depended on the feed lookup — which is why taps opened the alert list intermittently (after a subscription change, a slow feed, or an alert the feed did not yet list). The App Logs line [NavigateToFlightNotificationAsync] … keySource:payload|feed|none route:… now says which path a tap took, and [ScheduleKeyFromFeedAsync] logs why a feed lookup found nothing.

Absent vs. fabricated. PushNotification.CorrelationKey makes up a GUID when the field is unset (Android groups notifications by it). That GUID is shaped like a real key, so the handler trusts only a key the payload actually carried (PushNotification.HasCorrelationKey).

Hub template note. The field only reaches the device if the registration template carries it. The Android and silent-iOS templates always have; the visible-iOS template did not until 2026-08-14 (Constants.FSNotificationHubRegistrationTemplateiOS), so an iOS tap had nothing to route by regardless of what the sender supplied. Devices pick the added field up on their next hub registration, which happens at app start.