Push Notifications
Send real-time notifications to users via the LoadSites Cloud relay. Handle notifications both as system alerts and in-app events.
Push notifications require a LoadSites license (Basic tier or higher). The notification relay infrastructure is managed by LoadSites Cloud. See Licensing below for tier details.
Each license comes with two keys:
license_key(public) — Included in your manifest and used by the LoadSites container app when registering or unregistering devices. Safe to embed in client-side code.secret_key(private) — Used exclusively by your backend server to send notifications via the/sendand/send-batchendpoints. Never expose this key in client-side code, your manifest, or source control.
Architecture
Your Server LoadSites Cloud User's Device
┌──────────┐ HTTP POST ┌──────────────────┐ FCM ┌──────────────────┐
│ │ ─────────────► │ │ / APNS │ LoadSites App │
│ Backend │ │ Notification │ ───────► │ ┌────────────┐ │
│ Server │ device_key │ Relay Service │ │ │ Your CWA │ │
│ │ + secret_key │ │ │ │ App │ │
└──────────┘ └──────────────────┘ │ └────────────┘ │
└──────────────────┘
Container App LoadSites Cloud
┌──────────────────┐ POST ┌──────────────────┐
│ LoadSites App │ ──────► │ Register / │ Stores FCM token +
│ (on device) │ │ Unregister │ device_key mapping
│ │ │ │
│ uses │ └──────────────────┘
│ license_key │
└──────────────────┘
- Your app requests a device key via the bridge.
- You send the device key to your backend server.
- Your server sends notifications to the LoadSites Cloud API using the device key + your secret key.
- LoadSites Cloud delivers via APNS (iOS) or FCM (Android) — the standard platform push channels.
- The LoadSites container shows a system notification or forwards it to the open app.
All push notifications flow through Apple's APNS and Google's FCM — the same infrastructure used by all native apps. The LoadSites container does not implement custom background networking, persistent sockets, or polling. This ensures compliance with platform battery and background execution policies.
Client-Side Setup
Three steps to enable notifications in your CWA app:
Step 1: Declare the Permission
Add "notifications" to your manifest's permissions array:
{
"permissions": ["notifications", "haptics", "storage"]
}
Step 2: Request Permission & Get Device Key
async function setupNotifications() {
// Request OS-level notification permission
const perm = await LoadSites.notifications.requestPermission();
if (!perm.granted) {
console.log('User declined notifications');
return;
}
// Get the unique device key for this installation
const result = await LoadSites.notifications.getDeviceKey();
const deviceKey = result.deviceKey;
// Send the device key to your server
await fetch('https://your-api.com/register-device', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ deviceKey: deviceKey })
});
console.log('Notifications registered with key:', deviceKey);
}
Step 3: Handle In-App Notifications
When a notification arrives while the app is open, the container forwards it to your app instead of showing a system notification. Register a handler:
// Listen for notifications received while app is in foreground
LoadSites.notifications.onNotification(function(notification) {
// notification = {
// title: 'New Message',
// body: 'You have a message from Alice',
// data: { messageId: '123', sender: 'alice' }
// }
// Option A: Show an in-app toast
showToast(notification.title, notification.body);
// Option B: Update a badge count
updateBadgeCount(notification.data);
// Option C: Navigate to the relevant content
if (notification.data.messageId) {
navigateToMessage(notification.data.messageId);
}
});
When the app is in the foreground, the notification is delivered to your
onNotification callback and no system notification is shown.
When the app is in the background or closed, a system notification
is shown and tapping it opens the app.
Server-Side Registration
Device registration is handled automatically by the LoadSites container app using the
license_key from your manifest. When the user grants notification permission,
the container calls the register endpoint with the device's FCM token and a unique device key.
You do not need to call these endpoints from your backend — the container handles registration and unregistration. The endpoints are documented here for reference.
Register a Device
POST https://api.loadsites.app/v1/notifications/register
// Node.js example
const response = await fetch('https://api.loadsites.app/v1/notifications/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
fcmToken: 'firebase-cloud-messaging-token',
deviceKey: 'unique-device-uuid',
domain: 'yourdomain.com',
appId: 'default',
licenseKey: 'LS-XXXX-YYYY-ZZZZ'
})
});
| Field | Type | Status | Description |
|---|---|---|---|
fcmToken |
string | Required | Firebase Cloud Messaging token for the device. |
deviceKey |
string | Required | Unique per-domain device key (UUID). |
domain |
string | Required | The domain of the CWA app. |
appId |
string | Required | Which app within the domain. Defaults to "default". |
licenseKey |
string | Required | Your LoadSites license key. |
Returns { "success": true } on success, or 403 if your license tier doesn't allow notifications.
Unregister a Device
POST https://api.loadsites.app/v1/notifications/unregister
const response = await fetch('https://api.loadsites.app/v1/notifications/unregister', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
deviceKey: 'unique-device-uuid',
domain: 'yourdomain.com',
appId: 'default',
licenseKey: 'LS-XXXX-YYYY-ZZZZ'
})
});
Returns { "success": true } on success, or 404 if the registration was not found.
Sending Notifications (Server-Side)
Send notifications from your backend server by POSTing to the LoadSites Cloud API. These endpoints require your secret key (not the license key).
The /send and /send-batch endpoints authenticate with your
secretKey — the private key from your LoadSites dashboard. This is
different from the license_key in your manifest. Never expose the secret
key in client-side code.
Single Device
POST https://api.loadsites.app/v1/notifications/send
// Node.js example
const response = await fetch('https://api.loadsites.app/v1/notifications/send', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
deviceKey: 'target-device-key-here',
secretKey: process.env.LOADSITES_SECRET_KEY,
title: 'Order Shipped',
body: 'Your order #1234 has shipped!',
type: 'standard',
appId: 'default',
data: {
orderId: '1234',
status: 'shipped'
}
})
});
Batch Sending
Send to multiple devices in a single request:
POST https://api.loadsites.app/v1/notifications/send-batch
const response = await fetch('https://api.loadsites.app/v1/notifications/send-batch', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
deviceKeys: [
'device-key-1',
'device-key-2',
'device-key-3'
],
secretKey: process.env.LOADSITES_SECRET_KEY,
title: 'System Update',
body: 'Version 2.0 is now available',
type: 'standard',
appId: 'default',
data: { version: '2.0.0' }
})
});
API Responses
Single device response:
{
"success": true,
"messageId": "msg-abc123"
}
Batch response:
{
"success": true,
"sent": 3,
"failed": 0
}
Error Responses
The send endpoints return standard HTTP status codes with a JSON error body:
{
"error": "Device key not registered for notifications",
"code": "NOT_REGISTERED"
}
| HTTP Status | Error Code | Description |
|---|---|---|
400 |
— | Missing required fields in the request body. |
403 |
— | Invalid secretKey, or the license tier doesn't allow notifications. |
403 |
USER_DISABLED |
The user has disabled notifications for this app on their device. |
404 |
NOT_REGISTERED |
The device key was never registered for notifications. |
When you receive a 403 with "code": "USER_DISABLED", the user has
toggled off notifications for this app from their LoadSites home screen. Do not retry —
respect the user's preference and stop sending until they re-enable.
Send Notification Fields
| Field | Type | Status | Description |
|---|---|---|---|
deviceKey |
string | Required | Target device key. Use deviceKeys (array) for batch sends. |
secretKey |
string | Required | Your LoadSites secret key (private, server-only). Not the license_key from your manifest. |
title |
string | Required | Notification title displayed to the user. |
body |
string | Required | Notification body text. |
type |
string | Optional |
Notification type: "standard" (default) or "call".
The "call" type shows a high-priority notification with Answer/Decline buttons and a ringtone.
|
appId |
string | Optional | Which app within the domain this notification is for. Defaults to "default". |
actionData |
object | Optional | Custom data passed to the CWA when the user acts on the notification (taps, answers, declines). |
data |
object | Optional | Additional custom data payload. Delivered to onNotification callback and available when the user taps the notification. |
Handling Notifications in Your App
In-App (Foreground)
Use onNotification to handle notifications when the user is actively using your app.
Common patterns include:
- Toast / snackbar — Show a brief popup with the notification content
- Badge update — Increment an unread counter on a tab or icon
- Live update — Refresh the current view with new data from
notification.data - Sound / haptic — Play a chime or trigger
LoadSites.haptics.notification('success')
// Complete example: in-app notification handling
LoadSites.notifications.onNotification(function(notification) {
// Play haptic feedback
LoadSites.haptics.notification('success');
// Show a toast
const toast = document.createElement('div');
toast.className = 'in-app-toast';
toast.innerHTML = '<strong>' + notification.title + '</strong><p>' + notification.body + '</p>';
document.body.appendChild(toast);
// Auto-dismiss after 4 seconds
setTimeout(function() { toast.remove(); }, 4000);
// Handle the data payload
if (notification.data && notification.data.type === 'message') {
updateMessageBadge();
}
});
Background & Tap-to-Open
When the user taps a system notification to open the app, the notification data is available
through the normal app launch flow. The container stores the most recent notification and
delivers it via onNotification when the app initializes.
Unsubscribing
// Remove a specific callback
function myHandler(notification) { /* ... */ }
LoadSites.notifications.onNotification(myHandler);
// Later...
LoadSites.notifications.offNotification(myHandler);
Security Model
Push notifications are delivered through the platform's standard push infrastructure — Apple Push Notification Service (APNS) on iOS and Firebase Cloud Messaging (FCM) on Android. The LoadSites Cloud relay forwards notifications through these official channels; no custom background networking or persistent connections are used. This architecture complies with Apple and Google platform policies for push notification delivery.
Key Separation
LoadSites uses a two-key model to separate client-side and server-side concerns:
| Key | Visibility | Used For | Endpoints |
|---|---|---|---|
license_key |
Public | Manifest, device registration | /register, /unregister, /license/* |
secret_key |
Private | Sending notifications from your server | /send, /send-batch |
The license_key is safe to include in your manifest because it can only
register or unregister devices — it cannot send notifications. Only the
secret_key can trigger notification delivery, and it should never leave
your server environment.
Additional Protections
- Notifications are scoped to your domain — you can only send to devices that installed your app
- The
deviceKeyis unique per device + domain combination - The
license_keyauthenticates device registration; thesecret_keyauthenticates send requests - App-facing endpoints are protected by Firebase App Check, which verifies requests originate from a genuine LoadSites app installation
- The
datapayload is encrypted in transit (HTTPS/TLS) - Users can disable notifications per-app from the LoadSites home screen
- CWA apps cannot send notifications without the user granting OS-level permission
- The
notificationspermission must be declared in the manifest and accepted at install time
Best Practices
- Request permission at the right time — Don't ask immediately on first load. Wait until the user takes an action that makes notifications relevant (e.g., enabling alerts for an order).
- Keep payloads small — The
dataobject should be lightweight. Use it to reference content, not to carry it. - Handle foreground gracefully — Always register an
onNotificationhandler. A system notification when the app is already open feels jarring. - Respect the toggle — Users can disable notifications via the toggle on their home screen card. Check
isAllowed()before prompting. - Batch when possible — Use the batch API for broadcasts to reduce API calls and improve delivery speed.
Licensing
Push notifications are the only CWA feature that requires a paid license. Each license includes two keys:
license_key— Add this to your manifest. The LoadSites container uses it to register devices for notifications.secret_key— Store this securely on your server (e.g., as an environment variable). Use it to send notifications via the Cloud API.
Both keys are available in your LoadSites dashboard after purchasing a license.
| Tier | Monthly Price | Notifications / Month | Features |
|---|---|---|---|
| Free | $0 | 0 | All bridge APIs except notifications |
| Basic | $9 | 10,000 | Push notifications, email support |
| Pro | $29 | 100,000 | Push notifications, priority support, analytics dashboard |
| Enterprise | Custom | Unlimited | Custom SLA, dedicated infrastructure, white-label option |
Camera, haptics, biometrics, geolocation, storage, share, clipboard, network, device info, and navigation — all available at no cost, no license needed.
Troubleshooting
Notifications not arriving
- Verify the
license_keyin your manifest is valid and active - Verify you are using the
secret_key(notlicense_key) in your server's/sendcalls - Check that the user has enabled notifications (use
isAllowed()) - Ensure the
deviceKeyis correct and the device is registered - Check the Cloud API response for error codes (
USER_DISABLED,NOT_REGISTERED)
onNotification not firing
- Make sure you register the callback early (before any notifications could arrive)
- Verify the app is actually in the foreground — background notifications show as system alerts
- Check the browser console for errors in your callback function