Skip to main content

Send Push Notifications to a User

This document describes the production-style GOFA API for sending push notifications by user ID instead of by a raw device token.

Relevant backend file:

  • gofa-web-nextjs/src/app/api/fcm/send-to-user/route.ts

Endpoint:

POST /api/fcm/send-to-user

Why this endpoint exists

The older token-based endpoint is still useful for:

  • local debugging,
  • single-device testing,
  • payload validation.

But it is not the ideal production contract.

In production, the frontend should not decide which exact FCM token to use.

The backend should:

  1. receive a target user ID,
  2. load that user's registered devices from Firestore,
  3. send to one or more active tokens,
  4. clean up invalid tokens when delivery fails.

That is what this endpoint does.


Firestore data model used by this endpoint

The endpoint relies on the token registration flow documented in the FCM Token API.

Expected location:

Clients/{clientId}/ClientUsers/{uid}/fcmTokens

Each token entry looks like:

{
"fcmToken": "<token>",
"platform": "ios",
"installationId": "native-1772433667723-17uxidu",
"deviceInfo": {
"source": "flutter_webview",
"environment": "uat",
"app": "silvercare"
},
"createdAt": "...",
"updatedAt": "...",
"lastSeenAt": "..."
}

This means the backend already knows all devices registered under a user.


Authentication

This endpoint requires admin-level authentication.

It uses the same request authentication system as the rest of the GOFA API.

Current implementation:

  • authenticateRequest({ request, clientId, accessRole: "admin" })

That means this route is intended for:

  • internal admin tools,
  • backend services,
  • trusted server-to-server callers.

It is not intended to be called directly by ordinary end users.


Request body

Minimal request shape:

{
"uid": "lz2ThACquXbpZMRsYHNZ78ElqB42",
"type": "open_settings_test",
"title": "Open Settings",
"body": "Tap to open the settings page",
"url": "/settings"
}

Optional platform filter:

{
"uid": "lz2ThACquXbpZMRsYHNZ78ElqB42",
"type": "open_settings_test",
"title": "Open Settings",
"body": "Tap to open the settings page",
"url": "/settings",
"platforms": ["ios"]
}

Field meanings:

  • uid: target user
  • type: app-level notification type
  • title: visible system notification title
  • body: visible system notification body
  • url: in-app route target after tap
  • platforms: optional filter for web, ios, or android

How the endpoint works

Step 1: resolve client context

The route first resolves clientId using the same GOFA client resolution logic used elsewhere in the API.

Step 2: authenticate the sender

The route requires admin access.

Step 3: load the target user document

It reads:

Clients/{clientId}/ClientUsers/{uid}

Step 4: read fcmTokens

The route loads every registered token under that user.

Step 5: optionally filter by platform

If platforms is provided, only matching token records are used.

Step 6: send to each token

The endpoint sends a mixed payload for each token:

  • notification for system banners
  • data for app routing behavior
  • android overrides for Android delivery
  • apns overrides for iOS delivery
  • webpush compatibility for web use cases

Step 7: clean up invalid tokens

If Firebase returns one of these token-invalid errors:

  • messaging/registration-token-not-registered
  • messaging/invalid-registration-token

then that token entry is removed from Firestore.

This keeps the user's token list healthy over time.


Example response

Successful response:

{
"success": true,
"uid": "lz2ThACquXbpZMRsYHNZ78ElqB42",
"requestedCount": 1,
"sentCount": 1,
"failedCount": 0,
"cleanedUpInvalidTokenCount": 0,
"sentByUid": "admin-user-uid",
"protocolVersion": "1",
"results": [
{
"tokenKey": "installation:native-1772433667723-17uxidu",
"platform": "ios",
"success": true,
"messageId": "projects/gofa-sdk/messages/..."
}
]
}

Why this is the preferred production API

This API is better than sending by raw token because:

  1. the caller only needs a uid,
  2. the backend controls device selection,
  3. all of a user's active devices can be reached,
  4. invalid tokens can be cleaned automatically,
  5. the contract is safer and easier to evolve.

This is the recommended API shape for production notification flows.


Example local development script

A minimal local script can call this endpoint directly.

Example:

  • gofa-web-nextjs/send-to-user.ts

Run it with:

bunx tsx send-to-user.ts

This is useful for local testing while keeping the backend contract aligned with production behavior.