Documentation

    Concepts, value, and typical clone scenarios — less code.

    Welcome to Ring
    Quick Reference
    Getting Started
    Prerequisites
    Installation
    First Success Validation
    Next Steps
    Features
    Multi-Vendor Store
    Inventory & Stock
    Vendor Management
    Commissions & Settlements
    SubscriptionConductor
    PaymentConductor
    Ring Oracle
    Payments Overview
    Public Pools & DAO Jars
    WayForPay Payment Integration
    Wallet & Credit System
    WalletConductor
    Affiliate & Referral Enablement
    Referral Codes (Refcodes)
    NFT Exhibition Marketplace
    Solana NFT Gates
    Token Staking System
    Owner Project Lab
    Entities
    Opportunities
    Real-Time Messaging
    Ring Tasks
    WebRTC Calls & STUNner TURN
    Peer Games
    News Module
    Member Blogs
    Public Profile Pages
    Profile Account Widgets
    Ring File Cabinet
    Username Reservation System
    Scientific Editor
    Notifications
    Push Notifications with FCM (Ring-Powered)
    Email AI-CRM
    Ring Mailer & RingdomX Mail
    Tunnel Protocol
    VideoConductor
    MediaConductor
    Generative Gallery
    Authentication
    Security & Compliance
    Admin console
    Admin Wiki
    Manage via Telegram
    Locale System
    Mobile Experience
    Performance Optimization Patterns
    Examples
    Quick Start
    Basic Setup
    White Label
    Custom Branding
    Web3 Integration
    Real World
    Advanced Features
    Customization
    Quick Start — Your First Ring Clone
    Customization Guide
    Vertical Presets (SSOT)
    Ringization playbook
    Branding
    Themes
    Features
    Localization
    Token Economics Setup
    Payment Gateway Integration
    Reference Ring deployments
    Web3
    Token launch jurisdictions
    Wallet
    Wallet Security Tips
    Integrations
    Ethereum wallets (Wagmi v3)
    RingFileBase (object storage API)
    Ring CDN (RingFileBase edge)
    Deployment
    Self-hosted deployment
    Vercel Deployment
    Docker
    Environment Configuration
    Monitoring & Analytics
    Performance Optimization
    Backup & Recovery
    Architecture
    Data Model
    Security
    Real Time
    Discovery Mutation Sync
    PaymentConductor architecture
    WalletConductor architecture
    Backend Services
    Firebase Integration
    Development
    Ring MCP Server

    Quick entry (CTOs · auditors · agents)

    Welcome — mission & audiences
    Quick Reference
    Getting started
    Architecture & Auth.js
    Backend modes & databases (DB_BACKEND_MODE)
    Self-hosted
    Ring MCP Tools
    Ring MCP Server
    Token economics
    Token launch jurisdictions
    Deploy (Docker · k8s)
    Security & compliance reads
    ringdom.org — LegioX homebase
    Source — MIT license (GitHub)

    Documentation

    Concepts, value, and typical clone scenarios — less code.

    Welcome to Ring
    Quick Reference
    Getting Started
    Prerequisites
    Installation
    First Success Validation
    Next Steps
    Features
    Multi-Vendor Store
    Inventory & Stock
    Vendor Management
    Commissions & Settlements
    SubscriptionConductor
    PaymentConductor
    Ring Oracle
    Payments Overview
    Public Pools & DAO Jars
    WayForPay Payment Integration
    Wallet & Credit System
    WalletConductor
    Affiliate & Referral Enablement
    Referral Codes (Refcodes)
    NFT Exhibition Marketplace
    Solana NFT Gates
    Token Staking System
    Owner Project Lab
    Entities
    Opportunities
    Real-Time Messaging
    Ring Tasks
    WebRTC Calls & STUNner TURN
    Peer Games
    News Module
    Member Blogs
    Public Profile Pages
    Profile Account Widgets
    Ring File Cabinet
    Username Reservation System
    Scientific Editor
    Notifications
    Push Notifications with FCM (Ring-Powered)
    Email AI-CRM
    Ring Mailer & RingdomX Mail
    Tunnel Protocol
    VideoConductor
    MediaConductor
    Generative Gallery
    Authentication
    Security & Compliance
    Admin console
    Admin Wiki
    Manage via Telegram
    Locale System
    Mobile Experience
    Performance Optimization Patterns
    Examples
    Quick Start
    Basic Setup
    White Label
    Custom Branding
    Web3 Integration
    Real World
    Advanced Features
    Customization
    Quick Start — Your First Ring Clone
    Customization Guide
    Vertical Presets (SSOT)
    Ringization playbook
    Branding
    Themes
    Features
    Localization
    Token Economics Setup
    Payment Gateway Integration
    Reference Ring deployments
    Web3
    Token launch jurisdictions
    Wallet
    Wallet Security Tips
    Integrations
    Ethereum wallets (Wagmi v3)
    RingFileBase (object storage API)
    Ring CDN (RingFileBase edge)
    Deployment
    Self-hosted deployment
    Vercel Deployment
    Docker
    Environment Configuration
    Monitoring & Analytics
    Performance Optimization
    Backup & Recovery
    Architecture
    Data Model
    Security
    Real Time
    Discovery Mutation Sync
    PaymentConductor architecture
    WalletConductor architecture
    Backend Services
    Firebase Integration
    Development
    Ring MCP Server

    Quick entry (CTOs · auditors · agents)

    Welcome — mission & audiences
    Quick Reference
    Getting started
    Architecture & Auth.js
    Backend modes & databases (DB_BACKEND_MODE)
    Self-hosted
    Ring MCP Tools
    Ring MCP Server
    Token economics
    Token launch jurisdictions
    Deploy (Docker · k8s)
    Security & compliance reads
    ringdom.org — LegioX homebase
    Source — MIT license (GitHub)

    Documentation

    Concepts, value, and typical clone scenarios — less code.

    Welcome to Ring
    Quick Reference
    Getting Started
    Prerequisites
    Installation
    First Success Validation
    Next Steps
    Features
    Multi-Vendor Store
    Inventory & Stock
    Vendor Management
    Commissions & Settlements
    SubscriptionConductor
    PaymentConductor
    Ring Oracle
    Payments Overview
    Public Pools & DAO Jars
    WayForPay Payment Integration
    Wallet & Credit System
    WalletConductor
    Affiliate & Referral Enablement
    Referral Codes (Refcodes)
    NFT Exhibition Marketplace
    Solana NFT Gates
    Token Staking System
    Owner Project Lab
    Entities
    Opportunities
    Real-Time Messaging
    Ring Tasks
    WebRTC Calls & STUNner TURN
    Peer Games
    News Module
    Member Blogs
    Public Profile Pages
    Profile Account Widgets
    Ring File Cabinet
    Username Reservation System
    Scientific Editor
    Notifications
    Push Notifications with FCM (Ring-Powered)
    Email AI-CRM
    Ring Mailer & RingdomX Mail
    Tunnel Protocol
    VideoConductor
    MediaConductor
    Generative Gallery
    Authentication
    Security & Compliance
    Admin console
    Admin Wiki
    Manage via Telegram
    Locale System
    Mobile Experience
    Performance Optimization Patterns
    Examples
    Quick Start
    Basic Setup
    White Label
    Custom Branding
    Web3 Integration
    Real World
    Advanced Features
    Customization
    Quick Start — Your First Ring Clone
    Customization Guide
    Vertical Presets (SSOT)
    Ringization playbook
    Branding
    Themes
    Features
    Localization
    Token Economics Setup
    Payment Gateway Integration
    Reference Ring deployments
    Web3
    Token launch jurisdictions
    Wallet
    Wallet Security Tips
    Integrations
    Ethereum wallets (Wagmi v3)
    RingFileBase (object storage API)
    Ring CDN (RingFileBase edge)
    Deployment
    Self-hosted deployment
    Vercel Deployment
    Docker
    Environment Configuration
    Monitoring & Analytics
    Performance Optimization
    Backup & Recovery
    Architecture
    Data Model
    Security
    Real Time
    Discovery Mutation Sync
    PaymentConductor architecture
    WalletConductor architecture
    Backend Services
    Firebase Integration
    Development
    Ring MCP Server

    Quick entry (CTOs · auditors · agents)

    Welcome — mission & audiences
    Quick Reference
    Getting started
    Architecture & Auth.js
    Backend modes & databases (DB_BACKEND_MODE)
    Self-hosted
    Ring MCP Tools
    Ring MCP Server
    Token economics
    Token launch jurisdictions
    Deploy (Docker · k8s)
    Security & compliance reads
    ringdom.org — LegioX homebase
    Source — MIT license (GitHub)
    Ring Platform Logo

    Loading documentation...

    Preparing Ring Platform content

    Ring Platform Logo

    Loading documentation...

    Preparing Ring Platform content

    Ring Platform Logo

    Loading documentation...

    Preparing Ring Platform content

    Push Notifications with FCM (Ring-Powered)

    Use the Founder / Developer tabs to filter this page. FCM works in every DB_BACKEND_MODE (k8s-postgres-fcm, supabase-fcm, firebase-full) — you do not need Firestore for push alone.

    Ring stores FCM device tokens in your primary database (PostgreSQL or Firestore by backend mode), one row per user per device. Browser clients obtain a token with the Firebase Messaging SDK; the server sends with the Firebase Admin SDK.

    Firebase Web Push certificate vs staged VAPID_*

    Shipped (required for browser FCM): NEXT_PUBLIC_FIREBASE_VAPID_KEY is the public key from Firebase Console → Project Settings → Cloud Messaging → Web Push certificates for the same Firebase project as NEXT_PUBLIC_FIREBASE_PROJECT_ID. Only this variable is passed to getToken({ vapidKey }). Confirmed for ring-platform.

    Staged (not consumed by Ring code yet): VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY / VAPID_SUBJECT live in env.local.template, clone secrets, and k8s for the planned web-push dual-stack. FCM send still uses AUTH_FIREBASE_* Admin credentials — not VAPID_PRIVATE_KEY. The web-push package, services/push/, and push-sw.js are not in the tree yet.

    Notifications overview

    Notifications API

    Backend modes

    Firebase integration

    Why FCM matters for your clone

    • Reach users when the tab is closed — FCM delivers OS/browser push; Tunnel covers live in-app inbox while they are online
    • One row per device — phone + laptop + tablet stay separate; invalid tokens are cleaned when FCM reports them dead
    • Works without full Firestore — Postgres-primary clones still use Firebase only for Cloud Messaging
    • Per-clone Firebase project — each white-label needs its own Web Push certificate; copying ring-main’s key into another project breaks token subscribe

    Typical scenarios

    ScenarioWhat to do
    New clone / white-labelCreate (or reuse) a Firebase project for that clone; generate Web Push certificates there; set NEXT_PUBLIC_FIREBASE_* + NEXT_PUBLIC_FIREBASE_VAPID_KEY
    Push “not working” after copy-paste envSuspect cross-project certificate leak — regenerate Web Push certificates for the clone’s NEXT_PUBLIC_FIREBASE_PROJECT_ID, then rebuild the image (browser bundle is build-time)
    Staging dual-stack secrets earlyYou may set to the same Console public key and store / in secrets; Ring code does not read them until ships

    Shipped stack vs planned dual-stack

    LayerToday (verified)Planned (not in tree)
    Client subscribehooks/use-fcm.ts → Firebase getToken() with NEXT_PUBLIC_FIREBASE_VAPID_KEYweb-push + PushSubscription; VAPID_PUBLIC_KEY
    Service workerpublic/firebase-messaging-sw.jsPlanned push-sw.js (not present)
    Register token / subPOST /api/notifications/fcm/register (preferred by use-fcm to avoid RSC revalidation); Server Action upsertFcmToken still availablePlanned /api/push/register
    Token storefcm_tokens via lib/notifications/fcm-token-db.tsExtended schema / provider partition (plan)
    Server send + (Admin HTTP v1 )

    Related documentation

    Related documentation

    Notifications

    Prerequisite: notification types and channels before deep FCM setup.

    Notifications API

    Same-workflow: FCM register API contract and env truth for push.

    Firebase Integration

    Deep-dive: Firebase Admin, client SDK, and FCM adapter bridge.

    k8s-postgres-fcm Mode

    Depends-on: Postgres-primary mode where FCM tokens live in SQL.

    1. Docs
    2. /Features
    3. /Push Notifications with FCM (Ring-Powered)

    Updated Aug 5, 20266 min listen

    Push Notifications with FCM (Ring-Powered)

    Use the Founder / Developer tabs to filter this page. FCM works in every DB_BACKEND_MODE (k8s-postgres-fcm, supabase-fcm, firebase-full) — you do not need Firestore for push alone.

    Ring stores FCM device tokens in your primary database (PostgreSQL or Firestore by backend mode), one row per user per device. Browser clients obtain a token with the Firebase Messaging SDK; the server sends with the Firebase Admin SDK.

    Firebase Web Push certificate vs staged VAPID_*

    Shipped (required for browser FCM): NEXT_PUBLIC_FIREBASE_VAPID_KEY is the public key from Firebase Console → Project Settings → Cloud Messaging → Web Push certificates for the same Firebase project as NEXT_PUBLIC_FIREBASE_PROJECT_ID. Only this variable is passed to getToken({ vapidKey }). Confirmed for ring-platform.

    Staged (not consumed by Ring code yet): VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY / VAPID_SUBJECT live in env.local.template, clone secrets, and k8s for the planned web-push dual-stack. FCM send still uses AUTH_FIREBASE_* Admin credentials — not VAPID_PRIVATE_KEY. The web-push package, services/push/, and push-sw.js are not in the tree yet.

    Notifications overview

    Notifications API

    Backend modes

    Firebase integration

    Why FCM matters for your clone

    • Reach users when the tab is closed — FCM delivers OS/browser push; Tunnel covers live in-app inbox while they are online
    • One row per device — phone + laptop + tablet stay separate; invalid tokens are cleaned when FCM reports them dead
    • Works without full Firestore — Postgres-primary clones still use Firebase only for Cloud Messaging
    • Per-clone Firebase project — each white-label needs its own Web Push certificate; copying ring-main’s key into another project breaks token subscribe

    Typical scenarios

    ScenarioWhat to do
    New clone / white-labelCreate (or reuse) a Firebase project for that clone; generate Web Push certificates there; set NEXT_PUBLIC_FIREBASE_* + NEXT_PUBLIC_FIREBASE_VAPID_KEY
    Push “not working” after copy-paste envSuspect cross-project certificate leak — regenerate Web Push certificates for the clone’s NEXT_PUBLIC_FIREBASE_PROJECT_ID, then rebuild the image (browser bundle is build-time)
    Staging dual-stack secrets earlyYou may set to the same Console public key and store / in secrets; Ring code does not read them until ships

    Shipped stack vs planned dual-stack

    LayerToday (verified)Planned (not in tree)
    Client subscribehooks/use-fcm.ts → Firebase getToken() with NEXT_PUBLIC_FIREBASE_VAPID_KEYweb-push + PushSubscription; VAPID_PUBLIC_KEY
    Service workerpublic/firebase-messaging-sw.jsPlanned push-sw.js (not present)
    Register token / subPOST /api/notifications/fcm/register (preferred by use-fcm to avoid RSC revalidation); Server Action upsertFcmToken still availablePlanned /api/push/register
    Token storefcm_tokens via lib/notifications/fcm-token-db.tsExtended schema / provider partition (plan)
    Server send + (Admin HTTP v1 )

    Related documentation

    Related documentation

    Notifications

    Prerequisite: notification types and channels before deep FCM setup.

    Notifications API

    Same-workflow: FCM register API contract and env truth for push.

    Firebase Integration

    Deep-dive: Firebase Admin, client SDK, and FCM adapter bridge.

    k8s-postgres-fcm Mode

    Depends-on: Postgres-primary mode where FCM tokens live in SQL.

    1. Docs
    2. /Features
    3. /Push Notifications with FCM (Ring-Powered)

    Updated Aug 5, 20266 min listen

    Push Notifications with FCM (Ring-Powered)

    Use the Founder / Developer tabs to filter this page. FCM works in every DB_BACKEND_MODE (k8s-postgres-fcm, supabase-fcm, firebase-full) — you do not need Firestore for push alone.

    Ring stores FCM device tokens in your primary database (PostgreSQL or Firestore by backend mode), one row per user per device. Browser clients obtain a token with the Firebase Messaging SDK; the server sends with the Firebase Admin SDK.

    Firebase Web Push certificate vs staged VAPID_*

    Shipped (required for browser FCM): NEXT_PUBLIC_FIREBASE_VAPID_KEY is the public key from Firebase Console → Project Settings → Cloud Messaging → Web Push certificates for the same Firebase project as NEXT_PUBLIC_FIREBASE_PROJECT_ID. Only this variable is passed to getToken({ vapidKey }). Confirmed for ring-platform.

    Staged (not consumed by Ring code yet): VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY / VAPID_SUBJECT live in env.local.template, clone secrets, and k8s for the planned web-push dual-stack. FCM send still uses AUTH_FIREBASE_* Admin credentials — not VAPID_PRIVATE_KEY. The web-push package, services/push/, and push-sw.js are not in the tree yet.

    Notifications overview

    Notifications API

    Backend modes

    Firebase integration

    Why FCM matters for your clone

    • Reach users when the tab is closed — FCM delivers OS/browser push; Tunnel covers live in-app inbox while they are online
    • One row per device — phone + laptop + tablet stay separate; invalid tokens are cleaned when FCM reports them dead
    • Works without full Firestore — Postgres-primary clones still use Firebase only for Cloud Messaging
    • Per-clone Firebase project — each white-label needs its own Web Push certificate; copying ring-main’s key into another project breaks token subscribe

    Typical scenarios

    ScenarioWhat to do
    New clone / white-labelCreate (or reuse) a Firebase project for that clone; generate Web Push certificates there; set NEXT_PUBLIC_FIREBASE_* + NEXT_PUBLIC_FIREBASE_VAPID_KEY
    Push “not working” after copy-paste envSuspect cross-project certificate leak — regenerate Web Push certificates for the clone’s NEXT_PUBLIC_FIREBASE_PROJECT_ID, then rebuild the image (browser bundle is build-time)
    Staging dual-stack secrets earlyYou may set to the same Console public key and store / in secrets; Ring code does not read them until ships

    Shipped stack vs planned dual-stack

    LayerToday (verified)Planned (not in tree)
    Client subscribehooks/use-fcm.ts → Firebase getToken() with NEXT_PUBLIC_FIREBASE_VAPID_KEYweb-push + PushSubscription; VAPID_PUBLIC_KEY
    Service workerpublic/firebase-messaging-sw.jsPlanned push-sw.js (not present)
    Register token / subPOST /api/notifications/fcm/register (preferred by use-fcm to avoid RSC revalidation); Server Action upsertFcmToken still availablePlanned /api/push/register
    Token storefcm_tokens via lib/notifications/fcm-token-db.tsExtended schema / provider partition (plan)
    Server send + (Admin HTTP v1 )

    Related documentation

    Related documentation

    Notifications

    Prerequisite: notification types and channels before deep FCM setup.

    Notifications API

    Same-workflow: FCM register API contract and env truth for push.

    Firebase Integration

    Deep-dive: Firebase Admin, client SDK, and FCM adapter bridge.

    k8s-postgres-fcm Mode

    Depends-on: Postgres-primary mode where FCM tokens live in SQL.

    1. Docs
    2. /Features
    3. /Push Notifications with FCM (Ring-Powered)

    Updated Aug 5, 20266 min listen

    VAPID_PUBLIC_KEY
    VAPID_PRIVATE_KEY
    VAPID_SUBJECT
    web-push

    Operator checklist (shipped FCM)

    1. Firebase project with Cloud Messaging enabled
    2. Web Push certificate generated in that same project → NEXT_PUBLIC_FIREBASE_VAPID_KEY (also as Docker build-arg)
    3. Server service account → AUTH_FIREBASE_PROJECT_ID / AUTH_FIREBASE_CLIENT_EMAIL / AUTH_FIREBASE_PRIVATE_KEY
    4. HTTPS (or localhost) for browser permission + service worker at /firebase-messaging-sw.js
    5. Signed-in users (Auth.js) before token registration
    6. After rotating the certificate: rebuild/redeploy the image — ConfigMap alone does not refresh the browser bundle
    features/notifications/services/fcm-service.ts
    FirebaseAdapter.sendFcm*
    sendEach
    Planned webpush-service.ts using web-push
    npmfirebase, firebase-adminweb-push not installed yet

    Env map (critical)

    VariableRoleStatus
    NEXT_PUBLIC_FIREBASE_VAPID_KEYFirebase Console Web Push certificate (public) for getToken()Required — build-arg + client; only FCM subscribe reader
    VAPID_PUBLIC_KEYStaged public for future web-pushStaged in secrets/env — no process.env.VAPID_* readers yet; may equal the Console public when preparing dual-stack
    VAPID_PRIVATE_KEYStaged private for future web-push (server-only)Staged — unused by FCM Admin send
    VAPID_SUBJECTmailto: or https: contact URI for Web PushStaged

    FCM server send uses Admin SDK credentials (AUTH_FIREBASE_*), not VAPID_PRIVATE_KEY. Do not invent FCM_SERVER_KEY, FCM_VAPID_KEY, or FIREBASE_WEB_PUSH_* names — those are retired / never were Ring SSOT.

    Cross-clone certificate leak

    lib/firebase-client.ts detects known ring-main certificate prefixes (BKQ4OAwA…) when NEXT_PUBLIC_FIREBASE_PROJECT_ID is not ring-main. Symptom: token-subscribe-failed / missing authentication credential. Always use the Web Push certificate from the same Firebase project as the clone’s client config.

    Build-time vs runtime

    NEXT_PUBLIC_FIREBASE_VAPID_KEY is baked into the Next.js client bundle at image build. Cluster ConfigMap envFrom can update process env, but browsers keep the previous inlined value until rebuild. Stage VAPID_* in Secret for future dual-stack without waiting on that rebuild.

    Token flow (shipped)

    FCM token register and send

    Prerequisites

    • Auth.js session — server derives user_id; never trust a client-supplied user id
    • HTTPS or localhost; SW at /firebase-messaging-sw.js
    • Valid NEXT_PUBLIC_FIREBASE_* + matching NEXT_PUBLIC_FIREBASE_VAPID_KEY
    • Server Admin: AUTH_FIREBASE_PROJECT_ID, AUTH_FIREBASE_CLIENT_EMAIL, AUTH_FIREBASE_PRIVATE_KEY (or ADC where applicable)

    Register path

    1. 1

      Stable device fingerprint

      Persist a UUID (e.g. localStorage) and reuse it. Upsert key is (user_id, device_fingerprint).

    2. 2

      Permission + FCM token

      Notification.requestPermission(), then Firebase Messaging getToken() with vapidKey from NEXT_PUBLIC_FIREBASE_VAPID_KEY in a client component (hooks/use-fcm.ts).

    3. 3

      Persist on the server

      use-fcm prefers POST /api/notifications/fcm/register (avoids Server Action RSC revalidation storms). React can still call upsertFcmToken from app/_actions/fcm.ts when appropriate. Body: token, deviceFingerprint, optional deviceInfo / platform. Auth required.

    4. 4

      Unregister on logout

      useAuth().signOut() runs device unregister (unregisterFcmToken / fingerprint invalidation). Non-React: DELETE /api/notifications/fcm/register with the same deviceFingerprint.

    Server send (verified modules)

    ModulePath
    Domain FCM send + invalid-token cleanupfeatures/notifications/services/fcm-service.ts
    firebase-full adapter bridgelib/database/adapters/FirebaseAdapter.ts — sendFcmMessage, sendFcmToUser, sendFcmToTopic, validateFcmToken, cleanupInvalidFcmTokens
    Admin messaging accessorlib/firebase-admin.server.ts → getAdminMessaging()
    Token DB layerlib/notifications/fcm-token-db.ts
    Client hook / SWhooks/use-fcm.ts, public/firebase-messaging-sw.js

    PostgreSQL schema: data/migrations/016_fcm_jsonb_schema.sql.

    Environment (shipped + staged)

    PurposeVariablesWhere
    Firebase client + FCM subscribeNEXT_PUBLIC_FIREBASE_API_KEY, …_AUTH_DOMAIN, …_PROJECT_ID, …_MESSAGING_SENDER_ID, …_APP_ID, NEXT_PUBLIC_FIREBASE_VAPID_KEYClient / Docker build-arg
    Admin sendAUTH_FIREBASE_PROJECT_ID, AUTH_FIREBASE_CLIENT_EMAIL, AUTH_FIREBASE_PRIVATE_KEY; DB_BACKEND_MODEServer-only
    RFC Web Push (web-push)VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, VAPID_SUBJECTStaged in secrets / env — no code readers until dual-stack lands

    Troubleshooting

    IssueCauseFix
    token-subscribe-failed / auth credentialCertificate from another Firebase project (e.g. ring-main BKQ4… on a non-ring-main project)Regenerate Web Push certificates for this PROJECT_ID; rebuild image
    ConfigMap updated but browser still oldNEXT_PUBLIC_* inlined at buildRebuild/redeploy with matching build-arg
    Permission deniedUser denied notificationsRequest permission before getToken()
    401 on registerNo sessionCookie / Bearer required
    UNREGISTERED on sendStale tokenFCMService / sendFcmToUser marks row invalid
    Expecting web-push dual deliveryNot shippedKeep FCM path; VAPID_* stay staged only

    Backend modes and databases

    See-also: how token storage routes by DB_BACKEND_MODE.

    VAPID_PUBLIC_KEY
    VAPID_PRIVATE_KEY
    VAPID_SUBJECT
    web-push

    Operator checklist (shipped FCM)

    1. Firebase project with Cloud Messaging enabled
    2. Web Push certificate generated in that same project → NEXT_PUBLIC_FIREBASE_VAPID_KEY (also as Docker build-arg)
    3. Server service account → AUTH_FIREBASE_PROJECT_ID / AUTH_FIREBASE_CLIENT_EMAIL / AUTH_FIREBASE_PRIVATE_KEY
    4. HTTPS (or localhost) for browser permission + service worker at /firebase-messaging-sw.js
    5. Signed-in users (Auth.js) before token registration
    6. After rotating the certificate: rebuild/redeploy the image — ConfigMap alone does not refresh the browser bundle
    features/notifications/services/fcm-service.ts
    FirebaseAdapter.sendFcm*
    sendEach
    Planned webpush-service.ts using web-push
    npmfirebase, firebase-adminweb-push not installed yet

    Env map (critical)

    VariableRoleStatus
    NEXT_PUBLIC_FIREBASE_VAPID_KEYFirebase Console Web Push certificate (public) for getToken()Required — build-arg + client; only FCM subscribe reader
    VAPID_PUBLIC_KEYStaged public for future web-pushStaged in secrets/env — no process.env.VAPID_* readers yet; may equal the Console public when preparing dual-stack
    VAPID_PRIVATE_KEYStaged private for future web-push (server-only)Staged — unused by FCM Admin send
    VAPID_SUBJECTmailto: or https: contact URI for Web PushStaged

    FCM server send uses Admin SDK credentials (AUTH_FIREBASE_*), not VAPID_PRIVATE_KEY. Do not invent FCM_SERVER_KEY, FCM_VAPID_KEY, or FIREBASE_WEB_PUSH_* names — those are retired / never were Ring SSOT.

    Cross-clone certificate leak

    lib/firebase-client.ts detects known ring-main certificate prefixes (BKQ4OAwA…) when NEXT_PUBLIC_FIREBASE_PROJECT_ID is not ring-main. Symptom: token-subscribe-failed / missing authentication credential. Always use the Web Push certificate from the same Firebase project as the clone’s client config.

    Build-time vs runtime

    NEXT_PUBLIC_FIREBASE_VAPID_KEY is baked into the Next.js client bundle at image build. Cluster ConfigMap envFrom can update process env, but browsers keep the previous inlined value until rebuild. Stage VAPID_* in Secret for future dual-stack without waiting on that rebuild.

    Token flow (shipped)

    FCM token register and send

    Prerequisites

    • Auth.js session — server derives user_id; never trust a client-supplied user id
    • HTTPS or localhost; SW at /firebase-messaging-sw.js
    • Valid NEXT_PUBLIC_FIREBASE_* + matching NEXT_PUBLIC_FIREBASE_VAPID_KEY
    • Server Admin: AUTH_FIREBASE_PROJECT_ID, AUTH_FIREBASE_CLIENT_EMAIL, AUTH_FIREBASE_PRIVATE_KEY (or ADC where applicable)

    Register path

    1. 1

      Stable device fingerprint

      Persist a UUID (e.g. localStorage) and reuse it. Upsert key is (user_id, device_fingerprint).

    2. 2

      Permission + FCM token

      Notification.requestPermission(), then Firebase Messaging getToken() with vapidKey from NEXT_PUBLIC_FIREBASE_VAPID_KEY in a client component (hooks/use-fcm.ts).

    3. 3

      Persist on the server

      use-fcm prefers POST /api/notifications/fcm/register (avoids Server Action RSC revalidation storms). React can still call upsertFcmToken from app/_actions/fcm.ts when appropriate. Body: token, deviceFingerprint, optional deviceInfo / platform. Auth required.

    4. 4

      Unregister on logout

      useAuth().signOut() runs device unregister (unregisterFcmToken / fingerprint invalidation). Non-React: DELETE /api/notifications/fcm/register with the same deviceFingerprint.

    Server send (verified modules)

    ModulePath
    Domain FCM send + invalid-token cleanupfeatures/notifications/services/fcm-service.ts
    firebase-full adapter bridgelib/database/adapters/FirebaseAdapter.ts — sendFcmMessage, sendFcmToUser, sendFcmToTopic, validateFcmToken, cleanupInvalidFcmTokens
    Admin messaging accessorlib/firebase-admin.server.ts → getAdminMessaging()
    Token DB layerlib/notifications/fcm-token-db.ts
    Client hook / SWhooks/use-fcm.ts, public/firebase-messaging-sw.js

    PostgreSQL schema: data/migrations/016_fcm_jsonb_schema.sql.

    Environment (shipped + staged)

    PurposeVariablesWhere
    Firebase client + FCM subscribeNEXT_PUBLIC_FIREBASE_API_KEY, …_AUTH_DOMAIN, …_PROJECT_ID, …_MESSAGING_SENDER_ID, …_APP_ID, NEXT_PUBLIC_FIREBASE_VAPID_KEYClient / Docker build-arg
    Admin sendAUTH_FIREBASE_PROJECT_ID, AUTH_FIREBASE_CLIENT_EMAIL, AUTH_FIREBASE_PRIVATE_KEY; DB_BACKEND_MODEServer-only
    RFC Web Push (web-push)VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, VAPID_SUBJECTStaged in secrets / env — no code readers until dual-stack lands

    Troubleshooting

    IssueCauseFix
    token-subscribe-failed / auth credentialCertificate from another Firebase project (e.g. ring-main BKQ4… on a non-ring-main project)Regenerate Web Push certificates for this PROJECT_ID; rebuild image
    ConfigMap updated but browser still oldNEXT_PUBLIC_* inlined at buildRebuild/redeploy with matching build-arg
    Permission deniedUser denied notificationsRequest permission before getToken()
    401 on registerNo sessionCookie / Bearer required
    UNREGISTERED on sendStale tokenFCMService / sendFcmToUser marks row invalid
    Expecting web-push dual deliveryNot shippedKeep FCM path; VAPID_* stay staged only

    Backend modes and databases

    See-also: how token storage routes by DB_BACKEND_MODE.

    VAPID_PUBLIC_KEY
    VAPID_PRIVATE_KEY
    VAPID_SUBJECT
    web-push

    Operator checklist (shipped FCM)

    1. Firebase project with Cloud Messaging enabled
    2. Web Push certificate generated in that same project → NEXT_PUBLIC_FIREBASE_VAPID_KEY (also as Docker build-arg)
    3. Server service account → AUTH_FIREBASE_PROJECT_ID / AUTH_FIREBASE_CLIENT_EMAIL / AUTH_FIREBASE_PRIVATE_KEY
    4. HTTPS (or localhost) for browser permission + service worker at /firebase-messaging-sw.js
    5. Signed-in users (Auth.js) before token registration
    6. After rotating the certificate: rebuild/redeploy the image — ConfigMap alone does not refresh the browser bundle
    features/notifications/services/fcm-service.ts
    FirebaseAdapter.sendFcm*
    sendEach
    Planned webpush-service.ts using web-push
    npmfirebase, firebase-adminweb-push not installed yet

    Env map (critical)

    VariableRoleStatus
    NEXT_PUBLIC_FIREBASE_VAPID_KEYFirebase Console Web Push certificate (public) for getToken()Required — build-arg + client; only FCM subscribe reader
    VAPID_PUBLIC_KEYStaged public for future web-pushStaged in secrets/env — no process.env.VAPID_* readers yet; may equal the Console public when preparing dual-stack
    VAPID_PRIVATE_KEYStaged private for future web-push (server-only)Staged — unused by FCM Admin send
    VAPID_SUBJECTmailto: or https: contact URI for Web PushStaged

    FCM server send uses Admin SDK credentials (AUTH_FIREBASE_*), not VAPID_PRIVATE_KEY. Do not invent FCM_SERVER_KEY, FCM_VAPID_KEY, or FIREBASE_WEB_PUSH_* names — those are retired / never were Ring SSOT.

    Cross-clone certificate leak

    lib/firebase-client.ts detects known ring-main certificate prefixes (BKQ4OAwA…) when NEXT_PUBLIC_FIREBASE_PROJECT_ID is not ring-main. Symptom: token-subscribe-failed / missing authentication credential. Always use the Web Push certificate from the same Firebase project as the clone’s client config.

    Build-time vs runtime

    NEXT_PUBLIC_FIREBASE_VAPID_KEY is baked into the Next.js client bundle at image build. Cluster ConfigMap envFrom can update process env, but browsers keep the previous inlined value until rebuild. Stage VAPID_* in Secret for future dual-stack without waiting on that rebuild.

    Token flow (shipped)

    FCM token register and send

    Prerequisites

    • Auth.js session — server derives user_id; never trust a client-supplied user id
    • HTTPS or localhost; SW at /firebase-messaging-sw.js
    • Valid NEXT_PUBLIC_FIREBASE_* + matching NEXT_PUBLIC_FIREBASE_VAPID_KEY
    • Server Admin: AUTH_FIREBASE_PROJECT_ID, AUTH_FIREBASE_CLIENT_EMAIL, AUTH_FIREBASE_PRIVATE_KEY (or ADC where applicable)

    Register path

    1. 1

      Stable device fingerprint

      Persist a UUID (e.g. localStorage) and reuse it. Upsert key is (user_id, device_fingerprint).

    2. 2

      Permission + FCM token

      Notification.requestPermission(), then Firebase Messaging getToken() with vapidKey from NEXT_PUBLIC_FIREBASE_VAPID_KEY in a client component (hooks/use-fcm.ts).

    3. 3

      Persist on the server

      use-fcm prefers POST /api/notifications/fcm/register (avoids Server Action RSC revalidation storms). React can still call upsertFcmToken from app/_actions/fcm.ts when appropriate. Body: token, deviceFingerprint, optional deviceInfo / platform. Auth required.

    4. 4

      Unregister on logout

      useAuth().signOut() runs device unregister (unregisterFcmToken / fingerprint invalidation). Non-React: DELETE /api/notifications/fcm/register with the same deviceFingerprint.

    Server send (verified modules)

    ModulePath
    Domain FCM send + invalid-token cleanupfeatures/notifications/services/fcm-service.ts
    firebase-full adapter bridgelib/database/adapters/FirebaseAdapter.ts — sendFcmMessage, sendFcmToUser, sendFcmToTopic, validateFcmToken, cleanupInvalidFcmTokens
    Admin messaging accessorlib/firebase-admin.server.ts → getAdminMessaging()
    Token DB layerlib/notifications/fcm-token-db.ts
    Client hook / SWhooks/use-fcm.ts, public/firebase-messaging-sw.js

    PostgreSQL schema: data/migrations/016_fcm_jsonb_schema.sql.

    Environment (shipped + staged)

    PurposeVariablesWhere
    Firebase client + FCM subscribeNEXT_PUBLIC_FIREBASE_API_KEY, …_AUTH_DOMAIN, …_PROJECT_ID, …_MESSAGING_SENDER_ID, …_APP_ID, NEXT_PUBLIC_FIREBASE_VAPID_KEYClient / Docker build-arg
    Admin sendAUTH_FIREBASE_PROJECT_ID, AUTH_FIREBASE_CLIENT_EMAIL, AUTH_FIREBASE_PRIVATE_KEY; DB_BACKEND_MODEServer-only
    RFC Web Push (web-push)VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, VAPID_SUBJECTStaged in secrets / env — no code readers until dual-stack lands

    Troubleshooting

    IssueCauseFix
    token-subscribe-failed / auth credentialCertificate from another Firebase project (e.g. ring-main BKQ4… on a non-ring-main project)Regenerate Web Push certificates for this PROJECT_ID; rebuild image
    ConfigMap updated but browser still oldNEXT_PUBLIC_* inlined at buildRebuild/redeploy with matching build-arg
    Permission deniedUser denied notificationsRequest permission before getToken()
    401 on registerNo sessionCookie / Bearer required
    UNREGISTERED on sendStale tokenFCMService / sendFcmToUser marks row invalid
    Expecting web-push dual deliveryNot shippedKeep FCM path; VAPID_* stay staged only

    Backend modes and databases

    See-also: how token storage routes by DB_BACKEND_MODE.