Push Notifications
Overview
Keymaster brokers push notifications for your app. Your backend sends notifications via Keymaster's API; Keymaster delivers to FCM (Android, iOS, Web) and Web Push (Safari). Your app never touches Firebase or APNs directly.
Architecture
Your Backend
↓ POST /push/send (client_credentials JWT)
Keymaster Push Service
↓ FCM / Web Push
User's Device
↓ delivery receipt
Keymaster → Your Webhook URL
Setup
1. Enable Notifications
In the Console: App Detail → Notifications tab → Enable.
Keymaster provisions: - A VAPID keypair for Web Push (Safari) - A webhook secret for delivery receipts
2. Register Devices (Client-Side)
After the user authenticates in your app, request push permission and register the token:
// Web (using Firebase)
import { getMessaging, getToken } from 'firebase/messaging';
const messaging = getMessaging();
const fcmToken = await getToken(messaging, { vapidKey: 'your-vapid-key' });
// Register with Keymaster
await fetch('https://keymaster.cloud-monitor.com/push/devices/register', {
method: 'POST',
headers: {
'Authorization': `Bearer ${userAccessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
app_id: 'your-app-id',
platform: 'web',
push_token: fcmToken,
}),
});
// iOS (after getting APNs token via Firebase)
func registerDevice(fcmToken: String, accessToken: String) {
var request = URLRequest(url: URL(string: "https://keymaster.cloud-monitor.com/push/devices/register")!)
request.httpMethod = "POST"
request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try? JSONSerialization.data(withJSONObject: [
"app_id": "your-app-id",
"platform": "ios",
"push_token": fcmToken,
])
URLSession.shared.dataTask(with: request).resume()
}
3. Send Notifications (Server-Side)
Authenticate via client credentials, then call the push API:
from keymaster_client import KeymasterClient # see server-to-server.md
km = KeymasterClient(
base_url="https://keymaster.cloud-monitor.com",
client_id="your-app-id",
client_secret="your-secret",
)
# Single notification
km.send_push(
user_id="user-uuid",
title="Your shift starts in 30 min",
body="Open GymOps to view details.",
data={"route": "/schedule"},
)
API Endpoints
Register Device
POST /push/devices/register
Authorization: Bearer {user_jwt}
{
"app_id": "uuid",
"platform": "web" | "ios" | "android",
"push_token": "fcm-token-or-web-push-subscription"
}
→ { "device_id": "uuid" }
Rate limited: 10 registrations per user per hour.
Unregister Device
App-initiated (server-side):
DELETE /push/devices/{device_id}
Authorization: Bearer {client_credentials_jwt scope=push:send}
User-initiated (Account page):
DELETE /push/devices/{device_id}
Authorization: Bearer {user_jwt}
Send (Single)
POST /push/send
Authorization: Bearer {client_credentials_jwt scope=push:send}
{
"user_id": "uuid",
"title": "Notification title",
"body": "Notification body text",
"data": { "route": "/some-page" },
"ttl": 3600
}
→ { "notification_id": "uuid" }
Send Bulk (Same Message to Many)
POST /push/send/bulk
Authorization: Bearer {client_credentials_jwt scope=push:send}
{
"user_ids": ["uuid1", "uuid2", ...],
"title": "Gym closes early today",
"body": "Closing at 6pm",
"data": {}
}
→ { "notification_ids": ["uuid1", "uuid2", ...] }
Max user_ids: 500 (configurable via PUSH_MAX_BATCH_SIZE).
Send Batch (Different Messages)
POST /push/send/batch
Authorization: Bearer {client_credentials_jwt scope=push:send}
{
"notifications": [
{ "user_id": "uuid1", "title": "Shift approved", "body": "..." },
{ "user_id": "uuid2", "title": "Time off denied", "body": "..." }
]
}
Max notifications: 500.
Delivery Receipts
After FCM/APNs responds, Keymaster POSTs to your app's webhook URL:
POST https://yourapp.com/hooks/push-receipts
X-Keymaster-Signature: sha256=...
Content-Type: application/json
{
"notification_id": "uuid",
"user_id": "uuid",
"device_id": "uuid",
"app_id": "uuid",
"status": "delivered" | "failed" | "invalid_token",
"error": null,
"timestamp": "2026-03-17T12:00:00Z"
}
On invalid_token, Keymaster auto-removes the device registration.
Quotas
- Default: 10,000 notifications per day per app
- Configurable by platform admin
- Counter resets at midnight UTC
- 429 response with
Retry-Afterwhen exceeded
User Device Management
Users can view and manage their registered devices from the Account page:
GET /account/devices
Authorization: Bearer {account_jwt}
Returns all devices grouped by app, with platform and last_seen_at. Users can revoke individual devices.
Validation Rules
datafield: max 4KB (FCM limit)title: required, max 256 charactersbody: required, max 4096 characters- Send endpoints verify the target
user_idis enrolled in the calling app - Push tokens are encrypted at rest in the database