Integration: Firebase Cloud Messaging (FCM)

Summary

mam-main uses FCM v1 (the OAuth2 service-account JWT path), not the deprecated server-key API. Implementation is in includes/push-notification-manager/android/.


Components

Class Path Role
MAM_AndroidPushSender android/ FCM v1 sender
MAM_AndroidPayloadBuilder android/ Builds the FCM JSON payload
MAM_AndroidCredentials android/ Loads the Firebase service-account JSON path + project id
MAM_FirebaseJwtSigner auth/ Signs the OAuth2 JWT for FCM v1

Device tokens are read directly via get_user_meta() in the mam_push_notification_manager bridge — the repo/MAM_PushTokenRepository.php file is an empty stub, not an implemented class.


Credentials

Asset WordPress option
Firebase service-account JSON android_pn_pem_certs — the filename; MAM_AndroidCredentials resolves it to mam-main/pemcerts/<file>
FCM project id mam_firebase_project_id (a distinct option — not read from the JSON)

MAM_AndroidCredentials::load() returns null (and emits an admin_alert) if either the service-account file or the project id is missing. The service-account must have the messaging.send scope, or FCM v1 returns 403 Forbidden and the send silently fails.

MAM_AndroidCredentials is the only legitimate read path.

⚠️ Plaintext storage. Same hardening gap as APNs.


OAuth2 JWT flow

MAM_FirebaseJwtSigner signs a JWT using the service-account’s private key:

  1. Header: { "alg": "RS256", "typ": "JWT", "kid": "<service-account-key-id>" }
  2. Claims: { "iss": "<service-account-email>", "scope": "https://www.googleapis.com/auth/firebase.messaging", "aud": "https://oauth2.googleapis.com/token", "iat": <now>, "exp": <now+1hr> }
  3. Signed with the service-account’s RSA private key

The JWT is exchanged at oauth2.googleapis.com/token for an OAuth2 access token. The access token is cached for ~1 hour. Subsequent FCM sends use the access token in the Authorization: Bearer header.


Send endpoint

POST https://fcm.googleapis.com/v1/projects/<project_id>/messages:send
Authorization: Bearer <oauth2_access_token>
Content-Type: application/json

{
  "message": {
    "token": "<device_fcm_token>",
    "notification": { "title": "...", "body": "..." },
    "data": { ... },
    "android": { "priority": "high", ... }
  }
}

Device tokens

usermeta key mam_app_android_pn_token (written by the app on first launch).


Common errors

FCM error Meaning Action
UNREGISTERED Token is invalid (app uninstalled, etc.) Drop the stored token
INVALID_ARGUMENT Bad payload shape Fix the payload builder
MISMATCH_SENDER_ID Token belongs to a different FCM project Check the service-account JSON; token might be from a previous app build
QUOTA_EXCEEDED Per-project send quota exceeded Back off
UNAVAILABLE FCM transient outage Retry with backoff (no automatic retry today)

Send flow

do_action('mam_notification_send_pn', $msg)
        │
        ▼
mam_push_notification_manager::send_push_notification()
        │ wraps in MAM_PushNotification
        ▼
MAM_PushDispatcher::dispatch()
        │
        ▼
MAM_AndroidPushSender::send()
        │
        ▼
MAM_FirebaseJwtSigner::getAccessToken()  ← cached for ~1hr
        │
        ▼
HTTP POST to FCM v1 endpoint
        │
        ▼
MAM_PushResult (status + errors per-token)

Gotchas

  • FCM v1, not legacy. Don’t try to use a server key; mam-main only supports v1.
  • messaging.send scope is mandatory. A service-account without it returns 403 Forbidden.
  • Token churn — FCM tokens change when the user reinstalls or clears app data. UNREGISTERED errors should drop the stored token.
  • MISMATCH_SENDER_ID typically means the device installed a previous app build under a different FCM project. The customer needs to reinstall.
  • No retry on transient UNAVAILABLE — failed sends stay failed.
  • Plaintext credentials are a known hardening gap.

  • Recipe: Configure push notifications
  • Integration: Apple Push Notification service (APNs)
  • Notification channels: email, SMS, push
  • Hook: mam_notification_send_pn

Metadata

Field Value
Article type Plugin Overview
Plugin slug mam-main
Applies to plugin version 2.1.11+
Category Plugin Reference
Audience PHP developer
Last verified 2026-05-02
Was this article helpful?
Contents

    Need Support?

    Can't find the answer you're looking for? Don't worry we're here to help!