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:
- receive a target user ID,
- load that user's registered devices from Firestore,
- send to one or more active tokens,
- 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 usertype: app-level notification typetitle: visible system notification titlebody: visible system notification bodyurl: in-app route target after tapplatforms: optional filter forweb,ios, orandroid
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:
notificationfor system bannersdatafor app routing behaviorandroidoverrides for Android deliveryapnsoverrides for iOS deliverywebpushcompatibility for web use cases
Step 7: clean up invalid tokens
If Firebase returns one of these token-invalid errors:
messaging/registration-token-not-registeredmessaging/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:
- the caller only needs a
uid, - the backend controls device selection,
- all of a user's active devices can be reached,
- invalid tokens can be cleaned automatically,
- 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.