TL;DR: Contract reference for the content-driven push notification payload. Routing is driven bycontentType—Document,WebLink,DeepLink,FlightNotification, or empty string (title/message rendered in a WebView) — withdestination,docType, andfileIdconditionally required. Deep links use thePageName?Param=Valueformat against a fixed set of supported pages.requiresLoginandiconUrlare required: the parser reads both unguarded, so omitting either throwsKeyNotFoundExceptionand drops the notification.
This document defines the payload contract used by ALPA Mobile for content-driven push notifications.
The app supports content payloads that route users to one of these content types:
DocumentWebLinkDeepLinkFlightNotification — flight-change alert; opens the Flight Finder notifications tab with the tapped alert expanded (PushNotificationHandler)The docType field further identifies render behavior for document content, such as pdf or htm.
| Field | Required | Type | Description |
|---|---|---|---|
id | Yes | string | Unique content identifier as string. |
title | Yes | string | Notification title shown to the user. |
message | Yes | string | Notification body text. |
contentType | Yes | string | One of Document, WebLink, DeepLink, FlightNotification, or empty string (displays title/message in WebView). |
destination | Conditional | string | URL or route target. Required for Document, WebLink, and DeepLink. |
docType | Conditional | string | Required for Document; typical values: pdf, htm, webpage. |
fileId | Conditional | string | Document file identifier when source is a managed file. |
source | No | string | Upstream content source identifier (e.g. sitecore). |
correlationKey | No — but required in practice on FlightNotification | string | Groups related notifications — used as the Android notification group (FirebaseService.SetGroup). Defaults to a fresh GUID when omitted. On a flight alert it carries the fs-{ScheduleKey} audience the push was addressed to, 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. |
portalId | No | number | Deprecated — Legacy portal identifier (not parsed by the app). |
requiresLogin | Yes | string | "true"/"false" — sets RequiresAuthentication. Read unguarded in PushNotification.PushNotificationFromFBM/FromAPNS; omission throws KeyNotFoundException. |
iconUrl | Yes | string | Read unguarded in both parse paths; omission throws KeyNotFoundException. Value may be empty but the key must be present. |
iconUrl2 | No | string | Optional secondary icon URL (parsed when present). |
iconUrl3 | No | string | Optional tertiary icon URL (parsed when present). |
Every payload must also carry the requiresLogin and iconUrl keys (values may be "false"/empty); the examples below predate that finding and omit them.
{
"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}"
}
{
"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"
}
{
"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-change alerts for saved flights. Tapping opens the Flight Finder notifications tab with the tapped alert expanded (PushNotificationHandler); delivered to the fs-{ScheduleKey} audience via Azure Notification Hub.
{
"id": "188040",
"message": "DL5328 from JFK to SYR now arrives at 07/29/2026 15:38.",
"title": "Flight Changed",
"contentType": "FlightNotification",
"correlationKey": "fs-9fceb91f-0488-41ac-a2ab-40619d8d155c"
}
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 name | Blazor 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):
Launcher?Uri=tel:2027974180 — hands the URI to the OS (Launcher.OpenAsync); used for tel:/mailto: destinationsWebViewPage?SourceString=… — shows the URL; on Blazor Hybrid this resolves to the native
auto-login modal (IAuthenticatedWebView → AuthenticatedWebViewPage, AB#2591 —
not the system browser, so the member stays signed in) and the native chrome hints below
are dropped. Native's AppShell.OnNavigating handler renders the same pseudo-page string with
the full chrome-hint set below. The WebViewPage?... string itself is a stable wire-format
token, unrenamed by AB#2591 even though the native page it now builds is
AuthenticatedWebViewPage.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):
SourceString — URL to loadBannerTitle — Optional banner titleBannerSubtitle — Optional banner subtitleShowCompact — Set to True for compact viewShowFAB — Set to True to show floating action buttonImageGlyph — Icon glyph (e.g., Gear)ImageGlyphFontFamily — Font family for icon (e.g., FontAwesome)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"
}
contentType drives routing behavior and must match one of the supported values: Document, WebLink, DeepLink, FlightNotification, or empty string.destination must be non-empty and valid for the selected content type (not required when contentType is empty or FlightNotification).Document payloads, include both docType and fileId where available.requiresLogin and iconUrl keys are required on every payload — both are read unguarded (PushNotification.cs), so omitting either throws KeyNotFoundException. iconUrl2, iconUrl3, and correlationKey are optional to the parser; portalId is not parsed. On FlightNotification, however, omitting correlationKey silently downgrades the tap destination — see Flight alert routing.A tapped FlightNotification opens the flight the alert is about, not a list of alerts.
PushNotificationHandler strips the fs- prefix off correlationKey to recover the
schedule key and routes to /jumpseat/flight-detail/alert/{ScheduleKey}?notif={id}; the screen resolves that
key against the loaded search results, then against Saved Flights, where a subscribed flight always is (subscribing to
alerts saves the flight). Back from there returns to the alert list on the Flight Finder tab with the tapped alert
focused. A payload with no correlationKey — or one whose key matches no flight on the device — still lands
on /notifications?tab=flight&focus={id}, which is where every flight tap went before.
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.