Skip to main content

Firebase Cloud Messaging: Frontend Integration

Purpose and audience​

This page is the client contract for mobile and web-portal developers integrating BankLingo notifications with Firebase Cloud Messaging (FCM) and SignalR. It contains no tenant-specific Firebase project details or server credentials.

FCM is used to wake or open an installed mobile application. SignalR delivers immediate events to an active session, while persisted notifications provide inbox/history retrieval. The three delivery paths are complementary:

ChannelWhen it worksFrontend responsibility
Persisted notificationWhether the app is online or offlineRetrieve the inbox after authentication
SignalRWhile the application has a live connectionHandle the named SignalR event
FCMWhen an installed application has an active registrationHandle foreground, background, and terminated-app delivery

FCM project creation, Admin SDK credentials, and tenant configuration are covered separately in Firebase Server Configuration.

Command endpoint​

All mobile notification commands use the authenticated self-service command endpoint:

POST /api/bpm/selfservice/cmd

Required headers:

Authorization: Bearer <customer-access-token>
Content-Type: application/json
X-App-Channel: Mobile
X-Device-Id: <stable-app-installation-id>
X-Client-Platform: Android
X-App-Version: <application-version>

X-Device-Id is the stable BankLingo installation identifier already used by the login and device-switch flow. It is not an FCM registration token, Firebase Installation ID, advertising identifier, or hardware serial number.

The standard command request has this shape. The data property is a JSON-encoded string:

{
"cmd": "CommandName",
"data": "{\"property\":\"value\"}"
}

Register or refresh an FCM token​

After an authenticated login, request notification permission, obtain the current token from the Firebase client SDK, and call:

{
"cmd": "RegisterPushInstallationCommand",
"data": "{\"fcmToken\":\"<current-fcm-registration-token>\"}"
}

Call the same command whenever Firebase rotates the token. Registration is an upsert, so a separate update command is not required.

The backend derives the tenant and profile from the authenticated request. Do not include tenantId, userId, or another profile identifier in the request body. Registration succeeds only when X-Device-Id is the authenticated profile's verified mobile device.

Example helper:

async function executeMobileCommand(cmd, data, session, device) {
const response = await fetch(`${session.apiBaseUrl}/api/bpm/selfservice/cmd`, {
method: 'POST',
headers: {
Authorization: `Bearer ${session.accessToken}`,
'Content-Type': 'application/json',
'X-App-Channel': 'Mobile',
'X-Device-Id': device.installationId,
'X-Client-Platform': device.platform,
'X-App-Version': device.appVersion,
},
body: JSON.stringify({ cmd, data: JSON.stringify(data) }),
});

return response.json();
}

await executeMobileCommand(
'RegisterPushInstallationCommand',
{ fcmToken: currentFcmToken },
session,
device,
);

Disable push for an installation​

Call this only when the user explicitly disables BankLingo push notifications for the current installation:

{
"cmd": "UnregisterPushInstallationCommand",
"data": "{}"
}

The command uses X-Device-Id. An installation owned by the authenticated profile can also be supplied explicitly:

{
"cmd": "UnregisterPushInstallationCommand",
"data": "{\"installationId\":\"<owned-installation-id>\"}"
}

Do not unregister on normal logout. The device/profile mapping deliberately survives logout so the operating system can show a generic notification. Protected content remains unavailable until authentication succeeds.

Installation lifecycle​

EventFrontend actionBackend behavior
First authenticated loginObtain and register the current FCM tokenVerifies the profile and device before creating the mapping
FCM token refreshCall RegisterPushInstallationCommand againUpdates the existing mapping
Normal logoutKeep the mappingContinues generic logged-out push delivery
Verified profile/device switchRegister after the switch completesReassigns the installation and revokes conflicting mappings
User disables push in BankLingoCall UnregisterPushInstallationCommandDeactivates the mapping
OS notification permission is denied or revokedReflect the state in the UI; unregister if the product decision is to stop attemptsThe backend cannot directly observe an OS permission change
App is uninstalled or its token becomes invalidNo callback is guaranteedA later permanent FCM token error deactivates the mapping

FCM payload​

The current BankLingo sender includes both an operating-system notification block and a routing data map in every FCM message. Consequently, the operating system can display the message in the notification tray or as a banner when the application is in the background. Every value in the FCM data map is a string:

{
"notification": {
"title": "Transaction authorisation required",
"body": "Open BankLingo to review a pending request."
},
"data": {
"notificationId": "<unique-notification-id>",
"action": "transaction_authorization_required",
"notificationType": "transaction_authorisation",
"tenantId": "<current-tenant-id>",
"data": "{\"transactionId\":123,\"requestId\":456}"
}
}

Parse the inner data value before routing:

function parseBankLingoPush(remoteMessage) {
const envelope = remoteMessage?.data ?? {};
let actionData = {};

try {
actionData = JSON.parse(envelope.data || '{}');
} catch {
actionData = {};
}

return {
notificationId: envelope.notificationId,
notificationType: envelope.notificationType || 'info',
action: envelope.action || '',
tenantId: envelope.tenantId,
data: actionData,
};
}

Never treat FCM data as proof that a transaction, task, or approval is valid. It is only a navigation hint; retrieve authoritative data after authentication.

The backend omits the serialized inner data value when it exceeds the FCM payload safety limit. Routing must therefore tolerate an empty object and use notificationId, action, or an authenticated inbox/status query to recover authoritative state.

Visible, data-only, and inbox-only delivery​

Yes, FCM can deliver a message to the application without automatically displaying an operating-system notification. Firebase calls this a data message. A data-only message omits the top-level notification block and contains only data; the application decides whether to refresh state silently, update an in-app badge, or show its own user-visible notification.

This is different from merely setting an empty title or body. To prevent Firebase or the operating system from automatically presenting a notification, the backend must omit the notification block entirely.

Desired behaviorBackend deliveryAutomatic OS tray/bannerIntended client handlingCurrent BankLingo support
User-visible pushFCM notification plus dataYes, subject to OS and user settingsDisplay the system notification; after a tap, authenticate and retrieve authoritative data.Supported and currently used by every shared-dispatcher FCM notification.
Silent application refreshFCM data-only messageNoProcess the recognised data action without opening a modal or creating a local notification; refresh only non-sensitive cached state.Not currently supported. FirebasePushService always adds a notification block.
Inbox/SignalR onlyPersist the notification and optionally send SignalR, but skip FCMNoUpdate the in-app inbox or active screen; show no operating-system notification.Not currently selectable per notification. The shared dispatcher always attempts FCM after persistence and SignalR.

Data-only delivery is not guaranteed to wake an application immediately. Android background processing depends on message priority and system restrictions. Apple background notifications require the appropriate background configuration and are treated as low priority; see Receiving messages on Apple platforms.

notificationType and action do not select one of these delivery modes. If silent or inbox-only delivery is implemented later, it should use a separate explicit backend delivery/presentation setting with user-visible FCM remaining the safe default. The client must never infer silent behavior from an unknown notificationType.

Complete event, type, and action contract​

Values are case-sensitive. The table below covers every fixed or explicitly supported notification contract currently available through the shared BankLingo notification dispatcher. All entries currently use visible FCM (notification plus data) when FCM is enabled and the profile has an active installation.

notificationTypePossible action valuesDescriptionImportant data fieldsSignalR eventIntended client handling
infoEmpty or caller-definedGeneral informational message.Caller-definedReceiveNotificationShow a normal inbox item, toast, or information presentation. Navigate only when the action is explicitly recognised.
successEmpty or caller-definedConfirms a successful operation.Caller-definedReceiveNotificationShow positive/success styling. Do not automatically execute an unknown action.
warningEmpty or caller-definedWarns the user about something requiring attention.Caller-definedReceiveNotificationShow warning styling without executing an unrecognised action.
errorEmpty or caller-definedReports an unsuccessful operation or problem.Caller-definedReceiveNotificationShow error styling and the supplied message. Offer a retry only when the underlying operation is safely retryable.
transactioninitiatedA transaction has been initiated and its status may still change.requestId, type, payload; payload can include transactionIdReceiveTransactionStatusAuthenticate and refresh the transaction from the backend. Open the matching transaction only when it belongs to the current profile.
transactiontransaction_updateCurrent transaction-status update.requestId, type, payload, including legacy IsSuccess where suppliedReceiveTransactionStatusRefresh the authoritative transaction record and render its latest state.
transactiontransactionupdateLegacy transaction-status update.requestId, type, payload; payload can contain status and transactionIdReceiveTransactionStatusRetain compatibility with status values success, initiated, and failed, then refresh authoritative state.
transactiontransaction_authorization_approvedThe customer successfully completed the signing step. Transaction execution may still be processing.transactionId, requestId, status, authorizationStatus, transactionStatus, isTerminal, occurredAtReceiveTransactionStatusMatch the web/agent chat transaction card, stop the authorisation countdown, show an approved state, and refresh authoritative transaction status. Do not treat this as proof that CBS execution succeeded.
transactiontransaction_authorization_rejectedThe customer deliberately rejected a PUSH_APPROVE request.Above fields plus reasonCode=REJECTED_BY_CUSTOMERReceiveTransactionStatusStop the countdown, show rejected styling, enable further chat input, and refresh the authoritative transaction.
transactiontransaction_authorization_expiredThe pending signing request reached its expiry without a decision.Above fields plus reasonCode=AUTHORIZATION_EXPIREDReceiveTransactionStatusStop the countdown, show expired styling, enable further chat input, and offer a safe way to prepare a new transaction.
transactiontransaction_authorization_failedAuthorisation reached a terminal failure. This is currently emitted when the transaction PIN becomes blocked after the fifth failed PIN validation.Above fields plus a safe reasonCode, currently PIN_BLOCKEDReceiveTransactionStatusStop the countdown and show a terminal failure. Do not display credential values or infer how the PIN failed. Direct the customer to PIN reset/support before starting again.
transaction_authorisationtransaction_authorization_requiredA prepared transaction is waiting for the customer to authorise or reject it.transactionId, requestId, transactionType, expiresAt, commandEndpoint, commandName, rejectCommandName, pendingCommandNameReceiveTransactionStatusRequire login, retrieve pending authorisations, and open only the matching server-returned transaction. Never authorise directly from the notification.
approvaltransaction_approval_requiredAn existing transaction-approval request is waiting for an approver.requestId, typeReceiveApprovalNotificationAuthenticate and retrieve the authoritative approval before opening it.
TASK_ASSIGNMENTTenant portal URL or emptyA workflow task has been assigned to the user.instanceGuid, taskId, taskName, processDefinitionId, processName, entityType, entityId, optionally userId, initiatedByUserId, linkReceiveNotificationAuthenticate and open the matching task. Validate any URL against the configured tenant-portal origin.
PROCESS_COMPLETIONTenant portal URL or emptyA workflow completed or ended with an error.instanceGuid, status, processDefinitionId, processName, entityType, entityId, initiatedByUserId, linkReceiveNotificationDisplay completed or error; optionally open the process only after URL validation.
PROCESS_PROGRESS_UPDATETenant portal URL or emptyA workflow moved to another task or stage.instanceGuid, processDefinitionId, processName, entityType, entityId, currentTaskId, currentTaskName, optionally userId, linkReceiveNotificationRefresh process state and optionally open the current task after authentication and URL validation.
Missing or unknownAny valueFuture tenant-defined or unsupported contract.UnknownDetermined by producerTreat as info: display safe title/message text, but do not execute or navigate from an unknown action.

There is intentionally no public transaction_authorization_attempt_failed action. An invalid PIN, OTP, or authenticator-code attempt is returned only to the authenticated client that submitted it and may be written to the server security audit. It must not create an FCM, inbox, or SignalR notification for every attempt. Only a terminal state transition produces transaction_authorization_failed.

Extensible generic notifications​

SendNotificationCommand and SendNotificationToGroupCommand accept caller-defined notificationType, action, and data. Consequently, the fixed table cannot enumerate values created later by tenant processes or new backend features.

The frontend must therefore follow these compatibility rules:

  1. Match known notificationType and action values exactly.
  2. Treat an unknown or missing notificationType as info.
  3. Display the title and message for unknown types without automatically executing the action.
  4. Never interpret an unknown action as a command name, JavaScript expression, or unrestricted URL.
  5. Add a new documented contract before introducing a new action that navigates to a client screen.

SignalR-only event​

ReceiveGroupNotification is currently sent directly by TransactionService. It bypasses the shared dispatcher, so it is not persisted and does not generate FCM. Its legacy payload contains Message, requestId, and type.

Support-portal chat, presence, meeting, calling, ticket, invoice, and announcement SignalR events belong to SupportPortalHub; they are separate from the mobile FCM notification contract.

SignalR payload compatibility​

FCM always includes notificationId in its data map. SignalR keeps several established payloads for compatibility with existing applications:

SignalR eventSignalR payload
ReceiveNotificationCommon envelope: notificationId, title, message, notificationType, priority, action, data, timestamp, expiresAt, and isHistorical; group dispatch also includes groupNotificationId.
ReceiveTransactionStatus with legacy transaction actionsMessage, requestId, action, type, and action-specific fields such as IsSuccess, status, or transactionId. These legacy payloads do not contain notificationId.
ReceiveTransactionStatus for transaction authorisationCommon envelope containing notificationId and the authorisation routing data.
ReceiveApprovalNotificationMessage, requestId, and type. This legacy payload does not contain notificationId.

Prefer notificationId for deduplication whenever it is present. For legacy transaction/approval SignalR payloads, keep handlers idempotent, coalesce short-lived duplicates using the event name plus requestId, action, and status where available, and refresh authoritative backend state.

async function handleBankLingoNotification(notification) {
if (alreadyHandled(notification.notificationId)) return;

switch (notification.notificationType) {
case 'transaction_authorisation':
if (notification.action === 'transaction_authorization_required') {
await requireAuthenticationAndOpenPendingTransaction(notification.data.transactionId);
return;
}
break;

case 'approval':
if (notification.action === 'transaction_approval_required') {
await requireAuthenticationAndOpenApproval(notification.data.requestId);
return;
}
break;

case 'transaction':
await refreshTransactionState(notification.data);
return;

case 'TASK_ASSIGNMENT':
case 'PROCESS_COMPLETION':
case 'PROCESS_PROGRESS_UPDATE':
await openValidatedWorkflow(notification.data);
return;
}

showGenericNotification(notification);
}

FCM and SignalR may deliver the same logical notification while the app is active. Use notificationId where available and the legacy compatibility rule above otherwise.

Transaction-authorisation tap flow​

When notificationType=transaction_authorisation and action=transaction_authorization_required:

  1. Save the pending navigation intent.
  2. Require login or biometric authentication if there is no valid session.
  3. Call Get Pending Authorization Transactions to retrieve the authenticated profile's authoritative, non-expired list.
  4. Find the notification's transactionId in that server response. If it is absent, show that the request is expired, completed, or no longer available.
  5. Render the signing input required by the server-returned authorizationMethod.
  6. After explicit user confirmation, call Authorize Transaction.

The dedicated API pages define the request headers, command envelopes, response fields, signing-method behavior, failures, idempotency, and security requirements. Do not duplicate those command contracts in the FCM integration; the notification is only the entry point into that API flow.

Notification inbox​

After login, retrieve persisted notifications for the authenticated profile:

{
"cmd": "GetUserNotificationsQuery",
"data": "{\"target\":\"current\",\"limit\":50}"
}

Customer applications must use target=current and must not select another profile using an identifier received from a notification.

Delivery-state handling​

Application stateSignalRFCMExpected handling
Foreground and connectedYesMay also arriveHandle and deduplicate by notificationId; foreground FCM may require an in-app banner.
BackgroundUsually disconnectedYesTapping the operating-system notification opens the routing flow.
TerminatedNoYesRead the launch notification and run the same routing flow.
Logged out but installation remains registeredNoYesShow only generic lock-screen text; authenticate before fetching protected data.
FCM disabled or unavailableConnected sessions onlyNoSignalR remains unchanged; retrieve persisted notifications after login.

Frontend security requirements​

  • Never place Firebase Admin credentials or service-account JSON in a mobile or web application.
  • Never display sensitive account or transaction details from an unverified FCM payload.
  • Never approve, reject, transfer, or delete anything merely because a notification was tapped.
  • Authenticate first and retrieve the authoritative resource from the backend.
  • Reject malformed identifiers and expired requests.
  • Allowlist portal/deep-link origins before opening URL actions.
  • Do not log FCM tokens, access tokens, authorisation codes, or full notification data containing sensitive information.
  • Keep X-Device-Id stable for the application installation and consistent with the existing login/device-switch flow.

Frontend release checklist​

  • Firebase client configuration belongs to the intended application build.
  • Android notification permission and notification channels are tested on supported Android versions.
  • iOS push capability, APNs configuration, foreground presentation, and notification tap handling are tested.
  • Initial token registration and token refresh both invoke RegisterPushInstallationCommand.
  • Normal logout retains the mapping; explicit push opt-out unregisters it.
  • Foreground, background, terminated, logged-out, expired-request, wrong-profile, and device-switch cases are tested on physical devices.
  • FCM and SignalR duplicates are suppressed using notificationId, with an idempotent business-key fallback for legacy SignalR payloads.
  • Every fixed type/action in this page has a testable client fallback.

Firebase client references​