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

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